正则表达式基础库源码审计与漏洞分析
正则表达式是字符串处理的基本功能,它使用单个字符串来描述、匹配一系列符合某个句法规则的字符串,绝大部分语言库都提供了正则表达式的功能,例如:perl、python、tcl、php、glibc、uclibc、php等等,并且一般使用开源的正则表达式基础库来实现其功能,例如:glibc就使用了GRegex来提供正则表达式功能,而glibc是GNU发布的C运行库,是Linux系统中最底层的API,几乎其它任何应用库都会依赖于glibc。所以,如果正则表达式基础库包含漏洞,则会影响到上层的各种软件,风险隐患极大。
最近,360代码卫士团队对常见的正则表达式基础库进行了源码审计和漏洞挖掘,发现了10余个漏洞,均提交至CVE以及国家信息安全漏洞库,目前已获得CVE编号的漏洞有5个,具体如下:
CVE编号 | 影响软件和版本 | 漏洞类型 |
CVE-2017-11660 | PCRE - 8.41 | 内存越界访问 |
CVE-2017-11659 | PCRE - 8.41 | 越界读漏洞 |
CVE-2017-11164 | PCRE - 8.41 | 递归处理不当 |
CVE-2017-9729 | GRegex | 递归处理不当 |
CVE-2017-9728 | GRegex | 越界读一个字节 |
其中,PCRE库
在Apache、MySQL、PHP、KDE、Postfix、Analog、Nmap、Apple Safari、Nginx 等基础框架或组件中均有使用;GRegex 库由Isamu Hasegawak开发,在glibc、uclibc等标准库中有所使用。
本文以PCRE库为例,简述源码审计和漏洞挖掘的过程,并深入分析CVE-2017-11660的原理,说明不当的底层基础库源码处理对上层应用软件的影响和危害,进而说明基础库软件源代码安全的重要性。
源码审计与漏洞挖掘
PCRE库的源代码基本信息如下:
源代码基本信息表 | |
源代码名称 | pcre-8.41-IR-1.6.1.zip |
开发语言 | C/C++ |
源代码文件数 | 43 |
源代码行数 | 64616 |
使用360代码卫士扫描后,结果统计如下:
等级统计表 | ||
等级 | 数量 | 所占% |
高 | 3 | 0.68 |
中 | 378 | 85.52 |
低 | 61 | 13.8 |
总体而言,PCRE的代码质量还是挺高的。各缺陷详细信息及分类如下:
表中的越界访问漏洞就是CVE-2017-11660,主要触发原因是代码中没有对循环边界进行正确检查,导致越界访问了分配的内存。其主要相关代码如下:
for (;;)
{
RMATCH(eptr, ecode, offset_top, md,eptrb, RM18);
if (rrc != MATCH_NOMATCH)RRETURN(rrc);
if (eptr-- == pp) break; /* Stop if tried at original pos */
BACKCHAR(eptr);
}
其中的宏定义:#define BACKCHAR(eptr) while((*eptr & 0xc0) == 0x80)eptr--
在这个循环中有个判断条件退出,当“eptr-- == pp”的时候就会退出,但是后面的宏“BACKCHAR(eptr)”会对eptr做递减,当递减至“eptr<pp”时,则这个判断不再成立,导致访问分配给eptr之前的内存数据。
内存溢出是代码缺陷运行的结果。造成此缺陷,从代码层面讲,直接的原因是循环退出条件判断不当。上述代码中,对循环“for(;;)”的退出,通过条件“if (eptr-- == pp)”进行判断,如果eptr在循环开始时,其值已经小于pp,那么,该循环将无法有效终止。此类相似情况可能出现的判断条件为:
l 对获取的break等可以有效退出循环的语句进行判断,判断是否在循环体中;
l 判断执行该break,是否依赖于绝对相等(“==”)这样的条件;
l 分析等值判断的左右值,是否为可被污染的值;
l 分析循环体是否有“死循环”的可能(如“for(;;) { … }、while(true){ … }”等)。
上述代码中,缺陷如下图所示:
在上述for循环中,缺少每次进入循环的有效判断,创造了死循环的可能性;if语句判断条件为等值判断,只有在左右两值相等的情况下,才能触发执行语句;等值判断的左右值,存在被污染的可能性。这样,if等值条件可能无法被满足,循环退出语句break无法被执行,因此进入死循环。检测流程示意图如下:
进行检测时,界面显示效果如下:
分析发现,这段代码是PCRE中“match”函数处理“OP_NCLASS”、
“OP_CLASS”操作码的代码,并且这段代码在其它地方也出现了,对应着“OP_XCLASS”操作码。
漏洞验证分析
正则表达式包括查找符合某些复杂规则的字符串的描述规则,这些规则有通用的标准,涉及到上述漏洞的元字符包括:
元字符 | 功能说明 | PCRE对应的处理码 |
\C | 匹配任意字符 | OP_ANYBYTE |
[ ] | 字符类,匹配[]之间的字符,另外[]之间^表示非的意思 | OP_NCLASS, OP_CLASS |
* | 重复零次或更多次 | OP_CRSTAR |
根据代码审计的分析结果,我们构造了一个精简触发的验证POC:
此POC编译后运行,程序崩溃:
POC的实现从subjectmp的内存往前搜索字符串“AABB”,直到匹配到“AABB”的内存内容或者是访问了内存边界从而导致段访问错误。gdb中调试信息如下:
从调试信息中,我们可以看到subject的地址为0xbfffafc7:
正是提供的搜索内容“\xdd\x88\x42\x41”。pp 和 eptr 的值为:
pp的值正是subject+1,为正常的值,而eptr远远小于pp,且达到了内存边界,从而导致了内存访问异常。
漏洞利用与危害分析
在上述POC中往内存前搜索字符串“AABB”,当然也可以搜索其它字符串,这样会引发严重的信息泄露。PHP使用了PCRE正则表达式库,因此我们尝试在PHP中对此漏洞进行利用和危害分析,确认PCRE的这个漏洞是否影响到PHP。下载PHP最新版7.2.0,并编译,构造PHP的精简POC如下:
其中,preg_match函数的功能就是执行匹配正则表达式,其底层实现采用了PCRE。PHP 7.2.0使用了PCRE 4.1。分析发现,PHP对PCRE中的函数进行封装调用,且对结果值做了检测。相关代码在ext/pcre/php_prce.c文件中的php_pcre_match_impl内:
if((offsets[1] - offsets[0] < 0) || pcre_get_substring_list(subject, offsets,count, &stringlist) < 0) {
efree(subpat_names);
efree(offsets);
if (match_sets) efree(match_sets);
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Getsubpatterns list failed");
RETURN_FALSE;
}
“offsets[1] - offsets[0]<0”对返回的结果进行了检测,当漏洞触发后,返回的结果中offsets[1]是负数,所以条件成立,然后就返回了RETURN_FALSE。
所以,虽然此漏洞存在于PCRE中,但由于PHP对PCRE中的函数进行了封装调用,并对结果值做了检测,因此PHP并不受CVE-2017-11660这个漏洞的影响。但本次源码审计和漏洞挖掘发现的另外一个PCRE的漏洞CVE-2017-11164,就确认影响到了PHP,可以在PHP中造成拒绝服务。
即可造成:
CVE-2017-11164漏洞影响到PHP的库函数preg_match,因此基于PHP的各种web应用如果使用了pcre_match这个库函数则也会受到影响,存在被拒绝服务攻击的风险。比如,web应用中使用preg_match来实现搜索功能,那么攻击者在搜索的原始内容中插入“\x6C\x6F\xE5\xA2\x80\x2D”内容,然后在匹配的时候使用特殊构造的字符即可拒绝服务攻击。
(本文作者:360代码安全实验室总监柳本金)