# How I Hacked Microsoft Teams and got $150,000 in Pwn2Own

Three chained flaws in Microsoft Teams 1.x (Electron): missing contextIsolation, AngularJS ng-init XSS via class wildcard, and PluginHost IPC property access yielding RCE.

- Payout: $150,000
- Severity: critical
- Program: Microsoft
- Credited: Masato Kinugawa
- Disclosed: 2023-07-31
- Write-up quality: 9/10
- Original: https://speakerdeck.com/masatokinugawa/how-i-hacked-microsoft-teams-and-got-150000-dollars-in-pwn2own
- Library page: https://bugbountylibrary.com/r/blog%3Aspeakerdeck-com-masatokinugawa-how-i-hacked-microsoft-teams-and-got-150000-dollars-in-pwn2own
- Tags: domain:desktop impact:rce kind:bounty platform:blog sev:critical tech:electron tech:nodejs technique:chained technique:source-review vuln:rce vuln:xss

**Target:** Electron desktop app (Teams 1.x) main window, chat message renderer, PluginHost renderer
**Root cause:** Main window ran with contextIsolation:false allowing prototype override to leak ipcRenderer; chat sanitizer's swift-* class wildcard let AngularJS ng-init expressions through; PluginHost IPC MEMBER_GET did property access without hasOwnProperty, exposing Function constructor in a no-sandbox preload context.
**Steps:**
1. Inject `Function.prototype.call` override to capture `__webpack_require__` and grab `ipcRenderer` from `./lib/sandboxed_renderer/api/exports/electron.ts`
2. Send chat message `<strong class="swift-x;ng-init:['eval(decodeURIComponent(...))'].forEach($root.$$childHead.$$nextSibling.app.$window.eval)">aaa</strong>` to trigger AngularJS expression eval
3. From XSS, call `ipc.invoke('calling:teams:ipc:initPluginHost',true)` to get PluginHost id
4. Chain `sendToRendererSync` with `ELECTRON_REMOTE_SERVER_REQUIRE` (slimcore), `MEMBER_GET` toString, `MEMBER_GET` constructor, then `FUNCTION_CALL` with `process.binding('spawn_sync').spawn({file:'cmd',args:['/k','start','calc'],...})`
**How found:** Started by reading Electron BrowserWindow webPreferences (contextIsolation:false, sandbox:true) and hypothesizing prototype override could leak Node APIs; then audited client-side sanitize-html class allow-list, noticed swift-* wildcard, recalled AngularJS ng-init via class attribute, and finally inspected PluginHost IPC listeners (ELECTRON_REMOTE_SERVER_MEMBER_GET) noticing missing hasOwnProperty check.
**Exploitation:** Attacker sends a crafted chat message; victim opens it, AngularJS evaluates the ng-init expression, JS navigates to attacker page which uses leaked ipcRenderer to drive PluginHost IPC and spawn cmd via process.binding('spawn_sync'), achieving RCE with no user interaction beyond opening the message.
**Escalation:** Chained three independent weaknesses: prototype-override leak of ipcRenderer, sanitizer wildcard bypass for AngularJS expression injection, and unchecked property access in PluginHost IPC to reach Function constructor in a no-sandbox preload context.
**Insight:** When an Electron app has contextIsolation:false, overriding Function.prototype.call can capture webpack require and leak ipcRenderer even with nodeIntegration:false; also, AngularJS evaluates class attributes matching /(([\w-]+)(?::([^;]+))?;?)/ as directives, so any sanitizer wildcard that permits arbitrary class characters enables ng-init XSS.
