Apache HTTP Server mod_lua模块缓冲区溢出漏洞分析(CVE-2021-44790)

发布时间

漏洞概述


2021年12月20日、该漏洞存在于mod_lua解析器中,修复了Apache HTTP Server中的一个缓冲区溢出漏洞(CVE-2021-44790),Apache 团队发布了Apache HTTP Server 2.4.52版本,当服务器解析恶意请求时触发缓冲区溢出,可导致拒绝服务或执行任意代码。


影响范围


影响版本:Apache HTTP Server <= 2.4.51


相关介绍


Mod_lua模块

Mod_lua模块是Apache上的一个扩展模块。适用于2.3以上版本。该模块允许使用lua脚本扩展服务器。还包括许多其他模块可用的钩子函数。例如将请求 Map 到文件。生成动态响应,身份验证和授权等,访问控制。如果开启该模块,可能会造成一些安全隐患。

在/etc/httpd/httpd.cnf配置文件中取消下面这行注释,即可开启该模块的功能。


代码文件.png

当收到.lua文件请求时,mod_lua模块调用lua-script的handle函数进行处理。下图为handle函数实例。


代码文件.png

apr内存池


为了减少系统内存分配的时间。提高程序运行效率,Apache的开发者创建了一套基于池概念的内存管理方案。这套方法移到apr中成为通用的内存管理方案,也就是apr内存池。

apr的内存池结构其实是一种树状的层次结构,child指向当前内存池的子内存池,parent指向当前内存池的父内存池,sibling则指向当前内存池的兄弟内存池。用户使用的内存空间,则是active管理的一个节点链表。用户要申请内存空间的时候就会在active管理的内存节点中寻找。


结构体如下所示:


代码文件.png


用户申请内存过程:


(1)首先取最接近不小于8字节倍数大小的空间(8字节对齐),然后根据申请大小判断active节点可用空间是否足够。若内存足够,返回其地址;若空间不足,则继续进行2之后的步骤,移动first_avail指针。


(2)判断下一个内存节点的剩余空间是否足够,若足够则使用之,并将之脱离当前链表;若不足,则通过分配子分配新的内存节点。


(3)将第2步中得到的节点插入active节点之前,并成为新的active节点。


(4)计算旧的active节点的剩余空间大小、并且与其链表后的所有节点的剩余空间大小比较,并插入链表中正确的位置。


代码文件.png

补丁分析


该漏洞在Apache HTTP Server 2.4.52中进行了修复,在内存申请之前,增加了对长度的合法性校验。当end-crlf小于等于8。避免整数溢出,程序会直接退出。


代码文件.png


漏洞分析


根据漏洞公告,可知漏洞存在于mod_lua模块中,lua脚本调用了r:parsebody()函数发生了缓冲区溢出。结合patch信息,直接定位到req_parsebody函数。

本文使用Apache HTTP Server 2.4.49版本进行分析,图片中对关键部分进行了相应的注释,代码中红色方框标识出来的部分即漏洞代码位置。


代码文件.png


下面结合post数据包来分析程序处理逻辑。构造如下post数据包:


代码文件.png


首先、start变量指向post数据包开始的位置,crlf指向两个空行(\r\n\r\n)开始的位置,那么在crlf和end之间的数据就有下面这些内容,end指向下一个标识符VILC2R2IHFHLZZ开始的位置,也就是对应上面第一个标识符--VILC2R2IHFHLZZ的位置,总长度为8(特殊字符长度)+len(数据参数长度)个字节。


‘\r\n\r\ntest\r\n--’

根据上面参数内容,我们就可以理解下面这行代码的意义了。vlen等于总长度减去多余的8个特殊字符,就可以计算出参数的长度。


vlen=end-crlf-8;


然后,程序调用apr_pcalloc分配内存。


代码文件.png


程序没有对vlen值的合法性进行检查,如果上面参数中的特殊字符缺失,造成整数溢出,计算的vlen值就可能变为负数。当申请空间的时候,会出现安全问题。



动态调试


根据不同畸形包的构造、考虑以下两种情况,结合动态调试进行分析。

申请超大的空间

假设缺失'/r/n--'这4个特殊字符。且数据部分为2字节,vlen=(2+4-8)=-2。调用apr_pcalloc(r->pool, vlen+1)申请内存时,vlen+1=0xffffffffffffffff。

使用gdb附加进程,进行动态调试。在漏洞函数处设置断点,然后发送特殊的post请求。


代码文件.png


apr内存池无法提供这么大的内存。造成拒绝服务,但是申请的巨大内存空间是系统无法提供的,这时apr的分配子就会向系统申请内存空间,所以系统会直接将进程kill掉(0x75是进程号)。

代码文件.png


溢出超长的字节

假设缺失'/r/n--'这4个特殊字符。且数据部分为3字节,vlen=(3+4-8)=-1,调用apr_pcalloc(r->pool, vlen+1)申请内存时,长度vlen+1=0,根据apr内存池内存分配机制,apr内存池会分配最小的内存块8字节,最后使用函数memcpy的时候:


memcpy(buffer, crlf + 4, vlen)

vlen又为FFFFFFFF.......(-1),就会发生缓冲区溢出。

动态调试时可以看到调用apr_palloc时、实际上会分配8字节的空间,长度参数是0。



代码文件.png


代码文件.png


参考链接:


[1]https://mp.weixin.qq.com/s/XLzXHZYvpPIqNrDz3OHaMA


[2]https://nakedsecurity.sophos.com/2021/12/21/apaches-other-product-critical-bugs-in-httpd-web-server-patch-now/


[3]https://httpd.apache.org/security/vulnerabilities_24.html 


[4]https://ubuntu.com/security/CVE-2021-44790


[5]https://github.com/apache/httpd/commit/07b9768cef6a224d256358c404c6ed5622d8acce