Nginx-HTTP-Parse 文件解析漏洞
补丁分析
CVE-2013-4547是Nginx出现过的一个解析漏洞,
官方的补丁打在了_ngx_httpparse.c上
|
|
断点调试
使用gdb调试nginx,将断点下在/nginx-1.5.6/src/http/ngx_http_parse.c的ngx_http_parse_request_line上,
发送了测试payload
|
|
/a.jpg /0.php中的空格是hex 00,非真正的空格
传入的http请求只接收到了/a.jpg
ngx_http_parse_request_line传入了两个ngx_http_request_t类和ngx_buf_t类的指针,ngx_http_request_t在以下文件中定义了一个别名
文件: _/src/http/ngx_httprequest.h
|
|
ngx_http_request_s的结构成员
|
|
我们接下来再看一下ngx_buf_t类的结构成员
|
|
结构成员名字的含义:
| pos: | 当buf所指向的数据在内存里的时候,pos指向的是这段数据开始的位置。 |
|---|---|
| last: | 当buf所指向的数据在内存里的时候,last指向的是这段数据结束的位置。 |
| file_pos: | 当buf所指向的数据是在文件里的时候,file_pos指向的是这段数据的开始位置在文件中的偏移量。 |
| file_last: | 当buf所指向的数据是在文件里的时候,file_last指向的是这段数据的结束位置在文件中的偏移量。 |
| start: | 当buf所指向的数据在内存里的时候,这一整块内存包含的内容可能被包含在多个buf中(比如在某段数据中间插入了其他的数据,这一块数据就需要被拆分开)。那么这些buf中的start和end都指向这一块内存的开始地址和结束地址。而pos和last指向本buf所实际包含的数据的开始和结尾。 |
| end: | 解释参见start。 |
| tag: | 实际上是一个void*类型的指针,使用者可以关联任意的对象上去,只要对使用者有意义。 |
| file: | 当buf所包含的内容在文件中时,file字段指向对应的文件对象。 |
| shadow: | 当这个buf完整copy了另外一个buf的所有字段的时候,那么这两个buf指向的实际上是同一块内存,或者是同一个文件的同一部分,此时这两个buf的shadow字段都是指向对方的。那么对于这样的两个buf,在释放的时候,就需要使用者特别小心,具体是由哪里释放,要提前考虑好,如果造成资源的多次释放,可能会造成程序崩溃! |
| temporary: | 为1时表示该buf所包含的内容是在一个用户创建的内存块中,并且可以被在filter处理的过程中进行变更,而不会造成问题。 |
| memory: | 为1时表示该buf所包含的内容是在内存中,但是这些内容却不能被进行处理的filter进行变更。 |
| mmap: | 为1时表示该buf所包含的内容是在内存中, 是通过mmap使用内存映射从文件中映射到内存中的,这些内容却不能被进行处理的filter进行变更。 |
| recycled: | 可以回收的。也就是这个buf是可以被释放的。这个字段通常是配合shadow字段一起使用的,对于使用ngx_create_temp_buf 函数创建的buf,并且是另外一个buf的shadow,那么可以使用这个字段来标示这个buf是可以被释放的。 |
| in_file: | 为1时表示该buf所包含的内容是在文件中。 |
| flush: | 遇到有flush字段被设置为1的的buf的chain,则该chain的数据即便不是最后结束的数据(last_buf被设置,标志所有要输出的内容都完了),也会进行输出,不会受postpone_output配置的限制,但是会受到发送速率等其他条件的限制。 |
| sync: | |
| last_buf: | 数据被以多个chain传递给了过滤器,此字段为1表明这是最后一个buf。 |
| last_in_chain: | 在当前的chain里面,此buf是最后一个。特别要注意的是last_in_chain的buf不一定是last_buf,但是last_buf的buf一定是last_in_chain的。这是因为数据会被以多个chain传递给某个filter模块。 |
| last_shadow: | 在创建一个buf的shadow的时候,通常将新创建的一个buf的last_shadow置为1。 |
| temp_file: | 由于受到内存使用的限制,有时候一些buf的内容需要被写到磁盘上的临时文件中去,那么这时,就设置此标志 。 |
|
|
定义了一个状态机,通过state的值来确定处理步骤。
|
|
state被赋值为r->state,r->state的类型为ngx_uint_t,ngx_uint_t类型是在以下文件中声明的
文件:_src/core/ngxconfig.h
|
|
而uintptr_t类型的话,在Mac OS X中是在
_/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sys/_types/_uintptr_t.h_
中被声明
|
|
可以看出uintptr_t类型实际上就是一个unsigned long类型,在《深入分析Linux内核源码》中的原因描述是这样的,
|
|
进行验证:
|
|
输出结果:
|
|
接下来进入了for循环里面,for循环里面的p的开始和结束分别为buff在内存中的开始(pos)和结束(last)。b->pos和b->last的类型是u_char,uchar是在MacOSX在/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sys/types.h_中定义的。
所以pos和last分别储存了buff开始和结束的unsigned类型的两个字符。
接着使用了一个for循环来控制指针的移动,ch为当前指针指向的字符。r->state的值为0
,所以第一次进入状态机进入了sw_start,如果第一个字符是CR(回车)或者LF(换行)就break,接下去再把指针p向后移动直到碰到非LF和CR,再进入下一个判断语句
遇到A-Z和_之外的字符都返回一个解析错误。通过了这两项检查,会向后移动直到遇到一个空格,会进入method的判断,开发人员先判断了遇到的第一个空格之前字符的数量,根据数量再进入相应的case中去判断是什么method然后再将r->method设置为相应的method。
完成了以上对HTTP method的判断之后,进入了第二个判断sw_spaces_before_uri,
这一段我们可以看出,只要遇到空格(space)指针就会就会往后移动,直到遇到’/‘或者遇到字符A-Z。
代码就是判断是否是sw_host_start,不是sw_host_start就判别为uri的开始,如果在:后碰到两个/并且如果[就进行ipv6的判断。
文字看起来没有图像直观,以下是解析的流程图
在sw_uri之后放置空格,case会进入sw_check_uri_http_09,这时候uri_ext和uri_end之间的值为Nginx判断的后缀,即如果让nginx解析的是以下的连接
http://www.Johnis.online/1.jpg[空格][零零]1.php
那么nginx会判定为jpg,到了case sw_check_uri_http_09中,遇到/00并不会进行处理,会使用default条件


成功的没有让nginx对/00进行处理。
而后面.php成功的将uri_ext覆盖为了php,之后 nginx 就会将请求发送给 fastcgi 去解析,fastcgi查找文件会被00阻断[这里代码找不到,留个坑],于是漏洞就形成了。

注意点就是jpg文件上传的时候必须带一个空格,触发的时候空格后面加一个00跟上.php就能触发了[security.limit_extensions没有限制的情况下]
Reference:
taobao,(2013)._nginx开发从入门到精通
囧囧有神,(2015)._nginx源码分析之http解码实现