openssl发布了一个安全级别为”严重”的UAF漏洞,该漏洞利用简单,只需要发一个tcp包就能触发漏洞,但后果严重,可能导致TLS相关的应用拒绝服务,甚至任意代码执行等后果。唯一的限制是该漏洞影响范围较小,仅影响1.1.0a版本的openssl,而该版本的openssl发布时间比较晚,实际使用的并不多。笔者对此次漏洞进行了一次详细分析,同时通过漏洞分析分享笔者关于安全的一些思考。
漏洞重现
此次漏洞仅影响版本为1.1.0.a的openssl,下面让我们一起来一步步重现此次漏洞。漏洞测试的系统为Ubuntu。如果不熟悉linux的朋友建议安装一个虚拟机进行测试。
第一步首先我们从github上下载源码并编译:
wget "https://github.com/openssl/openssl/archive/OpenSSL_1_1_0a.tar.gz"
tar -xf OpenSSL_1_1_0a.tar.gz
cd openssl-OpenSSL_1_1_0a
./config --debug
make -j4
如果编译成功,可以在apps目录下看到openssl执行程序。
在这里我们为了不影响系统原有的openssl,不执行 sudo make install命令,因此需要把生成的动态库文件libssl.so和libcrypto.so放到系统库目录下。
sudo cp ./libssl.so.1.1 /usr/local/lib
sudo cp ./libcrypto.so.1.1 /usr/local/lib
生成一张测试证书
./openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 –nodes
运行该命令后openssl会询问一些关于证书的相关信息,无视掉,直接一路enter就好了。
使用openssl的s_server子命令搭建SSL服务器,监听20443端口
./openssl s_server -key key.pem -cert cert.pem -accept 20443 –www
使用nc向openssl的本地20443端口发送异常的ssl握手包
nc localhost 20443
openssl收到这一畸形的握手包后bang的一声down掉了!怎么样,利用是不是很简单呢?
send_content地址:
https://pan.baidu.com/s/1eRXpmgU
基础知识
接下来我们要对漏洞产生的原因和如何构造一个漏洞测试数据包进行学习,但是让我们首先来学习一些关于SSL的基础知识。
在漏洞重现中我们搭建了一个ssl服务器,下面我们打开wireshark进行抓包,捕获此次的SSL通信过程。在wireshark中设置过滤条件:tcp.port=20443,避免显示太多无用的通信包。
使用firefox与openssl的ssl服务器进行通信。在firefox的地址栏输入:
https://localhost:20443
此时firefox会提示你该网址不安全,不用理会这一提示,这是因为证书是我们为了测试生成的,不在firefox的可信根证书列表中。依次点击Advanced->Add Exception->Confirm Security Exception,确认安全例外网址https://localhost:20443。
这时回到wireshark的界面,可以看到wireshark已经抓到了本次ssl通信的数据包。
SSL通信的过程是这样的,首先客户端和服务器端经过三次握手建立TCP连接,然后客户端发送的第一个数据包通常被称为“client hello“,意思就是说client想要和server进行通信,首先要向服务器端say一下hello,这个hello包中包括了客户端需要交换的随机数,支持的加密算法等内容,但和本次漏洞相关的是SSL包的长度,就是图中标红的两个length,512和508,标明了SSL数据段的长度,正是因为openssl对长度的处理不当导致了此次漏洞。
漏洞分析
目前,openssl已经发布了漏洞补丁,我们先来看看补丁(这里):
+static int grow_init_buf(SSL *s, size_t size) {
+
+ size_t msg_offset = (char *)s->init_msg - s->init_buf->data;
+
+ if (!BUF_MEM_grow_clean(s->init_buf, (int)size))
+ return 0;
+
+ if (size
+ return 0;
+
+ s->init_msg = s->init_buf->data + msg_offset;
+
+ return 1;
+}
+
/*
* This function implements the sub-state machine when the message flow is in
* MSG_FLOW_READING. The valid sub-states and transitions are:
@@ -545,9 +560,8 @@ static SUB_STATE_RETURN read_state_machine(SSL *s)
/* dtls_get_message already did this */
if (!SSL_IS_DTLS(s)
&& s->s3->tmp.message_size > 0
- && !BUF_MEM_grow_clean(s->init_buf,
- (int)s->s3->tmp.message_size
- + SSL3_HM_HEADER_LENGTH)) {
size_t BUF_MEM_grow_clean(BUF_MEM *str, size_t len) { char *ret; size_t n; if (str->length >= len) { if (str->data != NULL) memset(&str->data[len], 0, str->length - len); str->length = len; return (len); } if (str->max >= len) { memset(&str->data[str->length], 0, len - str->length); str->length = len; return (len); } /* This limit is sufficient to ensure (len+3)/3*4 if (len > LIMIT_BEFORE_EXPANSION) { BUFerr(BUF_F_BUF_MEM_GROW_CLEAN, ERR_R_MALLOC_FAILURE); return 0; } n = (len + 3) / 3 * 4; if ((str->flags & BUF_MEM_FLAG_SECURE)) ret = sec_alloc_realloc(str, n); else ret = OPENSSL_clear_realloc(str->data, str->max, n); if (ret == NULL) { BUFerr(BUF_F_BUF_MEM_GROW_CLEAN, ERR_R_MALLOC_FAILURE); len = 0; } else { str->data = ret; str->max = n; memset(&str->data[str->length], 0, len - str->length); str->length = len; } return (len); }