freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

CVE-2017-7529复现
p4y1oad 2022-06-20 19:45:30 224006
所属地 重庆

Nginx安全性分析

影响版本:0.5.6-1.13.2

漏洞危害:敏感信息泄露

HTTP-Range

HTTP的Range 允许客户端分批次请求资源的一部分,如果服务端资源较大,可以通过Range来并发下载;如果访问资源时网络中断,可以断点续传。

Range 设置在HTTP请求头中,它是多个byte-range-spec(或suffix-byte-range-spec)的集合。

byte-range-set = ( byte-range-spec | suffix-byte-range-spec)*N
byte-range-spec = first-byte-pos "-" [last-byte-pos]
suffix-byte-range-spec = "-" suffix-length

其中∶

  • first-bytes-pos指定了访问的第一个字节,

  • last-byte-pos指定了最后一个字节,

  • suffix-length则表示要访问资源的最后suffix-length个字节的内容,

  • Range:bytes=O-1024表示访问第0到第1024字节,

  • Range:bytes=500-600601-999-300表示分三块访问,分别是500到600字节,601到600字节,以及最后的300字节。

如果一次请求有多次range,需要multipart来组织

HTTP/1.1 206 Partial Content
Date: Wed, 15 Nov 1995 06:25:24 GMT
Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT
Content-type: multipart/byteranges;boundary=THIS_STRING_SEPARATES
......
Content-type: application/pdf
Content-range: bytes 500-999/8000
......
Content-type: application/pdf
Content-range: bytes 7000-7999/8000

利用multipart进行分片

对于普通文件来说,Range的开始和结束并不会有什么影响,因为服务器返回的就是完整文件的一部分,但是缓存文件不同,它和普通的文件相比额外拥有一个文件头,里面保存了一些服务器的配置信息(正常情况下服务器是不会返回缓存文件头部的)。所以,当我们针对一个缓存文件进行请求时,如果可以绕过服务器限制,使缓存文件被完整的返回,这时只要控制Range的起始字节为一个合理的负值,就可以读到缓存文件头部。

HTTP-Cache

Nginx可以作为缓存服务器,将Web应用服务器返回的内容缓存起来。如果客户端请求的内容已经被缓存,那么就可以直接将缓存内容返回,而无需再次请求应用服务器。由此,可降低应用服务器的负载,并提高服务的响应性能。

NGINX 中的缓存策略

Nginx对Range 的支持包括header处理和body处理,分别用来解析客户端发送过来的Range header和裁剪返回给客户端的请求数据Body。ngx_http_range_header_filter_module——负责对header数据的处理ngx_http_range_body_filter_module——负责对body数据的处理

漏洞原理分析

漏洞文件 ngx_http_range_filter_module.c

在header中range的解析过程

image-20220615220120448

ngx_http_range_parse函数中有这样一个循环, 这段代码是要把“-”两边的数字取出分别赋值给startend变量,字符串指针p中即为bytes=后面的内容

//部分源码如下
cutoff = NGX_MAX_OFF_T_VALUE / 10;
cutlim = NGX_MAX_OFF_T_VALUE % 10;

for(;;)
{
start=0;
end=0;
suffix=0;
//...
while(*p == ' ') { p++; }

if (*p != '-')
{
if (*p < '0' || *p > '9')
{
return NGX_RANGENOT_SATISFIABLE;
}

while (*p >= '0' && *p <= '9')
{
if (start >= cutoff && (start > cutoff || *p - '0' > cutlim))
{
return NGX_RANGENOT_SATISFIABLE;
}

start = start * 10 + *p++ - '0';  // 更新start
}

while (*p == ' ') { p++; }

if (*p++ != '-')
{
return NGX_RANGENOT_SATISFIABLE;
}

while (*p == ' ') { p++; }

if (*p == ','  || *p == '\0')
{
end = content_length;  // 对end做更新
goto found;
}

}else{
suffix = 1;
p++;
}
//...
if (suffix)
{
start = content_length - end; // 第一次byte以“-end”格式传入时,end=0,start = content_length
end = content_length - 1; // start > end 不会进入found
}
//...
found:

if (start < end)
{
range = ngx_array_push(&ctx->ranges);
if (range == NULL)
{
return NGX_ERROR;
}

range->start = start;
range->end = end;

size += end - start;

if (ranges-- == 0)
{
return NGX_DECLINED;
}
}

if (*p++ != ',')
{
break;
}
}
//...
if (size > content_length)
{
return NGX_DELINED;
}
//...

在该段代码中存在cutoffcutlim阈值限定了从字符串中读取时不会让startend为负值, 所以这里需要进入suffix = 1的分支,因此使用Range:bytes=-xxx,(-end的格式)即省略初始start值的形式,由此可以绕过*p != '-'的限制,进入suffix=1的分支。

if (suffix)
{
start = content_length - end;
end = content_length - 1;
}

start等于content_length减去end值,所以如果传入的end比实际长度还要长,就可以使start变为负数。其中content_length为不包含文件头的文件长度。最终end的值会被设定为content_length - 1(因此我们需要构造一个小包)

if (start < end) 
{
range = ngx_array_push(&ctx->ranges);
if (range == NULL)
{
return NGX_ERROR;
}

range->start = start;
range->end = end;

size += end - start;
if (ranges-- == 0)
{
return NGX_DECLINED;
}
}

start相当于分片区间的头指针,end相当于分片区间的尾指针。如果此时end值要比文件长度(content_length)数值大的话,就可以将start解析为负值。与Range相关的还有一个size值,它是每段Range相加后的总长度

if (size > content_length)
{
return NGX_DELINED;
}

size(即所有range相加的总长度)超过文件长度content_length时,会返回默认的NGX_DELINED

注意到此处有一个退出条件:

if (*p++ != ',')
{
break;
}

支持支持range的值为start1-end1,start2-end2……的形式。

因此,可以构造range:bytes=-x,-y。一大一小两个end值,只需要 控制前面一个end值小而后一个end值大,从而实现start值和size值皆为负数,控制start值负到一个合适的位置,那么就能成功读到缓存文件头部了。

以下验证和POC来源于:

http://www.hacksee.com/blog/nginx-int-overflow.html

CentOS搭建Nginx服务

安装依赖库

yum install gcc-c++ wget
yum install pcre pcre-devel
yum install zlib zlib-devel
yum install openssl openssl-devel

下载指定版本的Nginx包

wget http://nginx.org/download/nginx-1.12.0.tar.gz

解压

tar -zxvf nginx-1.12.0.tar.gz

安装Nginx

cd nginx-1.12.0
./configure --prefix=/usr/local/nginx
make && make install
ln -s /usr/local/nginx/sbin/nginx /usr/bin
systemctl stop firewalld
nginx

修改Nginx配置文件

vi /usr/local/nginx/conf/nginx.conf

设置 Nginx 服务器反向代理百度,开启缓存功能,具体配置如下:

#user  nobody;
worker_processes 1;

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;


events {
worker_connections 1024;
}


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"';


proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_path /tmp/nginx levels=1:2 keys_zone=zone:10m;
proxy_cache_valid 200 10m;


#access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

#gzip on;

server {
listen 80;
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

location / {
#root html;
#index index.html index.htm;
proxy_pass http://www.baidu.com;
proxy_set_header Host www.baidu.com;

proxy_cache zone;
add_header X-Proxy-Cache $upstream_cache_status;
proxy_ignore_headers Set-Cookie;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}


# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;

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


# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;

# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;

# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;

# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;

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

}

proxy_cache_key用来指定生成的key的字段内容,用以区分缓存文件,这部分内容会在之后我们利用漏洞时被泄露。proxy_cache_path设置了缓存文件的路径和参数。proxy_cache_valid用来指定不同状态码下的缓存时间。server代码块设置了代理的内容,并对响应头进行了一些设置。add_header X-Proxy-Cache表示在响应头里添加一条X-Proxy-Cache,用以区分是否命中缓存,它一共有 5 种状态,MISS表示未命中,请求被传送到后端;HIT表示缓存命中;EXPIRED表示缓存已经过期请求被传送到后端;UPDATING表示正在更新缓存,将使用旧的应答;STALE表示后端将得到过期的应答。

重新加载配置

nginx -s reload

使用另一台服务器进行访问

image-20220616211003939


print check(target)
except Exception,e:
print '[-]Error: ' + str(e)
exit(0)

if __name__=='__main__'::
main()

测试

python2 nginx_poc.py [your ip]

image-20220616215100390

成功爆keyKEY: httpGETx.x.x.x/favicon.ico

漏洞利用成功!

参考文章:

http://www.hacksee.com/blog/nginx-int-overflow.html

https://paper.seebug.org/353/#5

https://gitee.com/geektime-geekbang/WebSecurity/raw/master/PDF/%E7%AC%AC%E5%9B%9B%E7%AB%A0%EF%BC%882%EF%BC%89%EF%BC%9ANginx%E5%AE%89%E5%85%A8%E4%B8%93%E9%A2%98.pdf

# 渗透测试 # web安全 # 漏洞分析
本文为 p4y1oad 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
p4y1oad LV.2
这家伙太懒了,还未填写个人描述!
  • 4 文章数
  • 0 关注者
【漏洞复现】Typora远程代码执行漏洞CVE-2023-2317
2023-09-05
从0到1学习OGNL沙箱绕过思路
2022-05-04
文件包含LFI演化史
2022-04-14
文章目录