I believe the URI should be freed before being set to null. When I try to dump it in previous line, it still contains the URI value.
Try this simple example that sends a GET request 100 times in a loop.
Since the URI consists of the path and query, longer values could exacerbate the leak.
Here's a simple Python server that listens on port 8000 and responds with "Hello" to all incoming requests:
from http.server import HTTPServer, BaseHTTPRequestHandler
import hashlib
import os
import re
# Define the username, password, realm, and quality of protection
USERNAME = "admin"
PASSWORD = "admin"
REALM = "Simple Digest Auth"
NONCE = os.urandom(16).hex() # Fixed nonce for simplicity in this example
QOP = "auth" # Define qop as "auth" for quality of protection
# The response message to send to authenticated clients
RESPONSE_MESSAGE = "hello"
def generate_digest_response(username, realm, password, method, uri, nonce, nc, cnonce, qop):
"""Generate the expected Digest response for comparison."""
# HA1: Hash of username:realm:password
ha1 = hashlib.md5(f"{username}:{realm}:{password}".encode()).hexdigest()
# HA2: Hash of method:uri
ha2 = hashlib.md5(f"{method}:{uri}".encode()).hexdigest()
# Response: Hash of HA1:nonce:nc:cnonce:qop:HA2
response = hashlib.md5(f"{ha1}:{nonce}:{nc}:{cnonce}:{qop}:{ha2}".encode()).hexdigest()
return response
def parse_digest_header(header):
"""Parse the Digest Authorization header into a dictionary."""
# Regular expression to match key="value" pairs
pattern = re.compile(r'(\b\w+\b)="?([^",]+)"?')
return dict(pattern.findall(header))
class DigestAuthHandler(BaseHTTPRequestHandler):
def do_GET(self):
# Parse the Authorization header for Digest Authentication
auth_header = self.headers.get('Authorization')
if auth_header is None or not auth_header.startswith('Digest '):
self.request_authentication()
return
# Parse the digest values using the parse_digest_header function
auth_params = parse_digest_header(auth_header[7:])
# Validate the username, realm, nonce, uri, qop, nc, cnonce, and response
if (auth_params.get("username") == USERNAME and
auth_params.get("realm") == REALM and
auth_params.get("nonce") == NONCE and
auth_params.get("uri") == self.path and
auth_params.get("qop") == QOP):
# Compute the expected response using the qop, nc, and cnonce values
expected_response = generate_digest_response(
USERNAME, REALM, PASSWORD, self.command, self.path,
NONCE, auth_params.get("nc"), auth_params.get("cnonce"), QOP
)
# If the response matches, grant access
if auth_params.get("response") == expected_response:
# Respond with the specified message and set Content-Length header
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.send_header("Content-Length", str(len(RESPONSE_MESSAGE)))
self.end_headers()
self.wfile.write(RESPONSE_MESSAGE.encode())
return
# If authentication fails, request it again
self.request_authentication()
def request_authentication(self):
"""Request Digest Authentication from the client with qop="auth", without returning a payload."""
self.send_response(401)
self.send_header(
'WWW-Authenticate',
f'Digest realm="{REALM}", nonce="{NONCE}", qop="{QOP}", algorithm="MD5"'
)
self.send_header('Content-type', 'text/plain')
self.end_headers() # No payload is written to the response body
# Define the server's address and port
server_address = ('', 8000)
httpd = HTTPServer(server_address, DigestAuthHandler)
print("Server running on port 8000...")
httpd.serve_forever()
https://github.com/espressif/esp-idf/blob/7a305c0284b7af7cd8b8f12b48f72e2685d9a363/components/esp_http_client/esp_http_client.c#L641
I believe the URI should be freed before being set to null. When I try to dump it in previous line, it still contains the URI value.
Try this simple example that sends a GET request 100 times in a loop.
Since the URI consists of the path and query, longer values could exacerbate the leak.
Here's a simple Python server that listens on port 8000 and responds with "Hello" to all incoming requests: