新聞中心
為了清楚和簡潔起見,下面引用的代碼已精煉為最簡單的形式。實(shí)際用于Fuzzing測試的完整版本可以在此處找到。

創(chuàng)新互聯(lián)憑借專業(yè)的設(shè)計團(tuán)隊(duì)扎實(shí)的技術(shù)支持、優(yōu)質(zhì)高效的服務(wù)意識和豐厚的資源優(yōu)勢,提供專業(yè)的網(wǎng)站策劃、成都網(wǎng)站設(shè)計、成都做網(wǎng)站、網(wǎng)站優(yōu)化、軟件開發(fā)、網(wǎng)站改版等服務(wù),在成都10年的網(wǎng)站建設(shè)設(shè)計經(jīng)驗(yàn),為成都成百上千家中小型企業(yè)策劃設(shè)計了網(wǎng)站。
https://github.com/Rewzilla/domatophp
最近,我一直在對PHP解釋器進(jìn)行Fuzzing,我探索了許多工具和技術(shù)(AFL,LibFuzzer,甚至是自定義的Fuzzing引擎),但是最近我決定嘗試Domato。對于那些不知道的人,Domato是基于語法的DOM Fuzzer,旨在從復(fù)雜的代碼庫中挖掘復(fù)雜的bug。它最初是為瀏覽器而設(shè)計的,但是我認(rèn)為我可以將其用于Fuzzing PHP解釋器。
https://github.com/googleprojectzero/domato
0x01 分析上下文語法
為了使用Domato,必須首先使用上下文無關(guān)的語法來描述語言,CFG只是一組定義語言構(gòu)造方式的規(guī)則。例如,如果我們的語言由以下形式的句子組成:
- [name] has [number] [adjective] [noun]s.
- [name]'s [noun] is very [adjective].
- I want to purchase [number] [adjective] [noun]s.
這些變量中的每一個都可以采用幾種形式,例如:
- Names: alice, bob, eve
- Numbers: 1, 10, 100
- Adjectives: green, large, expensive
- Nouns: car, hat, laptop
那么上下文無關(guān)文法可能看起來像...
然后Domato使用上下文無關(guān)文法生成符合語言規(guī)則的隨機(jī)組合。
- 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.
可以想象,通過將每個規(guī)則分解為更多子規(guī)則,我們可以開始定義更復(fù)雜的語言,而不僅僅是簡單的搜索/替換。實(shí)際上,Domato還提供了一些內(nèi)置函數(shù),用于限制遞歸并生成基本類型(int,char,string等)。
例如,以下Domato語法,該語法生成偽代碼...
將其送入Domato會產(chǎn)生以下結(jié)果...
- 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; } };
這非常適合Fuzzing解釋器,因?yàn)槊總€樣本都是不同的,并且仍然保證其在語法上是有效的!
0x02 列舉Attack Surface
然后,下一步就是將PHP語言描述為CFG。如果有興趣查看完整的CFG,請下載PHP源代碼,然后查看Zend/zend_language_parser.y。
但是,我對Fuzzing特定的代碼模式更感興趣。因此,我實(shí)現(xiàn)了CFG,使其僅使用“Fuzzing”參數(shù)生成對內(nèi)置函數(shù)和類方法的調(diào)用。為此,我們需要一個函數(shù),方法及其參數(shù)的列表。
有兩種獲取此數(shù)據(jù)的方法。最簡單的方法是使用PHP的內(nèi)置Reflection類來遍歷所有已定義的函數(shù)和類,從而構(gòu)建一個列表。
以下代碼對所有內(nèi)部PHP函數(shù)進(jìn)行了演示...
這會產(chǎn)生類似如下代碼:
- 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 ...
但是,此問題在于此列表不包含類型信息。ReflectionParameter類包含一個getType方法,但是對于大多數(shù)函數(shù)而言,它目前似乎不起作用。:(也許這是一個bug?很難說。無論如何,擁有類型信息將使我們的Fuzzing工作變得更加有效,因此值得花時間去尋找另一種獲取該數(shù)據(jù)的方法。
https://www.php.net/manual/en/reflectionparameter.gettype.php
為了解析出我們需要的東西,PHP的文檔通常相當(dāng)不錯,可以在此處將其作為單個壓縮的HTML文檔下載。經(jīng)過數(shù)小時的辛苦編寫正則表達(dá)式后,我能夠?qū)⑵浣馕鰹榭捎玫暮瘮?shù),方法和參數(shù)類型列表。我將其留給讀者練習(xí),但是最終產(chǎn)品(以CFG形式)看起來像這樣……
https://www.php.net/distributions/manual/php_manual_en.html.gz
0x03 設(shè)置Domato
為了使Domato使用我們的語法,我們還需要定義一些基本組件,例如:
經(jīng)過大量的調(diào)整和調(diào)整后,我的配置最終看起來像這樣……
我們還需要定義一個語法將被應(yīng)用到的模板。該模板將設(shè)置環(huán)境,實(shí)例化以后可能使用的所有對象,然后運(yùn)行每條線程。我的模板看起來像這樣...
最后一步是復(fù)制和修改Domato的generator.py文件。我發(fā)現(xiàn)只需進(jìn)行以下更改就足夠了...
· 第55和62行:將根元素更改為“
· 第78行:引用我自己的“ template.php”
· 第83行:在“ php.txt”中引用我自己的語法
· 第134行:將輸出名稱和擴(kuò)展名更改為“
然后,應(yīng)該能夠生成有效的Fuzzing輸入!
- andrew@thinkpad ~/domato/php % python generator.py /dev/stdout
- Writing a sample to /dev/stdout
- $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) { }
0x04 開始Fuzz
現(xiàn)在我們要處理的數(shù)據(jù)非常多,我們需要以一種最大化檢測任何類型的內(nèi)存損壞的機(jī)會的方式構(gòu)建PHP。為此,我強(qiáng)烈建議使用LLVM Address Sanitizer(ASAN),它將檢測任何無效的內(nèi)存訪問,即使它不會立即導(dǎo)致崩潰。
https://github.com/google/sanitizers/wiki/AddressSanitizer
用ASAN編譯PHP,下載最新版本的源代碼在這里,并運(yùn)行以下命令...
https://www.php.net/downloads
- ./configure CFLAGS="-fsanitize=address -ggdb" CXXFLAGS="-fsanitize=address -ggdb" LDFLAGS="-fsanitize=address"
- make
- make install
在Fuzzer運(yùn)行之前,嘗試消除不必要地阻礙該過程的任何條件也是一個好主意。例如,像大多數(shù)語言一樣,PHP具有一個sleep()函數(shù),該函數(shù)接受一個整數(shù)參數(shù),并僅等待幾秒后才能繼續(xù)。用較大的值(例如INT_MAX)調(diào)用此函數(shù)將迅速占用較大的簇。
還有一些函數(shù)可能會導(dǎo)致進(jìn)程合法地“崩潰”,例如posix_kill()或posix_setrlimit()。我們可能希望從測試語料庫中刪除這些內(nèi)容,以減少誤報的數(shù)量。
最后,由于PHP文檔中列出的許多函數(shù)和類實(shí)際上在核心安裝中不可用(而是從擴(kuò)展中提供),因此我們不妨從資料集中刪除其中的一些函數(shù)和類,以避免浪費(fèi)時間調(diào)用不存在的代碼。
最后,經(jīng)過一番試驗(yàn),我確定了以下清單...
- $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"
- );
盡管一臺機(jī)器既可以單獨(dú)生成樣本,但我還是選擇了一小組來加快處理速度。我使用了在Intel NUC上運(yùn)行的 Proxmox 和10個 Debian VM,其工作如下:
· 節(jié)點(diǎn)0:樣本生成,托管NFS共享。
· 節(jié)點(diǎn)1-8:Fuzzing節(jié)點(diǎn),從NFS共享中提取樣本進(jìn)行測試。
· 節(jié)點(diǎn)9:“分類”節(jié)點(diǎn):根據(jù)崩潰指標(biāo)對崩潰樣本進(jìn)行分類。
我創(chuàng)建了簡單的原始shell腳本以在每個腳本上運(yùn)行以執(zhí)行這些職責(zé),這些腳本可以在上面鏈接的github repo中找到。
0x05 分析Crashs
幾分鐘內(nèi),該Fuzzer就生成了多個崩潰樣本,一夜之間就生成了2,000多個。
通過根據(jù)崩潰的指令地址對崩潰進(jìn)行分類,我能夠確定所有2,000個崩潰都是3個錯誤造成的。其中,有2個顯然無法利用(兩個都是由于堆棧耗盡導(dǎo)致的OOM錯誤),但是最后一個似乎是UAF!這是最小化的崩潰示例...
此錯誤已在bug#79029中得到修復(fù),應(yīng)該包含在下一個版本中。在接下來的幾篇文章中,我將討論將其根本原因,實(shí)現(xiàn)任意代碼執(zhí)行的過程,以及在此過程中發(fā)現(xiàn)的一個巧妙的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如若轉(zhuǎn)載,請注明原文地址。
當(dāng)前名稱:用Domato通過Fuzzing對PHP進(jìn)行漏洞挖掘研究
鏈接地址:http://fisionsoft.com.cn/article/ccicjpo.html


咨詢
建站咨詢
