TE.TE request smuggling is the variant where both servers already agree, and you break the agreement on purpose. The front end and the back end both understand Transfer-Encoding: chunked, so a clean request desyncs nothing. The trick is to obfuscate the header just enough that one server still recognizes it and the other does not, which drops that second server back to Content-Length and reopens the split. If the underlying idea of a two server desync is unfamiliar, our primer on http request smuggling explains why two readers of one stream is the whole problem.
Why TE.TE request smuggling needs obfuscation
Start with what does not work. If both servers read Transfer-Encoding: chunked the same way, they agree on where the body ends, and there is no gap to smuggle through. A plain chunked request is just a plain chunked request. The desync only appears when exactly one server stops honoring the header. The other falls back to the length rule it has left, which is Content-Length, and now you are back to a split between chunked and byte counting, the same split that drives every other variant.
So the goal of TE.TE is narrow: write the Transfer-Encoding header in a form that one parser accepts and the other rejects or fails to see. You are not inventing a new attack. You are manufacturing a CL versus TE disagreement between two servers that would otherwise parse the request identically.
Here is the shape of it, using a second header with a value one server will not understand:
POST / HTTP/1.1 Host: acme-notes.example Content-Length: 6 Transfer-Encoding: chunked Transfer-Encoding: cow 0 GET /admin HTTP/1.1 Host: acme-notes.example Foo: x
Two Transfer-Encoding headers arrive. One server combines them, decides the encoding list ends in a value it does not recognize, and declares the whole thing unusable, so it falls back to Content-Length: 6. The other server reads the first header, sees chunked, and processes the body as chunks. From that point the request behaves exactly like the classic case: one server honors chunked and one honors the byte count, so a smuggled prefix is left in the buffer for the next request.
TE.TE is not a new desync. It is a way to manufacture a split between chunked and byte counting on two servers that would otherwise read the request the same way.
The obfuscation surface, at a high level
Which server breaks, and in which direction, depends entirely on the two parsers in your path. The useful spellings all target places where HTTP parsers quietly differ on what counts as a valid Transfer-Encoding header. At a high level they fall into a few groups:
- Unusual whitespace. A space or a tab in a spot a strict parser rejects but a lenient one trims, such as before the colon or inside the value.
- A second header with a junk value. Two
Transfer-Encodinglines where the second carries something unrecognized, as in the example above. - Odd casing or trailing junk. A value like
chunkedwith extra characters stuck to it, or an unexpected mix of upper and lower case that one parser normalizes and the other does not. - Line folding and control bytes. Obsolete continuation lines or stray control characters that a tolerant parser accepts while a strict one treats the header as malformed.
Each of those is a bet that two servers in the chain draw the line in different places. The full catalogue of spellings, and the specific parser quirks each one targets, is its own post: transfer encoding obfuscation. The point to carry away here is that the obfuscation is only a means. Its single job is to make one server ignore Transfer-Encoding so the two servers end up on opposite length rules.
Once the split exists, it is CL versus TE again
After the obfuscation lands, a TE.TE attack collapses into one of the two directions you already know. If the back end is the one that fell back to Content-Length, you are running a CL.TE style smuggle, walked byte by byte in CL.TE request smuggling. If the front end fell back instead, you craft the body the way you would for TE.CL request smuggling, with the careful chunk size that case demands. The leftover prefix, the poisoned queue, and the bypass of front end controls all follow from there.
The defense is to refuse the ambiguity rather than parse around it. A front end should reject a Transfer-Encoding it will not honor, including obfuscated spellings, instead of forwarding a header it parsed loosely for the back end to interpret differently. A request with a malformed or duplicated Transfer-Encoding is a request to drop and respond to with a closed connection, not one to clean up and pass along. These framing failures live next to the input handling bugs we file under injection and input, because both come down to a parser being too forgiving with untrusted bytes.
Spotting a weird header spelling is easy. Knowing whether a given spelling actually desyncs the two servers in front of a specific application is the hard part, and it is not something you can read off a signature. It depends on how each real parser in the path treats that exact byte sequence, which is a question you answer by testing the chain and proving the boundary moves. That is what UnboundCompute is built to do: reason about whether a desync is actually reachable rather than pattern matching headers, which you can read about on our about page.
Frequently asked questions
What makes TE.TE different from the other variants?
In TE.TE both the front end and the back end support Transfer Encoding chunked, so a clean request does not desync them at all. The attacker has to obfuscate the Transfer Encoding header so that one server still honors it and the other stops recognizing it. That second server falls back to Content Length, and only then do the two servers disagree about where the body ends.
What does header obfuscation mean here?
It means writing the Transfer Encoding header in a form that one parser accepts and the other rejects. Common ideas include unusual whitespace around the name or value, a second Transfer Encoding header carrying a value one server does not understand, odd casing or trailing junk on the chunked value, and obsolete line folding or stray control bytes. Each spelling is a bet that two parsers in the path draw the line in different places.
Does TE.TE become one of the other variants?
Yes. Once obfuscation makes one server fall back to Content Length, the attack is a plain split between chunked and byte counting. If the back end fell back you run it like a CL.TE smuggle, and if the front end fell back you craft the body like a TE.CL smuggle with its careful chunk size. The obfuscation is only the means of creating that split.
How do you defend against TE.TE request smuggling?
Refuse the ambiguity instead of parsing around it. A front end should reject any Transfer Encoding header it will not honor, including obfuscated spellings, rather than forwarding a header it parsed loosely for the back end to read differently. Treat a malformed or duplicated Transfer Encoding header as a request to drop, respond, and close the connection, so no leftover bytes can wait for the next user.
Put an autonomous researcher on your own systems
UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.
