PHP远程DoS漏洞分析
及防护方案

  • A+
所属分类:漏洞分享

执行摘要

5月14日,国内爆出php远程DoS漏洞,官方编号69364。利用该漏洞构造poc发起链接,很容易导致目标主机cpu的占用率100%,涉及PHP多个版本。绿盟科技威胁响应中心随即启动应急机制, 应急响应工作随即启动。

  1. 15日夜,启动漏洞分析工作,同步将分析结果发送产品团队;

  2. 16日,发布产品规则升级通告,绿盟科技RSAS产品升级相继就绪,客户通过在线及离线升级的方法,即可获得漏洞的检测能力;同时,在线漏洞检测引擎就绪;

  3. 17日,漏洞深入分析进行中。绿盟科技NIPS产品升级就绪,客户通过在线及离线升级的方法,即可获得漏洞的防护能力;

  4. 18日,我们回顾此次PHP漏洞的信息要点,从PHP漏洞防护的角度进行总结,为大家制定防御方案提供补充信息。

如果您需要了解更多信息,请联系:

PHP远程DoS漏洞

4月3日,有人在PHP官网提交PHP 远程DoS漏洞(PHP Multipart/form-data remote dos Vulnerability),代号69364。由于该漏洞涉及PHP的所有版本,故其影响面较大,一经发布迅速引发多方面关注。14日,各种PoC已经在网络上流传。此次漏洞具备如下特性:

  1. 一旦被利用成功,可以在迅速消耗被攻击主机的CPU资源,从而达到DoS的目的;

  2. PHP在全球的部署量相当大,为攻击者提供了相当多可以攻击的目标;

  3. PHP官方目前仅给出了5.4及5.5版本的补丁

受此漏洞影响的软件及系统包括PHP的如下版本。

  • PHP 5.0.0 – 5.0.5

  • PHP 5.1.0 – 5.1.6

  • PHP 5.2.0 – 5.2.17

  • PHP 5.3.0 – 5.3.29

  • PHP 5.4.0 – 5.4.40

  • PHP 5.5.0 – 5.5.24

  • PHP 5.6.0 – 5.6.8

绿盟科技常年密切关注PHP的安全问题。绿盟科技威胁响应中心在获知相关信息后,随即启动应急机制,相关工作随即启动。本文章将会深入分析该漏洞,并给出应对方案。

PHP远程DoS漏洞分析

2015年5月15日夜,绿盟科技威胁响应中心在获取PHP漏洞传播情况的同时,也在进行漏洞的分析工作,通过重现漏洞的攻击过程,分析其工作原理,得以清晰识别及检测该漏洞方法。

Boundary中的键值对分隔

PHP是一种流行的Web服务器端编程语言,它功能强大,简单易用,利用它编写网络应用程序,可以应对大规模的Http请求,所以很多业务环境中都部署了PHP。考虑规范性,PHP在设计之初就遵循rfc规范,进行各个协议模块的封装及过程处理。PHP与其他同样遵循rfc规范的语言及环境相比,不过是处理方式不同。

而从rfc1867开始,http协议开始支持”multipart/form-data”请求,以便接受多种数据格式,包括多种变量甚至是文件上传。multipart/form-data中可以包含多个报文,每一个报文boundary(分隔符)分隔开来,而每个报文中都包含了多行键值对,键值对用冒号分隔,这样的设计是为了让程序可以清晰的区分这些数据。

PHP远程DoS漏洞分析
及防护方案

但如果由于某种原因,键值中间缺少了那个冒号,PHP函数会将下一对键值合并到了上一行,形成这样的键值对,“键1:值1键2值2”。由于PHP进行键值合并的算法不够优化,这样的事情发生几次还没什么,如果数以百万记,就变成了一种灾难。

在下面的例子中,当a的部分达到一定数量的时候(几十万行or上百万行),由于每行键与值之间并没有冒号分隔,函数就自动将下一行的键值对合并,这样数据越来越大,越来越长,函数针对这些数据不断执行内存的分配和释放,最终被攻击目标主机的CPU资源被耗尽。

PHP远程DoS漏洞分析
及防护方案

*注:PHP中,Boundary是可以自定义的,比如“—–WebKitFormBoundarypE33TmSNWwsMphqz”

这样的代码,在抓包时显示情况如下:

PHP远程DoS漏洞分析
及防护方案

Boundary报文解析过程

PHP在main/rfc1867.c中,有两个函数都涉及boundary的解析,包括SAPI_API SAPI_POST_HANDLER_FUNC及multipart_buffer_headers函数。DoS漏洞出现在main/rfc46675pxultipart_buffer_headers函数。

PHP先解析解析multipart/form-data http请求, http请求体的入口函数在SAPI_POST_HANDLER_FUNC(rfc1867.c中的函数),SAPI_POST_HANDLER_FUNC函数首先解析请求的boundary,也就是POST请求中第一次定义时的boundary;并且在其内部调用了multipart_buffer_headers,该函数先找到boundary(也就是一次引用的boundary),会和定义时的boundary比较。如果相等即找到第一次引用的boundary,接下来会逐行读取请求的输入以解析body port header(也就是解析第一次引用boundary后面的内容)。

SAPI_API SAPI_POST_HANDLER_FUNC

  1.         /* Get the boundary */

  2.         /* 开始解析boundary */

  3.         boundary = strstr(content_type_dup, “boundary”);

  4.         if (!boundary) {

  5.                 int content_type_len = strlen(content_type_dup);

  6.                 char *content_type_lcase = estrndup(content_type_dup, content_type_len);

  7.                 php_strtolower(content_type_lcase, content_type_len);

  8.                 boundary = strstr(content_type_lcase, “boundary”);

  9.                 if (boundary) {

  10.                         boundary = content_type_dup + (boundary – content_type_lcase);

  11.                 }

  12.                 efree(content_type_lcase);

  13.         }

  14.         if (!boundary || !(boundary = strchr(boundary, ‘=’))) {

  15.                 sapi_module.sapi_error(E_WARNING, “Missing boundary in multipart/form-data POST data”);

  16.                 return;

  17.         }

  18.         boundary++;

  19.         boundary_len = strlen(boundary);

  20.         /* 对bondary进行合法校验 */

  21.         if (boundary[0] == ‘”‘) {

  22.                 boundary++;

  23.                 boundary_end = strchr(boundary, ‘”‘);

  24.                 if (!boundary_end) {

  25.                         sapi_module.sapi_error(E_WARNING, “Invalid boundary in multipart/form-data POST data”);

  26.                         return;

  27.                 }

  28.         } else {

  29.                 /* search for the end of the boundary */

  30.                 boundary_end = strpbrk(boundary, “,;”);

  31.         }

  32.         if (boundary_end) {

  33.                 boundary_end[0] = ‘\0′;

  34.                 boundary_len = boundary_end-boundary;

  35.         }

  36.         /* Initialize the buffer */

  37.         if (!(mbuff = multipart_buffer_new(boundary, boundary_len TSRMLS_CC))) {

  38.                 sapi_module.sapi_error(E_WARNING, “Unable to initialize the input buffer”);

  39.                 return;

  40.         }

  41.         while (!multipart_buffer_eof(mbuff TSRMLS_CC))

  42.         {

  43.                 char buff[FILLUNIT];

  44.                 char *cd = NULL, *param = NULL, *filename = NULL, *tmp = NULL;

  45.                 size_t blen = 0, wlen = 0;

  46.                 off_t offset;

  47.                 zend_llist_clean(&header);

  48.                 /* 漏洞函数 */

  49.                 if (!multipart_buffer_headers(mbuff, &header TSRMLS_CC)) {

  50.                         goto fileupload_done;

  51.                 }

multipart_buffer_headers

  1. /* parse headers */

  2. static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header TSRMLS_DC)

  3. {

  4.         char *line;

  5.         mime_header_entry prev_entry = {0}, entry;

  6.         int prev_len, cur_len;

  7.         /* didn’t find boundary, abort */

  8.         if (!find_boundary(self, self->boundary TSRMLS_CC)) {

  9.                 return 0;

  10.         }

  11.         /* get lines of text, or CRLF_CRLF */

  12.         /* 逐行解析 */

  13.         while( (line = get_line(self TSRMLS_CC)) && line[0] != ‘\0′ )

  14.         {

  15.                 /* add header to table */

  16.                 char *key = line;

  17.                 char *value = NULL;

  18.                 if (php_rfc1867_encoding_translation(TSRMLS_C)) {

  19.                         self->input_encoding = zend_multibyte_encoding_detector(line, strlen(line), self->detect_order, self->detect_order_size TSRMLS_CC);

  20.                 }

  21.                 /* space in the beginning means same header */

  22.                 /* 如果该行开头不是空格,则试图寻找’:'查看是否是有效键值对 */

  23.                 if (!isspace(line[0])) {

  24.                         value = strchr(line, ‘:’);

  25.                 }

  26.                 /* 如果找到’:'则说明该行包含一个有效的键值对,解析它 */

  27.                 if (value) {

  28.                         *value = 0;

  29.                         do { value++; } while(isspace(*value));

  30.                         entry.value = estrdup(value);

  31.                         entry.key = estrdup(key);

  32.                 /* 如果不包含’:',且该行前有一个有效键值对,则说明这一行是上一个键值对的值 */

  33.                 } else if (zend_llist_count(header)) { /* If no ‘:’ on the line, add to previous line */

  34.                         prev_len = strlen(prev_entry.value);

  35.                         cur_len = strlen(line);

  36.                         /* 进行值合并操作 */

  37.                         entry.value = emalloc(prev_len + cur_len + 1);

  38.                         memcpy(entry.value, prev_entry.value, prev_len);

  39.                         memcpy(entry.value + prev_len, line, cur_len);

  40.                         entry.value[cur_len + prev_len] = ‘\0′;

  41.                         entry.key = estrdup(prev_entry.key);

  42.                         zend_llist_remove_tail(header);

  43.                 } else {

  44.                         continue;

  45.                 }

  46.                 zend_llist_add_element(header, &entry);

  47.                 prev_entry = entry;

  48.         }

  49.         return 1;

  50. }

出现问题的函数处理逻辑

multipart_buffer_headers函数在解析HTTP请求中的multipart头部数据时,每次解析由get_line得到的一行键值对。当被解析的行是以空白字符开始,或者出现一个不包含 ‘ : ‘ 的行,该行将被当作是上一行键值对的延续来处理,将当前的值拼接到上一个键值对里,并且在拼接的过程里,该函数进行如下动作:

一次内存分配

  1. entry.value = emalloc(prev_len + cur_len + 1);

两次内存复制

  1. memcpy(entry.value, prev_entry.value, prev_len);

  2. memcpy(entry.value + prev_len, line, cur_len);

一次内存释放

  1. zend_llist_remove_tail(header);

当出现多个不包含 ‘ : ‘ 的行时,PHP就会进行大量内存分配释放的操作,并且分配的空间与拷贝的长度将越来越大。当行的数目足够多时,拷贝的操作将显著的消耗服务器的CPU。实际测试中,包含近一百万行的头字段可以使服务器的CPU保持100%几秒或者数十秒。如果并发多个攻击请求,可能造成更长时间的资源占用。

漏洞利用原理

攻击者可通过发送一个2M左右的包含多行multipart头部数据的HTTP请求来发起攻击,无需认证,也不依赖PHP程序本身的内容。例如,通过发送畸形请求,每隔若干秒,同时并发多个这样的请求,就会耗尽目标主机的CPU资源。

PHP远程DoS漏洞检测

面对如此简单的漏洞利用,以及较低的攻击门槛,分析人员迅速将经过安全验证后的检测方法向云端、产品端及服务端传递,并建议用户尽快对其业务环境进行一次全面的漏洞检测,以便可以尽快拿到第一手数据,为后续制定漏洞防护方案及执行措施提供数据支撑及决策依据。

云端检测

5月16日晚,绿盟科技客户自助门户系统Portal发布PHP远程DoS漏洞检测引擎,为PHP Multipart/form-data远程DoS漏洞(PHP-69364)提供扫描支持。

PHP远程DoS漏洞分析
及防护方案

现在您随时可以使用这个自助系统,对业务环境进行扫描,以便确认是否存在该漏洞,扫描请点击: https://portal.nsfocus.com/vulnerability/list/

漏洞确认 当扫描结果信息中出现信息“您的检测目标存在此漏洞”,即可确认当前业务环境中存在该漏洞,建议您尽快制定防护计划,以避免系统在获得加固前遭受攻击。

产品检测

通过部署绿盟远程安全评估系统(Remote Security Assessment System),可以在您的业务环境中快速扫描及获取此次漏洞情况,同时支持1实现漏洞的安全闭环管理,包括预警、检测、分析管理、修补、审计等几个环节;2获取丰富的漏洞和配置知识库支持,该知识库是国内领先的安全漏洞库,目前累计接近3万条;3灵活部署,并获得绿盟企业安全中心(NSFOCUS ESPC)进行集中管理,可以有效实现大型网络的统一漏洞管理。4享有Gartner推荐的信誉保障。

PHP远程DoS漏洞分析
及防护方案

针对此次PHP远程DoS漏洞,绿盟科技漏洞扫描系列产品已经就绪,用户请尽快升级到如下版本,以便为您定制自己的防护措施提供第一手数据支撑。

PHP远程DoS漏洞分析
及防护方案

PHP远程DoS漏洞防护

知道了漏洞利用方法,也知道了攻击检测方法,那么漏洞的防护也就知道该如何做了。如果确认您的业务环境中存在这个漏洞,那么就需要参考上面的信息,尽快制定并启动加固方案,这些加固从漏洞补丁开始,到产品防护,到整体防护,逐步推进。

漏洞加固

PHP官方已经针对PHP 5.4 及PHP 5.5版本给出了补丁,请使用这些版本的用户,尽快到官方网站下载并安装补丁,补丁的下载地址如下:

http://php.net/ChangeLog-5.php#5.4.41

http://php.net/ChangeLog-5.php#5.5.25

PHP远程DoS漏洞分析
及防护方案

如果您使用了PHP的其它版本,请随时关注PHP官方的最新通告。

产品防护

只是只是安装了漏洞补丁是不够的,整体安全等级的提升以及应对未来的攻击,安全产品是必不可少的一环,将Web系统置于DMZ区域并加以多产品的整体防护,是我们推荐的做法。在如下部署环境中,以绿盟网络入侵防护系统(Network Intrusion Prevention System,简称NIPS)为例,对业务系统部署NIPS,可以提供PHP远程DoS漏洞攻击防护。

PHP远程DoS漏洞分析
及防护方案

目前相关产品的升级信息如下:

PHP远程DoS漏洞分析
及防护方案

请所有使用绿盟产品的用户尽快升级产品规则。绿盟科技已在软件升级公告中提供规则升级包,规则可以通过产品界面的在线升级进行。如果您的业务系统暂时还无法升级规则包,那么可以在软件升级页面中,找到对应的产品,通过下载升级包,以离线方式进行升级。 相关信息请访问:

业务安全加固

在一些大型的企业或组织中,PHP远程DoS漏洞的防护或许并不能快速执行,其原因在于:1需要考虑业务系统的可用性;2需要考虑整体实施方案制定;3需要尽可能降低加固动作对业务环境的二次伤害。这就需要企业自身、漏洞相关厂商、安全厂商一起协作才能形成快速、安全、有效的行动方案,避免业务系统在获得安全加固之前遭受攻击。在此次应急响应过程中,绿盟科技的服务人员向客户建议行动方案应该且至少包含如下环节:

1、首先,应该第一时间获取漏洞通告及相关信息,了解此次漏洞的影响范围及深度。

2、再者,需要将通告和解读与自身实际IT业务系统状况相结合,全面判断出影响范围和程度(这包括对自身业务及对其客户的影响程度),这个判断过程,需要数据作为准确方案制定的事实依据,建议用户使用安全可靠的漏洞扫描工具,升级最新发布的插件或规则库,对全网进行安全扫描,拿到第一手数据后以便作为决策依据;

3、再次,IT人员需要从业务稳定性、危害程度和范围及重要性等多个维度综合考虑,制定整改时间计划表,权重由高到低依次对局部网络及主机设备或某业务系统设备展开整改和加固工作(建议邀请漏洞相关厂商及安全厂商一同参与)。然后,在加固阶段性或整体完成后,需要再次进行完整扫描和人工验证整改加固结果,在技术投入允许的条件下,建议您再次进行各方面日志分析,观察整改加固期间有没有成功的攻击到其系统造成其他损失;

3.1、这个阶段需要安全厂商提供专业技术协助,比如漏洞加固咨询、验证加固是否成功;同时需要了解安全厂商的哪些设备已经发布或即将发布防护规则,升级后即可进行防护;

3.2、如果还没有采用任何一款安全设备,就需要采取临时防护措施,包括采用漏洞相关厂商及安全厂商的相关方案,为整体加固争取时间,避免在未加固整改成功之前这个窗口时间遭到攻击并受到损失,这样的情况在相当多的0day事件中屡见不鲜;

3.3、另外,还需要漏洞相关厂商与安全厂商通力协作,互相沟通漏洞原理和利用过程,进行较深层次的解读,才能够促进漏洞相关厂商的开发人员深入了解这个漏洞并根据其自身情况进行代码层面的整改;

4、最后,在整体响应工作完成后,进行总结和备案记录。

威胁情报

从此次PHP远程DoS漏洞情况可以看到,无论漏洞原理怎样,无论漏洞防护方案如何实施,关键在于尽可能快的了解到漏洞信息及相关的情报,以便尽可能快的启动应急响应机制。这无论对于解决传统安全或者APT攻击来说都是重要的手段之一,威胁情报的获取及响应都体现了防御能力的建设程度,威胁情报服务体系至少包含了威胁监测及响应、数据分析及整理、业务情报及交付、风险评估及咨询、安全托管及应用等各个方面,涉及研究、产品、服务、运营及营销的各个环节,绿盟科技通过研究、云端、产品、服务等立体的应急响应体系,向企业和组织及时提供威胁情报并持续进行后续服务,保障客户业务的顺畅运行。

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: