This is how HTTP/2 Request Smuggling works
HTTP/2 is a binary protocol. HTTP/2 messages are sent as one or more frames and each frame has an explicit length that tells the server how many bytes to read. The length of an HTTP/2 message is calculated accordingly by adding up the length of all frames. This length can not be manipulated. Now the question arises how request smuggling can still occur in HTTP/2 if the length of the frames cannot be manipulated, since this manipulation of the length was the prerequisite for request smuggling in HTTP/1.1 requests. Basically, HTTP/2 request smuggling only occurs when the front-end supports HTTP/2 but the back-end does not. Then the front-end must convert the HTTP/2 request for the back-end into an HTTP/1.1 request. This creates new possibilities for manipulation.
Since HTTP/2 is a binary protocol, abstracted and simplified representations are used here to show the concepts. The individual frames are not shown. In HTTP/2 there are so-called pseudo headers. These are defined for the rest of the article as follows and marked with a colon in front of the name
| ||Corresponds to the request method.|
| ||Corresponds to the request path including possible parameters.|
| ||Corresponds approximately to the host header.|
| ||Corresponds to the request scheme and is normally http or https.|
| ||Corresponds to the response status code and is not used in requests.|
It is easiest to see these headers in use compared to the same request in HTTP/1.1 and HTTP/2.
POST /test HTTP/1.1\r\n Host: example.com\r\n User-Agent: test\r\n Content-Length: 3\r\n \r\n x=1
:method POST :path /test :authority example.com :scheme https user-agent test x=1
With the basics covered, HTTP/2 request smuggling vulnerabilities can now be addressed.
This vulnerability works in principle similar to the TE.CL vulnerability from the last article, but the front-end uses HTTP/2, ignores the Content-Length Header and passes it unchanged to the back-end. The back-end uses HTTP/1.1 and therefore interprets the Content-Length Header. In an example, the attack looks like this.
:method POST :path /test :authority example.com content-type application/x-www-form-urlencoded content-length 0 Injected
Since the back-end finishes processing the request earlier than what the front-end sent due to the content length of 0, the rest of the request remains in the pipeline between the front-end and the back-end. As soon as a next request arrives, the remainder of the previous request that is still in the pipeline is added before this new request. So a new request would look like this in the back-end.
InjectedGET /user-requested HTTP/1.1 Host: example.com
Similar to the vulnerability described above, the front-end uses HTTP/2, ignores the Transfer-Encoding Header and passes it unchanged to the back-end. The back-end uses HTTP/1.1 and therefore interprets the Transfer-Encoding Header. In an example, the attack looks like this.
:method POST :path /test :authority example.com content-type application/x-www-form-urlencoded transfer-encoding chunked 0 GET /admin HTTP/1.1 Host: example.com Foo: Injected
Since the back-end finishes processing the request earlier than what the front-end sent due to the transfer encoding of 0, the rest of the request remains in the pipeline between the front-end and the back-end. As soon as a next request arrives, the remainder of the previous request still in the pipeline is added before this new request. So a new request would look like this in the back-end.
POST /test HTTP/1.1 Host: example.com Content-Type: application/x-www-form-urlencoded Transfer-Encoding: chunked 0 --> Request one ends here GET /admin HTTP/1.1 Host: example.com Foo: InjectedGET /user-requested HTTP/1.1
Since HTTP/2 and HTTP/1.1 are not interpreted on the same basis, as HTTP/2 is a binary protocol and HTTP/1.1 is string based, these differences can be exploited. In an HTTP/2 header, for example, line breaks can be used without any problems, as they are not interpreted in a special way. This means that a header interpreted as one header in the front-end can become several headers in the back-end. For example
one header in the front-end, would look like this in the back-end
Testheader: Test Injected
The easiest way to prevent this vulnerability, as already indicated at the very beginning, is to use HTTP/2 throughout. If this is not possible, care must be taken that no headers such as content length or transfer encoding and no special characters such as \r\n and : are included in the conversion to an HTTP/1.1 request.
Our experts will get in contact with you!
Our experts will get in contact with you!
Further articles available here