Skip to content

fix: HTTP request smuggling via Transfer-Encoding list and redirect credential leak#63133

Closed
mohammadmseet-hue wants to merge 2 commits intodart-lang:mainfrom
mohammadmseet-hue:fix-http-smuggling-and-redirect
Closed

fix: HTTP request smuggling via Transfer-Encoding list and redirect credential leak#63133
mohammadmseet-hue wants to merge 2 commits intodart-lang:mainfrom
mohammadmseet-hue:fix-http-smuggling-and-redirect

Conversation

@mohammadmseet-hue
Copy link
Copy Markdown

Summary

Two security fixes in dart:io HTTP implementation:

1. HTTP Request Smuggling via Transfer-Encoding list parsing

File: sdk/lib/_http/http_parser.dart:751

RFC 7230 section 3.3.1 allows Transfer-Encoding to be a comma-separated list (e.g. gzip, chunked). The parser only checked for an exact match on "chunked", so it missed chunked encoding when other encodings preceded it in the list.

This creates a CL/TE desync (HTTP request smuggling) when a Dart HTTP server runs behind a compliant reverse proxy. The proxy correctly parses the list and reads a chunked body, while Dart falls back to Content-Length, causing the two to disagree on message boundaries.

Fix: Parse Transfer-Encoding as a comma-separated list and check each token individually for "chunked".

2. shouldCopyHeaderOnRedirect parameter swap — credential leak on redirect

File: sdk/lib/_http/http_impl.dart:3107

The call site passed resolved (redirect target URL) as originalUrl and previous.uri (original request URL) as redirectUri, swapping the intended parameter order. This caused the _isSubdomain check to verify the wrong direction:

  • Redirect sub.example.com → example.com: Authorization/Cookie headers leaked to parent domain (should be stripped)
  • Redirect example.com → sub.example.com: Headers stripped (should be kept)

Fix: Swap parameters to match the function signature: shouldCopyHeaderOnRedirect(header, previous.uri, resolved).

Test plan

  • Verify Transfer-Encoding: gzip, chunked is now parsed as chunked encoding
  • Verify Transfer-Encoding: chunked still works (exact match path preserved)
  • Verify auth headers are stripped when redirecting to parent domains
  • Verify auth headers are kept when redirecting to subdomains
  • Run existing _http tests

…eader leak

Two security fixes in dart:io HTTP implementation:

1. HTTP Request Smuggling via Transfer-Encoding list (http_parser.dart)

   RFC 7230 section 3.3.1 allows Transfer-Encoding to be a comma-separated
   list (e.g. "gzip, chunked"). The parser only checked for exact match on
   "chunked", missing the chunked encoding when other encodings preceded it.
   This creates a CL/TE desync when a Dart HTTP server sits behind a
   compliant reverse proxy that correctly parses the list.

   Fix: Parse Transfer-Encoding as a comma-separated list and check each
   token individually for "chunked".

2. shouldCopyHeaderOnRedirect parameter swap (http_impl.dart)

   The call at line 3107 passed `resolved` (redirect target) as
   `originalUrl` and `previous.uri` (original URL) as `redirectUri`,
   swapping the parameters. This caused the _isSubdomain check to verify
   the wrong direction, leaking Authorization/Cookie headers to parent
   domains on redirect and stripping them when redirecting to subdomains.

   Fix: Swap parameters to match the function signature.
@copybara-service
Copy link
Copy Markdown

Thank you for your contribution! This project uses Gerrit for code reviews. Your pull request has automatically been converted into a code review at:

https://dart-review.googlesource.com/c/sdk/+/493600

Please wait for a developer to review your code review at the above link; you can speed up the review if you sign into Gerrit and manually add a reviewer that has recently worked on the relevant code. See CONTRIBUTING.md to learn how to upload changes to Gerrit directly.

Additional commits pushed to this PR will update both the PR and the corresponding Gerrit CL. After the review is complete on the CL, your reviewer will merge the CL (automatically closing this PR).

@kevmoo
Copy link
Copy Markdown
Member

kevmoo commented Apr 8, 2026

A TEST that demonstrates the problem would be amazing.

Adds two test cases to http_parser_test.dart that exercise the
CL/TE request smuggling fix:

1. POST with `Content-Length: 10` and `Transfer-Encoding: gzip, chunked`.
   Before the fix the parser did an exact match on the header value,
   so "gzip, chunked" was not recognized as chunked encoding and the
   request was read using Content-Length instead.

2. POST with `Transfer-Encoding: identity, chunked` to cover the more
   general comma-separated list case.

Both tests assert chunked=true and that the chunked body bytes are
correctly received, which fails on the unpatched parser and passes
after the fix.

Addresses review feedback on PR dart-lang#63133.
@mohammadmseet-hue
Copy link
Copy Markdown
Author

@kevmoo good call — I added two regression tests in tests/standalone/io/http_parser_test.dart exercising the fix:

  1. POST with Content-Length: 10 and Transfer-Encoding: gzip, chunked (the CL/TE desync case).
  2. POST with Transfer-Encoding: identity, chunked (general comma-separated list case).

Both assert chunked=true and that the chunked body bytes are received correctly. On the unpatched parser they fail at Expect.equals(-1, incoming.transferLength) because the parser falls back to Content-Length mode; with the fix they pass.

@copybara-service
Copy link
Copy Markdown

https://dart-review.googlesource.com/c/sdk/+/493600 has been updated with the latest commits from this pull request.

1 similar comment
@copybara-service
Copy link
Copy Markdown

https://dart-review.googlesource.com/c/sdk/+/493600 has been updated with the latest commits from this pull request.

@brianquinlan
Copy link
Copy Markdown
Contributor

Hi @mohammadmseet-hue thanks very much for the PR! I'm just waiting for the tests to finish running and then I'll merge it.

@copybara-service
Copy link
Copy Markdown

Gerrit CL has been approved, please wait for a reviewer to merge it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants