用Domato通过Fuzzing对PHP进行漏洞挖掘研究

安全 漏洞
最近,我一直在对PHP解释器进行Fuzzing,我探索了许多工具和技术(AFL,LibFuzzer,甚至是自定义的Fuzzing引擎),但是最近我决定尝试Domato。

为了清楚和简洁起见,下面引用的代码已精炼为最简单的形式。实际用于Fuzzing测试的完整版本可以在此处找到。

https://github.com/Rewzilla/domatophp

最近,我一直在对PHP解释器进行Fuzzing,我探索了许多工具和技术(AFL,LibFuzzer,甚至是自定义的Fuzzing引擎),但是最近我决定尝试Domato。对于那些不知道的人,Domato是基于语法的DOM Fuzzer,旨在从复杂的代码库中挖掘复杂的bug。它最初是为浏览器而设计的,但是我认为我可以将其用于Fuzzing PHP解释器。

 https://github.com/googleprojectzero/domato
  • 1.

0x01  分析上下文语法

为了使用Domato,必须首先使用上下文无关的语法来描述语言,CFG只是一组定义语言构造方式的规则。例如,如果我们的语言由以下形式的句子组成:

[name] has [number] [adjective] [noun]s. 
[name]'s [noun] is very [adjective]. 
I want to purchase [number] [adjective] [noun]s. 
  • 1.
  • 2.
  • 3.

这些变量中的每一个都可以采用几种形式,例如:

Names: alice, bob, eve 
Numbers: 1, 10, 100 
Adjectives: green, large, expensive 
Nouns: car, hat, laptop 
  • 1.
  • 2.
  • 3.
  • 4.

那么上下文无关文法可能看起来像...

用Domato通过Fuzzing对php进行漏洞挖掘研究

然后Domato使用上下文无关文法生成符合语言规则的随机组合。

eve has 1 expensive laptops. 
alice's hat is very green. 
I want to purchase 100 expensive cars. 
I want to purchase 10 large laptops. 
bob has 100 expensive cars. 
eve has 100 green laptops. 
I want to purchase 100 large laptops. 
bob has 1 large cars. 
I want to purchase 1 large cars. 
I want to purchase 1 large hats. 
bob's laptop is very expensive. 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

可以想象,通过将每个规则分解为更多子规则,我们可以开始定义更复杂的语言,而不仅仅是简单的搜索/替换。实际上,Domato还提供了一些内置函数,用于限制递归并生成基本类型(int,char,string等)。

例如,以下Domato语法,该语法生成伪代码...

用Domato通过Fuzzing对php进行漏洞挖掘研究

将其送入Domato会产生以下结果...

if (var0 == var5) { int var5 = 915941154; } else { int var3 = 1848395349; }; if (var3 == -121615885) { int var7 = 1962369640;; int var1 = 196553597;;; int var6 = -263472135;; } else { int var2 == 563276937; }; 
while (var9 = var8) { while (var0 == -2029947247) { int var7 = 1879609559; } }; char var0 = '';; 
char var2 = '/'
char var3 = 'P'
if (var8 == var1) { int var7 = -306701547; } else { while (var3 == 868601407) { while (var0 == -1328592927) { char var10 = '^'; }; char var8 = 'L';;; int var9 = -1345514425;; char var5 = 'b';;; } } 
int var8 = 882574440; 
if (var8 == var9) { int var7 = 1369926086; } else { if (var9 != -442302103) { if (var3 != 386704757) { while (var4 != -264413007) { char var6 = 'C'; } } else { int var8 = 289431268; } } else { char var10 = '~'; } } 
char var5 = '+'
if (var9 == 1521038703) { char var2 = '&'; } else { int var7 = -215672117; } 
while (var9 == var0) { char var9 = 'X';; int var7 = -1463788903;; }; if (var8 == var7) { int var10 = 1664850687;; char var6 = 'f';; } else { while (var5 == -187795546) { int var3 = -1287471401; } }; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

这非常适合Fuzzing解释器,因为每个样本都是不同的,并且仍然保证其在语法上是有效的!

0x02 列举Attack Surface

然后,下一步就是将PHP语言描述为CFG。如果有兴趣查看完整的CFG,请下载PHP源代码,然后查看Zend/zend_language_parser.y。

但是,我对Fuzzing特定的代码模式更感兴趣。因此,我实现了CFG,使其仅使用“Fuzzing”参数生成对内置函数和类方法的调用。为此,我们需要一个函数,方法及其参数的列表。

有两种获取此数据的方法。最简单的方法是使用PHP的内置Reflection类来遍历所有已定义的函数和类,从而构建一个列表。

以下代码对所有内部PHP函数进行了演示...

用Domato通过Fuzzing对php进行漏洞挖掘研究

这会产生类似如下代码:

andrew@thinkpad /tmp % php lang.php  
zend_version(); 
func_num_args(); 
func_get_arg(arg_num); 
func_get_args(); 
strlen(str); 
strcmp(str1, str2); 
strncmp(str1, str2, len); 
strcasecmp(str1, str2); 
strncasecmp(str1, str2, len); 
each(arr); 
error_reporting(new_error_level); 
define(constant_name, value, case_insensitive); 
defined(constant_name); 
get_class(object); 
... etc ... 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

但是,此问题在于此列表不包含类型信息。ReflectionParameter类包含一个getType方法,但是对于大多数函数而言,它目前似乎不起作用。:(也许这是一个bug?很难说。无论如何,拥有类型信息将使我们的Fuzzing工作变得更加有效,因此值得花时间去寻找另一种获取该数据的方法。

 https://www.php.net/manual/en/reflectionparameter.gettype.php
  • 1.

为了解析出我们需要的东西,PHP的文档通常相当不错,可以在此处将其作为单个压缩的HTML文档下载。经过数小时的辛苦编写正则表达式后,我能够将其解析为可用的函数,方法和参数类型列表。我将其留给读者练习,但是最终产品(以CFG形式)看起来像这样……

 https://www.php.net/distributions/manual/php_manual_en.html.gz
  • 1.

用Domato通过Fuzzing对php进行漏洞挖掘研究

0x03  设置Domato

为了使Domato使用我们的语法,我们还需要定义一些基本组件,例如:

经过大量的调整和调整后,我的配置最终看起来像这样……

用Domato通过Fuzzing对php进行漏洞挖掘研究

我们还需要定义一个语法将被应用到的模板。该模板将设置环境,实例化以后可能使用的所有对象,然后运行每条线程。我的模板看起来像这样...

用Domato通过Fuzzing对php进行漏洞挖掘研究

最后一步是复制和修改Domato的generator.py文件。我发现只需进行以下更改就足够了...

· 第55和62行:将根元素更改为“

· 第78行:引用我自己的“ template.php”

· 第83行:在“ php.txt”中引用我自己的语法

· 第134行:将输出名称和扩展名更改为“

然后,应该能够生成有效的Fuzzing输入!

andrew@thinkpad ~/domato/php % python generator.py /dev/stdout 
Writing a sample to /dev/stdout 
<?php 
$vars = array( 
    "stdClass"                       => new stdClass(), 
    "Exception"                      => new Exception(), 
    "ErrorException"                 => new ErrorException(), 
    "Error"                          => new Error(), 
    "CompileError"                   => new CompileError(), 
    "ParseError"                     => new ParseError(), 
    "TypeError"                      => new TypeError(), 
    ... etc ... 
); 
try { try { $vars["SplPriorityQueue"]->insert(false, array("a" => 1, "b" => "2""c" => 3.0)); } catch (Exception $e) { } } catch(Error $e) { } 
try { try { filter_has_var(1000, str_repeat("%s%x%n", 0x100)); } catch (Exception $e) { } } catch(Error $e) { } 
try { try { posix_access(implode(array_map(function($c) {return "\\x" . str_pad(dechex($c), 2, "0");}, range(0, 255))), -1); } catch (Exception $e) { } } catch(Error $e) { } 
try { try { rand(0, 0); } catch (Exception $e) { } } catch(Error $e) { } 
try { try { fputcsv(fopen("/dev/null""r"), array("a" => 1, "b" => "2""c" => 3.0), str_repeat(chr(135), 65), str_repeat(chr(193), 17) + str_repeat(chr(21), 65537), str_repeat("A", 0x100)); } catch (Exception $e) { } } catch(Error $e) { } 
try { try { $vars["ReflectionMethod"]->isAbstract(); } catch (Exception $e) { } } catch(Error $e) { } 
try { try { $vars["DOMProcessingInstruction"]->__construct(str_repeat(chr(122), 17) + str_repeat(chr(49), 65537) + str_repeat(chr(235), 257), str_repeat(chr(138), 65) + str_repeat(chr(45), 4097) + str_repeat(chr(135), 65)); } catch (Exception $e) { } } catch(Error $e) { } 
try { try { utf8_encode(str_repeat("A", 0x100)); } catch (Exception $e) { } } catch(Error $e) { } 
try { try { $vars["MultipleIterator"]->current(); } catch (Exception $e) { } } catch(Error $e) { } 
try { try { dl(str_repeat("A", 0x100)); } catch (Exception $e) { } } catch(Error $e) { } 
try { try { ignore_user_abort(true); } catch (Exception $e) { } } catch(Error $e) { } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

0x04  开始Fuzz

现在我们要处理的数据非常多,我们需要以一种最大化检测任何类型的内存损坏的机会的方式构建PHP。为此,我强烈建议使用LLVM Address Sanitizer(ASAN),它将检测任何无效的内存访问,即使它不会立即导致崩溃。

 https://github.com/google/sanitizers/wiki/AddressSanitizer
  • 1.

用ASAN编译PHP,下载最新版本的源代码在这里,并运行以下命令...  

 https://www.php.net/downloads
  • 1.

 

./configure CFLAGS="-fsanitize=address -ggdb" CXXFLAGS="-fsanitize=address -ggdb" LDFLAGS="-fsanitize=address" 
make 
make install 
  • 1.
  • 2.
  • 3.

在Fuzzer运行之前,尝试消除不必要地阻碍该过程的任何条件也是一个好主意。例如,像大多数语言一样,PHP具有一个sleep()函数,该函数接受一个整数参数,并仅等待几秒后才能继续。用较大的值(例如INT_MAX)调用此函数将迅速占用较大的簇。

还有一些函数可能会导致进程合法地“崩溃”,例如posix_kill()或posix_setrlimit()。我们可能希望从测试语料库中删除这些内容,以减少误报的数量。

最后,由于PHP文档中列出的许多函数和类实际上在核心安装中不可用(而是从扩展中提供),因此我们不妨从资料集中删除其中的一些函数和类,以避免浪费时间调用不存在的代码。

最后,经过一番试验,我确定了以下清单...

$class_blacklist = array( 
// Can't actually instantiate 
    "Closure"
    "Generator"
    "HashContext"
    "RecursiveIteratorIterator"
    "IteratorIterator"
    "FilterIterator"
    "RecursiveFilterIterator"
    "CallbackFilterIterator"
    "RecursiveCallbackFilterIterator"
    "ParentIterator"
    "LimitIterator"
    "CachingIterator"
    "RecursiveCachingIterator"
    "NoRewindIterator"
    "AppendIterator"
    "InfiniteIterator"
    "RegexIterator"
    "RecursiveRegexIterator"
    "EmptyIterator"
    "RecursiveTreeIterator"
    "ArrayObject"
    "ArrayIterator"
    "RecursiveArrayIterator"
    "SplFileInfo"
    "DirectoryIterator"
    "FilesystemIterator"
    "RecursiveDirectoryIterator"
    "GlobIterator"
); 
 
$function_blacklist = array( 
    "exit", // false positives 
    "readline",    // pauses 
    "readline_callback_handler_install", // pauses 
    "syslog",    // spams syslog 
    "sleep", // pauses 
    "usleep", // pauses 
    "time_sleep_until", // pauses 
    "time_nanosleep", // pauses 
    "pcntl_wait", // pauses 
    "pcntl_waitstatus", // pauses 
    "pcntl_waitpid", // pauses 
    "pcntl_sigwaitinfo", // pauses 
    "pcntl_sigtimedwait", // pauses 
    "stream_socket_recvfrom", // pauses 
    "posix_kill", // ends own process 
    "ereg", // cpu dos 
    "eregi", // cpu dos 
    "eregi_replace", // cpu dos 
    "ereg_replace", // cpu dos 
    "similar_text", // cpu dos 
    "snmpwalk", // cpu dos 
    "snmpwalkoid", // cpu dos 
    "snmpget", // cpu dos 
    "split", // cpu dos 
    "spliti", // cpu dos 
    "snmpgetnext", // cpu dos 
    "mcrypt_create_iv", // cpu dos 
    "gmp_fact", // cpu dos 
    "posix_setrlimit" 
); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.

尽管一台机器既可以单独生成样本,但我还是选择了一小组来加快处理速度。我使用了在Intel NUC上运行的 Proxmox 和10个 Debian VM,其工作如下:

· 节点0:样本生成,托管NFS共享。

· 节点1-8:Fuzzing节点,从NFS共享中提取样本进行测试。

· 节点9:“分类”节点:根据崩溃指标对崩溃样本进行分类。

我创建了简单的原始shell脚本以在每个脚本上运行以执行这些职责,这些脚本可以在上面链接的github repo中找到。

用Domato通过Fuzzing对php进行漏洞挖掘研究

0x05  分析Crashs

几分钟内,该Fuzzer就生成了多个崩溃样本,一夜之间就生成了2,000多个。

通过根据崩溃的指令地址对崩溃进行分类,我能够确定所有2,000个崩溃都是3个错误造成的。其中,有2个显然无法利用(两个都是由于堆栈耗尽导致的OOM错误),但是最后一个似乎是UAF!这是最小化的崩溃示例...

用Domato通过Fuzzing对php进行漏洞挖掘研究

此错误已在bug#79029中得到修复,应该包含在下一个版本中。在接下来的几篇文章中,我将讨论将其根本原因,实现任意代码执行的过程,以及在此过程中发现的一个巧妙的shellcode技巧。

 https://bugs.php.net/bug.php?id=79029
  • 1.

本文翻译自:https://blog.jmpesp.org/2020/01/fuzzing-php-with-domato.html?m=1&fbclid=IwAR16VPIISd2dERbma9o5bmYrEo-iBS7gPhsr0UqjUJWLlctWiHO1zpmPjHg如若转载,请注明原文地址。

 

责任编辑:姜华 来源: 嘶吼网
相关推荐

2020-09-29 10:44:51

漏洞

2020-12-18 09:49:48

iOS ChromiWKWebViewJavaScript

2017-09-19 15:01:06

PHP漏洞渗透测试

2010-05-19 14:13:12

MySQL存储过程

2023-08-11 09:41:48

AFLfuzzingPatch

2010-02-22 15:13:01

Python模块

2009-12-28 11:14:53

ADO 连接对象

2019-01-09 10:26:32

web安全漏洞挖掘前端打包

2020-09-25 10:14:54

漏洞

2010-03-01 14:02:26

Python批处理语言

2010-03-10 18:29:57

2010-02-02 15:48:49

Python数据库

2009-12-09 16:52:51

VS 2003插件

2014-09-25 19:30:51

2021-02-23 10:43:42

Facebook Ga

2010-01-05 20:39:22

2016-05-03 09:51:08

2020-09-27 14:23:36

AI

2015-03-06 15:43:39

2013-07-03 11:28:47

点赞
收藏

51CTO技术栈公众号