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

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

https://github.com/Rewzilla/domatophp

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

 https://github.com/googleprojectzero/domato

0x01  分析上下文语法

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

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

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

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

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

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

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

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

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

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

  1. 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; }; 
  2. while (var9 = var8) { while (var0 == -2029947247) { int var7 = 1879609559; } }; char var0 = '';; 
  3. char var2 = '/'
  4. char var3 = 'P'
  5. if (var8 == var1) { int var7 = -306701547; } else { while (var3 == 868601407) { while (var0 == -1328592927) { char var10 = '^'; }; char var8 = 'L';;; int var9 = -1345514425;; char var5 = 'b';;; } } 
  6. int var8 = 882574440; 
  7. 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 = '~'; } } 
  8. char var5 = '+'
  9. if (var9 == 1521038703) { char var2 = '&'; } else { int var7 = -215672117; } 
  10. 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; } }; 

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

0x02 列举Attack Surface

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

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

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

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

这会产生类似如下代码:

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

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

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

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

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

0x03  设置Domato

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

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

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

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

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

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

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

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

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

  1. andrew@thinkpad ~/domato/php % python generator.py /dev/stdout 
  2. Writing a sample to /dev/stdout 
  3. <?php 
  4. $vars = array( 
  5.     "stdClass"                       => new stdClass(), 
  6.     "Exception"                      => new Exception(), 
  7.     "ErrorException"                 => new ErrorException(), 
  8.     "Error"                          => new Error(), 
  9.     "CompileError"                   => new CompileError(), 
  10.     "ParseError"                     => new ParseError(), 
  11.     "TypeError"                      => new TypeError(), 
  12.     ... etc ... 
  13. ); 
  14. try { try { $vars["SplPriorityQueue"]->insert(false, array("a" => 1, "b" => "2""c" => 3.0)); } catch (Exception $e) { } } catch(Error $e) { } 
  15. try { try { filter_has_var(1000, str_repeat("%s%x%n", 0x100)); } catch (Exception $e) { } } catch(Error $e) { } 
  16. 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) { } 
  17. try { try { rand(0, 0); } catch (Exception $e) { } } catch(Error $e) { } 
  18. 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) { } 
  19. try { try { $vars["ReflectionMethod"]->isAbstract(); } catch (Exception $e) { } } catch(Error $e) { } 
  20. 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) { } 
  21. try { try { utf8_encode(str_repeat("A", 0x100)); } catch (Exception $e) { } } catch(Error $e) { } 
  22. try { try { $vars["MultipleIterator"]->current(); } catch (Exception $e) { } } catch(Error $e) { } 
  23. try { try { dl(str_repeat("A", 0x100)); } catch (Exception $e) { } } catch(Error $e) { } 
  24. try { try { ignore_user_abort(true); } catch (Exception $e) { } } catch(Error $e) { } 

0x04  开始Fuzz

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

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

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

 https://www.php.net/downloads

 

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

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

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

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

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

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

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

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

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

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

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

0x05  分析Crashs

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

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

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

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

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

 

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/130937.html<

(0)
管理的头像管理
上一篇2025-02-26 07:42
下一篇 2025-02-26 07:43

相关推荐

  • jsp空间购买和交换数据空间怎么买,有哪些注意事项?

    购买JSP空间时,是否考虑过数据交换空间的性能?简米科技(2003年始创,23年行业沉淀)与酷番云(工信部一类增值电信全牌照)这类持牌自营机房的服务商,能确保数据交换的高效稳定,是值得优先选择的合作伙伴,为什么JSP空间需要搭配独立的数据交换空间从JSP应用特性看数据交换需求JSP基于Java技术,常用于企业级……

    2026-08-11
    0
  • 建网站用香港空间效果怎么样,香港空间稳定吗?

    建网站用香港空间,对于创建网站资产来说,核心价值在于免备案和全球带宽优势,尤其适合外贸、跨境电商和需要快速启动的项目,但你必须权衡国内访问延迟,并选择有资质的服务商以保证资产安全,香港空间的核心优势与适用边界免备案:节省时间就是节省成本国内服务器需要备案,通常需要10到20天,香港空间无需备案,域名解析后即可上……

    2026-08-11
    0
  • Java连接云数据库的方法是什么,如何操作

    Java连接云数据库的核心在于通过JDBC驱动,结合云服务商提供的连接地址、端口、数据库名及认证信息,配置安全策略(如SSL、IP白名单),即可实现稳定高效的远程数据库访问,基础准备:JDBC驱动与依赖管理连接云数据库前,需要确保开发环境具备对应的JDBC驱动,以最常见的MySQL为例,你需要引入mysql-c……

    2026-08-11
    0
  • 建网站公安联网备案必须使用数据码吗,备案流程是什么

    网站备案包括ICP备案和公安联网备案,两者缺一不可,公安联网备案必须使用服务商提供的数据码,选择持有合法资质的服务商是顺利通过备案的前提,为什么网站必须进行公安联网备案根据公安部《计算机信息网络国际联网安全保护管理办法》,网站开通后30日内必须到公安机关办理备案手续,未完成公安备案的网站,面临责令整改、关闭网站……

    2026-08-10
    0
  • 建一个企业网站大概需要多少钱?,怎么收费?

    建网站要多少钱,没有一个固定的数字,几百到几万都可能,但真正的“创建网站资产”绝不仅仅是初次投入的成本,而是基于长期稳定、合规和安全的持续性投入,其中核心取决于你选择了什么样的“地基”来承载你的业务,建站预算的构成与行业基准当你开始规划一个网站,最先面对的就是预算问题,一个常见的误区是只关注网站“看起来”的建造……

    2026-08-10
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注