Skip to content

HTTP Request Smuggling: CL/TE desync + duplicate Content-Length forwarding #609

@TristanInSec

Description

@TristanInSec

Overview

Two HTTP request smuggling vulnerabilities in tinyproxy (commit 09312a1). Both allow an attacker to inject requests to the backend server.


Finding 1: CL/TE Desync (CWE-444, CVSS 7.5)

When a client sends both Content-Length and Transfer-Encoding: chunked headers, tinyproxy uses Content-Length to determine body size but forwards BOTH headers to the backend. Per RFC 7230 Section 3.3.3, the backend MUST use Transfer-Encoding when both are present, creating a CL/TE desync.

Vulnerable code

src/reqs.c:917-920:

connptr->content_length.client = get_content_length (hashofheaders);
if (connptr->content_length.client == -1 && is_chunked_transfer (hashofheaders))
    connptr->content_length.client = -2;

The chunked check is SKIPPED when Content-Length is present (content_length.client != -1). Both headers are forwarded to the backend (neither is in the skipheaders list at line 887-894).

Attack

  1. Client sends: Content-Length: 5\r\nTransfer-Encoding: chunked\r\n
  2. tinyproxy reads 5 bytes of body (uses CL)
  3. Backend reads body as chunked (uses TE)
  4. Data after the chunked terminator is treated as a new request

Finding 2: Duplicate Content-Length (CWE-444, CVSS 7.5)

When a client sends multiple Content-Length headers with different values, tinyproxy uses the FIRST value but forwards ALL duplicate headers. Backend servers using the LAST value read a different body size.

Vulnerable code

src/pseudomap.c:44-56 -- pseudomap_append() adds entries unconditionally (no duplicate detection):

int pseudomap_append(pseudomap *o, const char *key, char *value) {
    ...
    sblist_add(o, &e);
}

src/pseudomap.c:58-66 -- pseudomap_find_index() returns the FIRST match for body read.

The forwarding loop at src/reqs.c:953-971 sends ALL duplicates to the backend.

Attack

  1. Client sends: Content-Length: 5\r\nContent-Length: 100\r\n
  2. tinyproxy uses first CL (5), reads 5 bytes
  3. Backend uses last CL (100), reads 100 bytes as body
  4. The extra 95 bytes contain an injected HTTP request

Impact

Both enable classic HTTP request smuggling: cache poisoning, access control bypass, request hijacking against other proxy users.

Fix

  • CL/TE: When both headers are present, either reject the request (400) or strip Content-Length before forwarding (per RFC 7230 Section 3.3.3).
  • Duplicate CL: Reject requests with multiple Content-Length headers having different values (per RFC 7230 Section 3.3.3).

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