OpenResty/lua-nginx-module HTTP Request Smuggling in HEAD requests - CVE-2024-33452

Discovery & Motivation

I first came across this HTTP Request Smuggling vulnerability while conducting an internal pentest for a client at work. Realizing its potential impact, I decided to dig deeper into its root cause and discovered that it stemmed from the lua-nginx-module implementation in OpenResty, which ignores the body of a HEAD request.

In this blog post, I’ll break down the technical details of the vulnerability, its impact, and the attack scenarios it enables. Also, stay tuned for the second part of this research, where we will show case exploitation in real-world applications for cool findings and big bounties at FrogSec Research.

Lastly, I’d like to thank Mr. James Kettle for his invaluable insights, both through his incredible resources on HTTP Request Smuggling and by answering my questions, which greatly helped me in understanding this vulnerability.

Description

I found a HTTP Request Smuggling vulnerability in OpenResty/lua-nginx-module <= v0.10.26 which allows attackers to smuggle requests.

When processing HTTP/1.1 requests, lua-nginx-module incorrectly parses HEAD requests with a body and treats the body as the new separate request.

Normally for other proxies, the following request is treated as a single request because the GET /smuggle request is inside of the HEAD request’s body.

1
2
3
4
5
6
7
HEAD / HTTP/1.1
Host: localhost
Content-Length: 52

GET /smuggle HTTP/1.1
Host: localhost

But when parsed by lua-nginx-module this request is treated as 2 separate requests. This leads to discrepancies between proxies if chained together.

Steps to Reproduce:

  1. Setup OpenResty/lua-nginx-module like the following video
    https://www.youtube.com/watch?v=eSfYLvVQMxw

  2. Send this request in Burp Suite.

    1
    2
    3
    4
    5
    6
    7
    HEAD / HTTP/1.1
    Host: 192.168.17.130:8000
    Content-Length: 52

    GET /smuggle HTTP/1.1
    Host: 192.168.17.130:8000

    Sending the smuggle request
    Log results

    As you can see in the response, and the access log. There is a smuggled GET request even though we have only sent the HEAD request.

Root-cause analysis:

The vulnerability lives in src/ngx_http_lua_util.c file.

Vulnerable Code

  • ngx_http_lua_send_chain_link is called when processing pipeline requests.
  • ngx_http_discard_request_body at line 614 instructs nginx to discard (skip through it) the request body
    alt text
  • ngx_http_discard_request_body calls ngx_http_discard_request_body_filter, which moves the request pointer forward by the Content-Length value to skip the body and correctly position the server for processing the next request.
  • However, at line 599, if the current method is HEAD, ngx_http_lua_send_chain_link will returns before reaching ngx_http_discard_request_body and the current request body will be treated as a new request. Causing the HTTP Request Smuggling issue!

Attack scenerios:

I’ve observed that proxies which are implemented on top of lua-nginx-module also suffer from this issue (Kong Gateway, Apache APISIX, …). Here are some attack scenarios to signify the impactfulness of this vulnerability:

I will be using Kong Gateway as an example for my POCs.

This is not a problem if Kong stands on its own at the front since it is just a normal HTTP pipelining behaviour. But, if Kong is chained with a front-end proxy (Nginx, Cloudflare, …) it would allow attackers to serve malicious responses and smuggle requests through the front-end proxies, which have persistent connection (keep-alive) to Kong. Let me explained how:

As I have observed in many proxies, a HEAD request with a body will be treated as a single request, however when this request is passed to Kong gateway it would be treated as 2 separated pipelined requests (a HEAD request and a malicious request in the body) due to lua-nginx-module behaviour. This would leave the malicious request dangling in the response queue, and when another user sends a request that malicious response will be sent back to them.

Attack scenario 1: Serving XSS Responses to all victims

I have set up another POC (in kong-xss folder) where the latest Nginx will be the front-end proxy, Kong as the API gateway, and a default Apache page with a single configuration to redirect /assets to /assets/ path.

An attacker will be able to achieve XSS with this attack, even with a default Apache web page.

XSS Smuggle Payload:

1
2
3
4
5
6
7
8
9
10
HEAD / HTTP/1.1
Host: localhost
Content-Length: 122

HEAD /app HTTP/1.1
Host: localhost
Connection: keep-alive

GET /app/assets?<script>alert(origin)</script> HTTP/1.1
X:

You can read more detail the HEAD technique here here.

Attack scenario 2: Bypassing Front-end proxies protection (E.g: Cloudflare)

I have created an attack scenario where CloudFlare will be blocking requests to /admin. However, an attacker can smuggle a GET /admin request in a HEAD request’s body to bypass CloudFlare.

Bypassing Cloudflare

Attack Example 3: Stealing other user’s responses

This attack allows the attacker to desync the response queue and capture other user’s responses. Here is a visualization video on how this attack works:

POC

The proxy setup can be found in my GitHub repository:

https://github.com/Benasin/OpenResty_HEAD_HRS/tree/main

Impact:

The attacker can use this attack to bypass any frontend proxies protection, serve malicious responses to all the users in the same connection pool and capture responses of other users.

Discovery/Disclosure Timeline