Skip to content

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

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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions