iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 7
0
Security

CTF30系列 第 7

CTF 6: Baby Cake (Web, HITCON CTF 2018)

解說

這一題是最近結束的 HITCON CTF 2018 裡面,第一個出現的 Web 題。

Get the shell plz!!!!! 13.230.134.135

Author: orange

Hint: This is not related to SSRF

(看了一天之後還是沒解出來)

偵查階段

我們開啟該頁面,會看到這個頁面。其原碼包的連結放在頁面上。
這頁面很單純,送出網址後會把你導向到那個頁面去。

看起來還是用伺服器的身份去發請求的。不過有趣的是:如果用 Putsreq 或自己的伺服器實驗的話,會發現他把你的 Header 給一五一十的轉過去了。

我們來看看原碼。這個是一包使用 CakePHP 架的題目,版本定在 PHP 5。

    private function back() {
        return $this->render('pages');
    }

    private function _cache_dir($key){
        $ip = $this->request->getEnv('REMOTE_ADDR');
        $index = sprintf('mycache/%s/%s/', $ip, $key);
        return CACHE . $index;
    }

    private function cache_set($key, $response) {
        $cache_dir = $this->_cache_dir($key);
        if ( !file_exists($cache_dir) ) {
            mkdir($cache_dir, 0700, true);
            file_put_contents($cache_dir . "body.cache", $response->body);
            file_put_contents($cache_dir . "headers.cache", serialize($response->headers));
        }
    }

    private function cache_get($key) {
        $cache_dir = $this->_cache_dir($key);
        if (file_exists($cache_dir)) {
            $body   = file_get_contents($cache_dir . "/body.cache");
            $headers = file_get_contents($cache_dir . "/headers.cache");

            $body = "<!-- from cache -->\n" . $body;
            $headers = unserialize($headers);
            return new DymmyResponse($headers, $body);
        } else {
            return null;
        }
    }

    public function display(...$path) {
        $request  = $this->request;
        $data = $request->getQuery('data');
        $url  = $request->getQuery('url');
        if (strlen($url) == 0)
            return $this->back();

        $scheme = strtolower( parse_url($url, PHP_URL_SCHEME) );
        if (strlen($scheme) == 0 || !in_array($scheme, ['http', 'https']))
            return $this->back();

        $method = strtolower( $request->getMethod() );
        if ( !in_array($method, ['get', 'post', 'put', 'delete', 'patch']) )
            return $this->back();


        $headers = [];
        foreach ($request->getHeaders() as $key => $value) {
            if (in_array( strtolower($key), ['host', 'connection', 'expect', 'content-length'] ))
                continue;
            if (count($value) == 0)
                continue;

            $headers[$key] = $value[0];
        }

        $key = md5($url);
        if ($method == 'get') {
            $response = $this->cache_get($key);
            if (!$response) {
                $response = $this->httpclient($method, $url, $headers, null);
                $this->cache_set($key, $response);
            }
        } else {
            $response = $this->httpclient($method, $url, $headers, $data);
        }

        foreach ($response->headers as $key => $value) {
            if (strtolower($key) == 'content-type') {
                $this->response->type(array('type' => $value));
                $this->response->type('type');
                continue;
            }
            $this->response->withHeader($key, $value);
        }

        $this->response->body($response->body);
        return $this->response;
    }

我們看 display()。當你用 GET 請求頁面時,$_GET['url'] 的內容會被拿出來解析。如果它長得像 URL (開頭是 http https,正常的 method) 的話,就會到下一步。
下一步即是把 url 給轉 md5,之後比對看看本地端 cache 中有沒有以此爲名 (呼叫 cache_get) 的快取。若有則拿出來給客戶端用,若無則重做一次請求,並把結果給除存到快取中。

當時在看這份時,因為已知 PHP 版本爲 5,且 phar 模組有開啟,故我當時猜測是可以透過 phar:// 和拿快取時 cache_get 中,unserialize() 的呼叫來做一些事情。PHP 的反序列化沒有像 Python 那麼可以被濫用,但還是要小心帶有 magic method 的 class。

如果以 Source-Sink [1] [2] 的概念來看 display() 的話,會發現它的 Source 是 url data 兩個參數,輸出是 httpclientcache

我們來探討一下,如果走「新請求」的路徑,應該要發生什麼事情。

HTTP request

假設我們要發請新請求,有兩種做法:

  1. 找一個一定不會有人用的網頁
  2. 走不是 GET 的 HTTP method

我們注意到 data 會被傳入 $this->httpclient(),來看看這裡面是啥。

private function httpclient($method, $url, $headers, $data) {
    $options = [
        'headers' => $headers,
        'timeout' => 10
    ];

    $http = new Client();
    return $http->$method($url, $data, $options);
}

這樣會導向到:

public function post($url, $data = [], array $options = [])
{
    $options = $this->_mergeOptions($options);
    $url = $this->buildUrl($url, [], $options);

    return $this->_doRequest(Request::METHOD_POST, $url, $data, $options);
}
protected function _doRequest($method, $url, $data, $options)
{
    $request = $this->_createRequest(
        $method,
        $url,
        $data,
        $options
    );

    return $this->send($request, $options);
}
protected function _createRequest($method, $url, $data, $options)
{
    ...
    $request = new Request($url, $method, $headers, $data);
    $cookies = isset($options['cookies']) ? $options['cookies'] : [];
    /** @var \Cake\Http\Client\Request $request */
    $request = $this->_cookies->addToRequest($request, $cookies);
    ...
    return $request;
}

protected function _createRequest($method, $url, $data, $options)
{
    ...
    return $request;
}

我們在 _createRequest 裡面,會看到它初始化了 Cake/Http/Client/Request 物件。基本上這變數可以被 $data (GET data) 控制。我們在下一篇當中,會來看看如何從物件初始化來打這題目。


上一篇
CTF 5: pwn2 (Part 2) (Pwn, TamuCTF2018)
下一篇
CTF 6: Baby Cake (Part 2) (Web, HITCON CTF 2018)
系列文
CTF3030
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言