从补丁追溯漏洞触发路径

VSole2021-10-25 20:02:51

背景

操作系统:ubuntu 18.04 64bit
漏洞软件:nginx-1.4.0

漏洞补丁信息

从补丁可以认识一个漏洞的触发源。

查看github中的补丁信息Fixed chunk size parsing. · nginx/nginx@818807d (github.com)如下:

if (ctx->size < 0 || ctx->length < 0) {goto invalid;    }
return rc;

可以看到补丁中在/src/http/ngx_http_parse.cngx_http_parse_chunked函数返回值中增加了对变量ctx->lengthctx->size负值判断

查看ctx变量的结构体定义,

struct ngx_http_chunked_s {ngx_uint_t           state;off_t                size;off_t                length;};

可以看到sizelength的类型变量是off_t,而off_t对应了long int,是一个有符号的变量(记住这一点,很重要)。

漏洞触发路径分析

从上一步中可以得到漏洞的根源在于/src/http/ngx_http_parse.cngx_http_parse_chunked函数,与负值的变量ctx->lengthctx->size有关,现在开始追踪这两个变量的后续流向。

2.1 漏洞复现

POC信息

从互联网可以找到该漏洞的POC如下:

import socket
host = "127.0.0.1"ip='127.0.0.1'
raw = '''GET / HTTP/1.1\r\nHost: %s\r\nTransfer-Encoding: chunked\r\nConnection: Keep-Alive\r\n\r\n''' % (host)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((ip, 80))
data1 = rawdata1 += "f000000000000060" + "\r\n"
print data1s.send(data1)
s.send("B" * 6000)s.close()

这个POC会发送两次TCP请求数据,第一次是一个HTTP请求:

GET / HTTP/1.1Host: 127.0.0.1Transfer-Encoding: chunkedConnection: Keep-Alive
f000000000000060

第二次是一个超长的"B"字符串。

chunked HTTP请求

第一个HTTP请求的特殊之处在于这是一个分块传输的请求。在请求体中,在每一个分块的开头需要添加当前分块的长度,以十六进制的形式表示,后面紧跟着 '\r\n' ,之后是分块本身,后面也是'\r\n'

漏洞复现

在shell中找到nginx工作进程的pid,并使用gdb 挂载调试 ,并在patch函数下断点。

osboxes@osboxes:~$ ps aux |grep nginxroot      2081  0.0  0.0  21860  1908 ?        Ss   11:14   0:00 nginx: master process ./nginx -c conf/nginx.confnobody    7185  0.0  0.0  22256  2196 ?        S    17:32   0:00 nginx: worker processosboxes   7406  0.0  0.0  14436  1008 pts/0    S+   19:13   0:00 grep --color=auto nginxosboxes@osboxes:~$ sudo gdb -p 7185pwndbg> b ngx_http_parse_chunkedBreakpoint 1 at 0x5599fb464871: file src/http/ngx_http_parse.c, line 1974.pwndbg> cContinuing.

执行POC,并查看函数调用栈可以看到如下:

那我们就依照源码来分析漏洞的触发路径

1.ngx_http_parse_chunked函数解析HTTP中的块大小

查看ngx_http_parse_chunked函数,可以看到该函数的主要功能为解析HTTP请求体中的chunk信息。

ngx_int_tngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b,    ngx_http_chunked_t *ctx){...    state = ctx->state;...    rc = NGX_AGAIN;...
        switch (state) {                ...        case sw_chunk_size:            if (ch >= '0' && ch <= '9') {                ctx->size = ctx->size * 16 + (ch - '0');                break;            }            c = (u_char) (ch | 0x20);
            if (c >= 'a' && c <= 'f') {                ctx->size = ctx->size * 16 + (c - 'a' + 10);                break;            }                ...    }
data:    switch (state) {...    case sw_chunk_data:        ctx->length = ctx->size + 4 /* LF "0" LF LF */;        break;...    return rc;...}

当遇到HTTP请求体中的块大小,即f000000000000060时,会将字符串解析为对应的十六进制数字,并保存在ctx->size中。注意,由于是有符号的,ctx的值是为负数的。之后ctx->size的值会赋值到ctx->lenth中,也就是:

ctx->lenth= ctx->size+4          = parseLong('f000000000000060')+4          = -1152921504606846880+4          = -1152921504606846876

之后,函数返回,返回值为rc=NGX_AGIN

2.ngx_http_discard_request_body_filter将值进一步向上传递

根据返回值rc == NGX_AGAIN, 这个负值会进一步传递到r->headers_in.content_length_n 变量中,注意这也是一个off_t类型的,也就是它也是**负数。**也就是

r->headers_in.content_length_n = rb->chunked->length                               = -1152921504606846876

之后函数返回 ,返回值为NGX_OK

3.ngx_http_discard_request_body简单跳转

ngx_http_discard_request_body函数中, 控制流返回后进入到另一个子函数中。

4.ngx_http_read_discarded_request_body栈溢出

逃脱ngx_min检查

ngx_http_read_discard_request_body函数中本来是有长度范围检查ngx_min,但是正如我们前面所说的,长度为负数,所以这个检查就被绕过了

size 被赋予超大值

在函数中size_t是一个无符号的long int, 这样size就被意外的赋值为一个超大的数值。也就是

(size_t) size= r->headers_in.content_length_n             = 17293822569102704740
recv 将超长的输入写入局部变量buffer

在解析size之后,nginx 会尝试再次读取输入,

n = r->connection->recv(r->connection, buffer, size);

此时,系统会尝试size=17293822569102704740大小的输入写入到局部变量buffer中,由此造成了栈溢出

漏洞数据流

总结整理数据的流动方向如下图:

总结

这个漏洞的原因在于,带符号整数在转为无符号数时会变为极大的值,从而导致nginx从socket中读取了超长的值到局部变量中。

漏洞的触发条件为三个:

条件作用HTTP 请求头中 Transfer-Encoding: chunked确保进入ngx_http_parse_chunked函数,读取精心设置的长度块长度为负数确保逃过ngx_min的检查第二次请求超长确保数据被读入并造成栈溢出(ps: 这也是利用构造的地方) 参考:Nginx栈溢出分析 - CVE-2013-2028 - l3m0n - 博客园 (cnblogs.com)

https://www.cnblogs.com/iamstudy/articles/nginx_CVE-2013-2028_brop.html

nginxsocket
本作品采用《CC 协议》,转载必须注明作者和本文链接
从补丁可以认识一个漏洞的触发源。 查看github中的补丁信息Fixed chunk size parsing. · nginx/nginx@818807d (github.com)如下:
最近安全圈公布了一个利用 HTTP/2 快速重置机制进行 DDoS 攻击的 0day 漏洞,CVE-2023-44487,鉴于 HTTP/2 协议已经在 Internet 上广泛使用,所以该漏洞一经发布,在业界引起广泛关注。
给木马带双眼睛
2023-04-18 09:56:25
近月,内存马的技术也产生新的变化,如利用websocket进行通信,Executor内存马进行socket通信。本文介绍利用Poller内存马实现全流量监控,这样,攻击方可以实时监控经过系统的每一个请求,或者增加了钓鱼等信息利用的便利。这也就是说注入Poller的内存马一定是不能出任何bug的,一旦出了,整个服务直接崩溃。反向,Executor是一个任务类,创建后就执行一个线程任务,如果这次业务异常,最多这次的请求无法正常执行罢了。
常见边界拓扑第一种情况Inbound Stream ---> Firewall ---> Target. 这其中无论负载均衡设备转发或者防火墙的,均有可能存在带来源IP转发或者不带来源IP转发的情况,带来源IP或者端口的方案我们在下文的常见端口复用实现机制中有所介绍,该如何在此种情况下实现端口复用,
HTTP request smuggling与CTF实战利用
常见边界拓扑第一种情况Inbound Stream ---> Firewall ---> Target. 这其中无论负载均衡设备转发或者防火墙的,均有可能存在带来源IP转发或者不带来源IP转发的情况,带来源IP或者端口的方案我们在下文的常见端口复用实现机制中有所介绍,该如何在此种情况下实现端口复用,而不带来源IP或者端口的情况目前网上讨论得不多,下文也将就这类情况进行分析提出解决方案。
VSole
网络安全专家