Skip to content

HTTP Request Parsing Desynchronization via Case-Sensitive Transfer-Encoding Handling #604

@TuranSecurityCNA

Description

@TuranSecurityCNA

Tinyproxy version

Tested on:

Tinyproxy 1.11.3 (latest stable)
Also reproducible on git master (as of current commit)

Issue:

Summary

Tinyproxy is vulnerable to HTTP Request Parsing Desynchronization due to improper, case-sensitive handling of the Transfer-Encoding header in src/reqs.c.

The function is_chunked_transfer() relies on strcmp() to compare the header value with "chunked", while RFC 7230 specifies that transfer codings are case-insensitive.

Technical Details
static int is_chunked_transfer (pseudomap *hashofheaders) {
char *data = pseudomap_find (hashofheaders, "transfer-encoding");
return data ? !strcmp (data, "chunked") : 0;
}

Because of this:

Transfer-Encoding: chunked → handled correctly
Transfer-Encoding: Chunked → NOT recognized
Behavior

When Transfer-Encoding: Chunked is used:

Tinyproxy fails to detect chunked encoding

Sets:

content_length.client = -1
Assumes request has no body
Skips pull_client_data_chunked()
Immediately forwards headers upstream
Switches to relay_connection() (raw TCP proxying)

At this point, any unread body remains in the client socket buffer and is blindly proxied to backend without HTTP parsing.

Impact

  1. WAF / Filtering Bypass
    Request body is never processed by Tinyproxy
    Any logic depending on parsed body is bypassed
    Raw payload is forwarded via TCP relay

This allows:

Smuggling payloads past inspection layers
Bypassing ICAP/WAF integrations relying on Tinyproxy parsing
2. Backend Denial of Service (DoS)

If attacker sends:

Transfer-Encoding: Chunked

but does not complete the chunk stream:

Tinyproxy forwards headers immediately
Backend (RFC-compliant) expects chunked body
Backend connection hangs waiting for chunks

By repeating this:

Backend worker pool is exhausted
Application-level DoS occurs

Steps to Reproduce

  1. Start backend (Node.js example)
    const http = require('http');

http.createServer((req, res) => {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
console.log(Received Body: ${body});
res.writeHead(200);
res.end(Processed by Backend\n);
});
}).listen(9000);
2. Start Tinyproxy
./src/tinyproxy -d -c etc/tinyproxy.conf
3. Valid request (works)
curl -i -x http://127.0.0.1:8888
-H "Transfer-Encoding: chunked"
-H "Content-Length:"
--data-binary $'5\r\nadmin\r\n0\r\n\r\n'
http://127.0.0.1:9000/

Result:

HTTP/1.1 200 OK
4. Exploit (hang / desync)
curl -i -x http://127.0.0.1:8888
-H "Transfer-Encoding: Chunked"
-H "Content-Length:"
--data-binary $'5\r\nadmin\r\n0\r\n\r\n'
http://127.0.0.1:9000/

Result:

Request hangs indefinitely
Backend waits for chunk stream
Tinyproxy already switched to relay mode
Root Cause
Case-sensitive comparison (strcmp)
RFC 7230 violation (transfer codings must be case-insensitive)
Incorrect request state handling
Suggested Fix

Replace:

strcmp(data, "chunked")

with:

strcasecmp(data, "chunked")

or normalize header value before comparison.

Notes
Issue is reproducible consistently
Does not rely on special configuration
Requires no authentication
Works against any RFC-compliant backend (Node.js, Nginx, etc.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions