Gopher SSRF Protocol Abuse: Notes From Real Targets
Most SSRF writeups stop at http://169.254.169.254. Fine for cloud metadata. Boring when the sink already blocks link-local and RFC1918 HTTP. The interesting question is whether the HTTP client still understands gopher://.
Gopher lets you push nearly arbitrary bytes after the host and port. That turns "the server fetched a URL for me" into "the server opened a TCP socket and wrote my payload." Redis, Memcached, SMTP, and some FastCGI listeners are chatty enough that a short scripted conversation is enough.
Confirm the scheme before crafting Redis
Hit a collaborative canary first with http:// and https://. Then try gopher://, dict://, and ftp:// against an endpoint you control. If only HTTP survives, stop. Do not invent a Gopher chain for a library that already strips unknown schemes.
When Gopher works, watch how the client encodes the path. Many stacks percent-decode once, then send the remainder as the Gopher selector. Newlines become %0D%0A. Spaces and ? need encoding too. A payload that looks clean in Repeater can arrive as a single malformed line on the wire.
I keep a tiny notebook of which languages still cooperate. Older curl bindings, some PHP file_get_contents setups, and Java URL handlers from forgotten utility jars show up more than "modern" frameworks. If the app shells out to curl with your URL string, you may get schemes the application library claimed to block.
Redis is the usual lab target
A classic shape talks to an internal Redis with no auth:
gopher://127.0.0.1:6379/_*1%0d%0a$4%0d%0ainfo%0d%0a
If the SSRF response body reflects the socket output, INFO or CONFIG GET proves reachability. Prefer read-only commands in production programs. Writing webshells via CONFIG SET dir is how you leave scope and burn trust.
SMTP is similar: EHLO, MAIL FROM, RCPT TO, DATA. Prove you can speak the protocol with a message to an address you own on an internal relay—if the program allows that class of test. Otherwise stick to banner grabs and HELP.
FastCGI and memcached need more care. Wrong bytes can crash a worker. Keep payloads minimal, use unique canaries, and never spray management ports across a shared production fleet hoping something answers.
Encoding traps that waste a weekend
Trailing nulls, double CR/LF requirements, and URL parsers that reject % sequences will break otherwise perfect Redis RESP. Try both %0a and %0d%0a. Some clients prepend a selector slash you did not ask for; compensate by adjusting the first command framing.
Also test whether the SSRF follows redirects from an HTTP canary into Gopher. An allowlist that only inspects the first hop is still common. If redirects are followed, host your 302 on an approved domain and bounce into gopher://127.0.0.1:6379/....
What triage needs to see
Show three things: the parameter that accepted gopher://, the internal host:port that answered, and a canary proving your bytes arrived. A screenshot of redis_version beats a theoretical "I could have RCE'd Redis."
Defenders should disable non-HTTP schemes at the URL parser, pin allowlists to https: only, and keep management ports off the application network. If a legacy client must support Gopher for some forgotten reason, it should not be the same client that fetches user-supplied URLs.
I've seen hunters skip Gopher because "modern languages removed it." Check anyway. Curl-based wrappers, old Java URL handlers, and copy-pasted SSRF filters still surprise people.