“Optionsbleed”CVE-2017-9798漏洞详解

2017-11-03 16:42:59 启明星辰 ADLab

更多资讯和分析文章请关注启明星辰ADLab公众号及官方网站(adlab.venustech.com.cn)



背 景



国庆前,一位国外安全研究员在The Fuzzing Project网站上写了一篇blog,文章描述了他发现的一个Apache信息泄漏漏洞(CVE-2017-9798),并给该漏洞起名“Optionsbleed”,意为发生在Options请求中的出血漏洞。尽管使用了“bleed”这个名字,但是作者也在文章中坦言,该漏洞的影响确实无法与心脏出血“Heartbleed”(CVE-2014-0160)漏洞媲美。首先,这个漏洞影响的目标并不像心脏出血那么多,其次该漏洞只能泄漏很少(甚至什么都泄漏不出来)内存

漏洞作者仅仅给出了ASAN的分析截图和漏洞复现方法,并未纰漏更多细节,本文启明星辰ADLab安全研究员将对此漏洞进行详细深入地分析。



漏洞复现



根据blog的描写,想要触发漏洞,需要在服务器目录下添加一个.htaccess文件(这是一个配置文件),并且需要在该文件中添加一个没有被全局注册过的Limit指令。为此,ADLab研究员下载编译了存在漏洞的Apache版本2.4.27,修改了漏洞作者提供的poc并测试发包,在配置没有问题的服务器上结果是这样的。

接着在Apache服务器子目录下创建.htaccess文件,并在文件中写入如下内容:

<Limit abcxyz>

</Limit>

证明漏洞存在(如下图“OPTIONS”后面多出来的“,”)。


漏洞分析



事实上,通过漏洞作者提供的poc向Apache发消息后,Apache服务器一共处理了两次连接请求(之所以产生两次连接,涉及到uri重定向等一系列问题,细节和本漏洞无关所以不再赘述,感兴趣的可以参考mod_dir.c及相关文件了解详情)。每次处理连接请求时Apache都会为当前的连接分配一个request结构体,该结构体比较复杂,包括了各种连接相关的信息,这里我们只关心它的第一个变量pool,即request的内存池。

Apache服务器自行维护一套内存管理机制,这套内存管理机制最大的特点就是简单,它并不是按照大小将空闲的内存块连接到不同的链表上以期提高内存管理效率、减少内存碎片并节约内存,甚至不会频繁地释放无用内存。相反,Apache会把下一块可用内存指针指向空闲内存的起始地址,不管需要分配多大的内存,都从空闲内存的起始地址开始分配,并将这个空闲地址的指针后移。相应的,当分配的内存不再被需要的时候,也不会主动的释放这块内存方便重复利用,而是选择在网络连接的收尾阶段将整块request_rec的pool释放掉

Apache这么做可能主要基于服务器对每个网络连接处理的时间并不长,每个网络连接需要的内存池并不算太大这两个原因。所以,采用这种方式来管理内存不仅不会造成对内存的浪费反而提高了服务器的响应效率。

  • 第一次连接

当服务器处理第一次连接的时候,Apache为request分配的内存池地址为0xaa102488。

Apache根据请求的地址遍历其目录,当遍历到被请求目录时发现该目录下有.htaccess文件,就会打开该文件读取并处理文件内容,当读到<Limit abcxyz>这一行时,服务器会从request地址池里分配一块内存用于存放abcxyz字符串。

然后,流程进入ap_method_register函数。

这个函数是非常重要的函数,我们来看一下其实现:

该函数中的methods_registry是一个apr_hash_t类型的结构体指针,这是一个用于描述服务器中注册方法的结构体,用hash表的方式来保存和查找注册的方法。实现如下:

其中,array是hash表数组,数组中的每一项都指向一个由若干个hash节点串联起来的单向链表。每个节点的数据结构如下:

从代码上看,服务器首先会调用apr_hash_get来查找是否该方法(即Limit指令指定的方法)已经被注册,之前我们已经说过触发漏洞需要添加一个没有被全局注册过的Limit指令(本例中是abcxyz),所以该查找一定会失败。而register_one_method函数一定会被执行,这个函数主要是对apr_hash_set的封装,所以重点看一下apr_hash_set函数以及其子函数find_entry。

apr_hash_set函数功能是对hash表进行增删改操作,而函数的最后一个参数val就是用来确定到底要对hash表进行哪种操作的。如果val为0就对找到的hash节点进行删除操作,不为0就对hash表进行插入或者重新赋值操作,具体来说就是当这个hash节点不存在的时候就插入一个新的节点,否则对val重新赋值。find_entry的解释请参见我们在代码中添加的注释。

在hash插入之前内存如下,我们看到在array[3]中只有两个hash节点。

当节点被插入后内存如下,我们看到hash表中多了一个节点,这个节点就是我们在.htaccess中定义的Limit指令abcxyz。

当Apache将回应包通过socket发出后,request地址池将会被释放(methods_registry中abcxyz的key和val的地址也在这块内存池中)。但是,通过对图中内存的观察,我们发现methods_registry中对abcxyz的引用还在。(Apache的内存管理在释放内存之后并不会将内存中的数据覆盖,所以这时候我们在调试器里还能看到原有的数据)。

至此,第一次连接的主要流程已经走完,留下了一个对已经释放的地址有引用的hash节点。

  • 第二次连接

第二次连接首先分配内存池。

继续往下进行,当服务器对配置文件中的新配置项进行添加的时候会执行如下指令,从request内存池里分配一块内存,该内存起始地址是0xaa107718大小为152字节,并通过memcpy函数对这块内存进行内存拷贝工作,拷贝之后0xaa107758和0xaa107760(分别保存着释放前hash节点的key和val,见第一次连接中request地址池释放图片中的蓝色方框)两处的内容都被覆盖成了0。

当服务器再次进行到注册的时候,由于hash节点0x811b5d0处的key和val都已经被拷贝成了新的数据,所以注册函数认为abcxyz的节点不存在,于是重新分配了一个节点。原来的节点就成了一个指向了空字符串且val为0 的节点。

最后,当服务器开始对Options进行响应的时候,会进入一个叫做make_allow的函数,其作用就是生成Options返回的支持方法的字符串,这个函数中有一个很重要的变量mask,它是一个选择子,用来判断hash表上哪些节点是符合要求的,在我们的调试器里这个值是37。

make_allow具体实现如下,函数中的AP_METHOD_BIT是int64型的1,当mask值为37(二进制100101)时,要满足mask的筛选条件hash节点的val值需要为0、2或5。0x811b5d0这个节点(已经释放的abcxyz)本来是不符合要求的,但是由于其val已经被写成0了,所以在这里也符合了筛选条件,这就是我们之前看到allow中多了一个逗号的原因。其实真正的情况是allow里多出了一个字符串,不过这个字符串为空字符串,所以只是多了一个分割用的逗号。




补丁分析



在补丁中,我们看到服务器在注册methods_registry前进行了验证,不让.htaccess全局注册新方法,但并未真正地修补漏洞的root cause,而是通过一个判断来阻止该漏洞执行到有问题的代码流程上。



结 论



这是一个由UAF导致的信息泄漏漏洞,正如漏洞作者所说这个漏洞的影响确实远小于心脏出血,原因如下:

第一,使用.htaccess文件进行服务器配置本身并不被Apache官方推荐,因为这会极大的影响服务器的响应效率。理论上,只有对特定目录设置权限之类的特殊情况,.htaccess文件才应该被使用,再加上想要触发漏洞还要满足.htaccess文件中需定义一个未被全局注册过的Limit指令这一条件,使得符合情况的机器大幅减少。

第二,漏洞利用程序不一定能够稳定运行,只有当hash节点的val被覆盖成了0、2或5的情况下,字符串才能被当作是合法的方法加入到allow数组中。

第三,也是最重要的一点,这个漏洞泄漏的内存数据并不大。



参考:

Optionsbleed - HTTP OPTIONS method can leak Apache's server memory

https://blog.fuzzing-project.org/60-Optionsbleed-HTTP-OPTIONS-method-can-leak-Apaches-server-memory.html





启明星辰积极防御实验室(ADLab)



ADLab成立于1999年,是中国安全行业最早成立的攻防技术研究实验室之一,微软MAPP计划核心成员。截止目前,ADLab通过CVE发布Windows、Linux、Unix等操作系统安全或软件漏洞近400个,持续保持国际网络安全领域一流水准。实验室研究方向涵盖操作系统与应用系统安全研究、移动智能终端安全研究、物联网智能设备安全研究、Web安全研究、工控系统安全研究、云安全研究。研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。