为了清晰和简洁,下面引用的代码已被压缩为最简单的形式。可以在此处找到实际用于模糊测试的完整版本。https://github.com/Rewzilla/domatophp我最近一直在对PHP解释器进行模糊测试,我探索了许多工具和技术(AFL、LibFuzzer,甚至是自定义模糊测试引擎),但最近我决定尝试Domato。对于那些不知道的人,Domato是一种基于语法的DOM模糊器,旨在从复杂的代码库中挖掘出复杂的错误。它最初是为浏览器设计的,但我想我可以用它来模糊测试PHP解释器。https://github.com/googleprojectzero/domato0x01分析上下文语法为了使用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那么上下文无关语法可能看起来像...thenDomato使用上下文无关文法来生成遵循语言规则的随机组合。evehas1expensivelaptops.alice'shatisverygreen.Iwanttopurchase100expensivecars.Iwanttopurchase10largelaptops.bobhas100expensivecars.evehas100greenlaptops.Iwanttopurchase100largelaptops.bobhas1largecars.Iwanttopurchase1largecars.Iwanttopurchase1largehats.bob'slaptopisveryexpensive.可以想象,通过将每个规则分解为更多子规则,我们可以开始定义更复杂的语言,Andnotjustsimplesearch/replace.Infact,Domatoalsoprovidessomebuilt-infunctionsforlimitingrecursionandgeneratingprimitivetypes(int,char,string,etc.).例如,以下生成伪代码的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)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;}};这对于模糊解释器来说非常有用,因为每个样本都是不同的,并且仍然保证在语法上是有效的!0x02列出攻击面,然后,下一步是将PHP语言描述为CFG。如果您有兴趣查看完整的CFG,请下载PHP源代码并查看Zend/zend_language_parser.y。但是,我更感兴趣的是对特定代码模式进行模糊测试。所以我实现了CFG,只生成对带有“模糊测试”参数的内置函数和类方法的调用。为此,我们需要一个函数、方法及其参数的列表。有两种获取此数据的方法。最简单的方法是使用PHP的内置反射类迭代所有定义的函数和类,构建一个列表。以下代码演示了所有内部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);每个(arr);error_reporting(new_error_level);定义(constant_name,value,case_insensitive);defined(constant_name);get_class(object);...等...但是,问题是这个列表不包含类型信息。ReflectionParameter类包含一个getType方法,但对于大多数函数来说,它目前似乎不起作用。:(也许这是一个错误?很难说。无论如何,拥有类型信息将使我们的模糊测试工作更有效率,因此值得花时间寻找另一种获取该数??据的方法。https://www.php.net/manual/en/reflectionparameter.gettype.php为了解析出我们需要的东西,PHP的文档一般都很好,可以在这里下载为单个压缩的HTML文档。经过数小时的苦心编写正则表达式之后,我能够将其解析为可用函数、方法和参数类型的列表。我把它留给读者练习,但最终产品(以CFG形式)看起来像这样......https://www.php.net/distributions/manual/php_manual_en.html.gz0x03设置Domato以便Domato要使用我们的语法,我们还需要定义一些基本组件,例如:经过大量的调整和调整,我的配置最终看起来像这样……我们还需要定义一个模板,该语法将应用于该模板。该模板将设置环境,实例化以后可能使用的任何对象,然后运行每个线程。我的模板是这样的...最后一步是复制和修改Domato的generator.py文件。我发现只需进行以下更改就足够了...第55和62行:将根元素更改为“第78行:引用我自己的“template.php”第83行:在“php.txt中引用我自己的语法”行134:将输出名称和扩展名更改为“那么,您应该能够生成有效的模糊测试输入!andrew@thinkpad~/domato/php%pythongenerator.py/dev/stdoutWritingasampleto/dev/stdoutnewstdClass(),"Exception"=>newException(),"ErrorException"=>newErrorException(),"Error"=>newError(),"CompileError"=>newCompileError(),"ParseError"=>newParseError(),"TypeError"=>newTypeError(),...等...);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{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开始模糊测试现在我们有很多数据要处理,我们需要最大限度地提高检测任何类型内存损坏的机会PHP为此构建的方式,我强烈建议使用LLVMAddressSanitizer(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在模糊器运行之前尝试删除任何不必要地阻塞进程的条件也是一个好主意.例如,与大多数语言一样,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",//虚假陈述ives"readline",//暂停"readline_callback_handler_install",//暂停"syslog",//spamssyslog"sleep",//暂停"usleep",//暂停"time_sleep_until",//暂停"time_nanosleep",//暂停"pcntl_wait",//暂停"pcntl_waitstatus",//暂停"pcntl_waitpid",//暂停"pcntl_sigwaitinfo",//暂停"pcntl_sigtimedwait",//暂停"stream_socket_recvfrom",//暂停"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");虽然一台机器既可以单独生成样本,但我选择了一个小组来加快速度我使用Proxmox和10个在IntelNUC上运行的Debian虚拟机,工作方式如下:节点0:示例构建,托管NFS共享。节点1-8:Fuzzing节点,从NFS共享中提取样本进行测试。节点9:“分类”节点:根据崩溃指标对崩溃样本进行分类。我创建了简单的原始shell脚本以在每个脚本上运行以执行这些职责,这些脚本可以在上面链接的github存储库中找到。0x05分析崩溃在几分钟内,模糊器生成了多个崩溃样本,一夜之间超过2,000个。通过按指令地址对崩溃进行排序,我能够确定所有2,000次崩溃都是由3个错误引起的。其中,2个显然无法利用(都是由于堆栈耗尽导致的OOM错误),但最后一个似乎是UAF!这是最小化崩溃的示例...此错误已在错误#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转载请注明原文地址。
