這一題是最近結束的 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
兩個參數,輸出是 httpclient
或 cache
。
我們來探討一下,如果走「新請求」的路徑,應該要發生什麼事情。
假設我們要發請新請求,有兩種做法:
我們注意到 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
) 控制。我們在下一篇當中,會來看看如何從物件初始化來打這題目。