为了更好地清晰和简约考虑,下边引入的编码已精练为非常简单的方式。具体用以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仅仅一组定义语言结构方法的标准。例如,如果我们的语言由下列方式的语句构成:
[name]has[number][adjective][noun]s.[name]'s[noun]isvery[adjective].Iwanttopurchase[number][adjective][noun]s.
这种自变量中的每一个都能够选用几类方式,例如:
Names:alice,bob,eveNumbers:1,10,100Adjectives:green,large,expensiveNouns:car,hat,laptop
那麼前后文不相干语法很有可能看上去像...
随后Domato应用前后文无关文法生成符合语言表达规则的任意组合。
evehas1expensivelaptops.alice'shatisverygreen.Iwanttopurchase100expensivecars.Iwanttopurchase10largelaptops.bobhas100expensivecars.evehas100greenlaptops.Iwanttopurchase100largelaptops.bobhas1largecars.Iwanttopurchase1largecars.Iwanttopurchase1largehats.bob'slaptopisveryexpensive.
可以想像,根据将每一个规则溶解为更多子规则,我们可以逐渐界定更繁杂的语言表达,而不仅是简洁的检索/更换。事实上,Domato还带来了一些内嵌函数公式,用以限定递归法并生成基本上种类(int,char,string等)。
例如,下列Domato英语的语法,该语法生成伪代码...
将其送进Domato会造成下列結果...
if(var0==var5){intvar5=915941154;}else{intvar3=1848395349;};if(var3==-121615885){intvar7=1962369640;;intvar1=196553597;;;intvar6=-263472135;;}else{intvar2==563276937;};while(var9=var8){while(var0==-2029947247){intvar7=1879609559;}};charvar0='';;charvar2='/';charvar3='P';if(var8==var1){intvar7=-306701547;}else{while(var3==868601407){while(var0==-1328592927){charvar10='^';};charvar8='L';;;intvar9=-1345514425;;charvar5='b';;;}}intvar8=882574440;if(var8==var9){intvar7=1369926086;}else{if(var9!=-442302103){if(var3!=386704757){while(var4!=-264413007){charvar6='C';}}else{intvar8=289431268;}}else{charvar10='~';}}charvar5=' ';if(var9==1521038703){charvar2='&';}else{intvar7=-215672117;}while(var9==var0){charvar9='X';;intvar7=-1463788903;;};if(var8==var7){intvar10=1664850687;;charvar6='f';;}else{while(var5==-187795546){intvar3=-1287471401;}};
这特别适合Fuzzing编译器,由于每一个样版全是不一样的,而且依然确保其在语法结构上是合理的!
0x02 例举Attack Surface
随后,下一步便是将PHP语言表达叙述为CFG。如果有兴趣爱好查询详细的CFG,立即下载PHP源码,随后查询Zend/zend_language_parser.y。
可是,我对Fuzzing特殊的编码方式更有兴趣。因而,我完成了CFG,使其仅应用“Fuzzing”主要参数转化成对内嵌函数公式和类方法的启用。因此,大家必须一个函数公式,方式以及主要参数的目录。
有二种获得此数据信息的方式。有效的方法是采用PHP的内嵌Reflection类来解析xml全部已界定的函数公式和类,进而建立一个目录。
下列编码对全部内部结构PHP函数公式开展了演试...
这会造成相近如下所示编码:
andrew@thinkpad/tmp%phplang.phpzend_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...
可是,此问题取决于此目录不包含类型信息。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键入!
andrew@thinkpad~/domato/php%pythongenerator.py/dev/stdoutWritingasampleto/dev/stdout<?php$vars=array("stdClass"=>newstdClass(),"Exception"=>newException(),"ErrorException"=>newErrorException(),"Error"=>newError(),"CompileError"=>newCompileError(),"ParseError"=>newParseError(),"TypeError"=>newTypeError(),...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){}
0x04 逐渐Fuzz
如今我们要解决的信息十分多,大家必须以一种利润最大化检验一切种类的运行内存破坏的时机的方法搭建PHP。因此,我建议应用LLVM Address Sanitizer(ASAN),它将检验一切没用的运行内存浏览,即使它不容易马上造成奔溃。
https://github.com/google/sanitizers/wiki/AddressSanitizer
用ASAN编译程序PHP,下载最新版本的源码在这儿,并运作下列指令...
https://www.php.net/downloads
./configureCFLAGS="-fsanitize=address-ggdb"CXXFLAGS="-fsanitize=address-ggdb"LDFLAGS="-fsanitize=address"makemakeinstall
在Fuzzer运作以前,试着清除多余地阻拦该全过程的一切标准也是一个好点子。例如,像大部分语言表达一样,PHP具备一个sleep()函数公式,该函数接受一个整数主要参数,并仅等待几秒后才可以再次。用比较大的值(例如INT_MAX)调用此函数将快速占用比较大的簇。
也有一些函数很有可能会造成过程合理合法地“奔溃”,例如posix_kill()或posix_setrlimit()。大家很有可能想要从检测语料库中移除这种內容,以降低乱报的总数。
最终,因为PHP文档中列举的很多函数和类事实上在关键安裝中不能用(反而是从拓展中给予),因而大家何不从材料集中化删掉在其中的一些函数和类,以防止浪费時间调用不会有的编码。
最终,通过一番实验,我明确了下列明细...
$class_blacklist=array(//Can'tactuallyinstantiate"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",//falsepositives"readline",//pauses"readline_callback_handler_install",//pauses"syslog",//spamssyslog"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",//endsownprocess"ereg",//cpudos"eregi",//cpudos"eregi_replace",//cpudos"ereg_replace",//cpudos"similar_text",//cpudos"snmpwalk",//cpudos"snmpwalkoid",//cpudos"snmpget",//cpudos"split",//cpudos"spliti",//cpudos"snmpgetnext",//cpudos"mcrypt_create_iv",//cpudos"gmp_fact",//cpudos"posix_setrlimit");
虽然一台设备既可以独立转化成样版,但我还是挑选了一工作组来加速响应速度。我采用了在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个显而易见没法运用(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倘若转截,请标明全文详细地址。