Sometimes developers forget that if something looks like a number and should be a number, it won’t always be one once it lands in a pentester’s hands. During another penetration test of the powerful data-annotation tool Computer Vision Annotation Tool (CVAT), I came across a classic but very dangerous Reflected Cross-Site Scripting (XSS) flaw.
The bug I found earned the official identifier CVE-2024-47064 and a GitHub security advisory (GHSA-hp6c-f34j-qjj7). The vulnerability was classified as High with a score of 8.1 (vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N). Let’s see how the lack of strict input type validation and proper output encoding allowed injecting malicious JavaScript directly into the victim’s browser.
TL;DR - Key takeaways
- What Went Wrong? (Endpoints and Assumptions)
- Proof of Concept
- What Is the Security Impact?
- How to Fix It? (Remediation)
What Went Wrong? (Endpoints and Assumptions)
CVAT (vulnerable versions include Server: 2.16.3, Core: 15.1.1, Canvas: 2.20.8, UI: 1.64.51) has API endpoints for managing Requests. In my tests I focused on two specific addresses:
A standard request uses a numeric identifier (an int) in place of <ID>. If we try to query the server for a nonexistent request (e.g. ID 2137), the server politely returns an error in HTML format:
/api/requests/<ID>(GET and POST methods)/api/requests/<ID>/cancel(POST method)
"There is no request with specified id: 2137"
"There is no request with specified id: <script>alert(1)</script>"
Proof of Concept
Seems harmless, right? But what if, instead of a number, we enter an arbitrary string? It turns out the application accepts it without flinching and reflects it directly in the response, without filtering HTML special characters!
Exploiting the fact that our string is reflected on the page, we can attempt to inject HTML/JavaScript. Instead of a number, we place a malicious payload in the URL path (properly URL-encoded so the server interprets it correctly):
The decoded value is simply: <img src=x onerror=print()>. What happens on the browser side?
The browser tries to render an image from the source x. Since no such image exists, the onerror event fires and obediently executes the print() function, popping up the system print dialog on the victim’s screen.
I achieved the same on the /cancel endpoint, using a payload that triggers a classic alert box on a mouseover event:
Reflected XSS — why does it work?
The server does not encode the output. Whatever we place in the URL as the ID parameter is pasted directly into the HTML of the response. The browser treats it as valid HTML and obediently executes the embedded script.
Decoded: <img src=x onerror=print()>
Effect: the browser executes print() - the print dialog
Event: onmouseover
Effect: alert('xss') on mouseover

What Is the Security Impact?
Although this is a Reflected XSS (requiring user interaction — the UI:R parameter in the CVSS vector, e.g. clicking a crafted link sent in a phishing email or messenger), its consequences are serious. That is also why the vector indicates high confidentiality (C:H) and integrity (I:H).
When an authenticated user (e.g. a project administrator) clicks such a link, the malicious script runs in the context of their trusted CVAT session. In this way an attacker can:
- Hijack session tokens or cookies — full account compromise.
- Perform actions with the victim’s privileges (e.g. modify annotations, add malicious accounts, delete projects).
- Redirect the victim to data-harvesting (phishing) pages.
CVSS 8.1, High
Despite the user-interaction requirement, the lack of required privileges (PR:N) and the high impact on confidentiality and integrity earned this vulnerability a score of 8.1 (High). A phishing link is all the attacker needs.
How to Fix It? (Remediation)
This bug is a reminder of how important two layers of defense against XSS are:
- Strict Input Validation: The server should check right at the start whether the supplied <ID> parameter is really an integer. If it is not, the request should be rejected immediately (returning, say, a 400 Bad Request), without processing its contents. It is always better to use whitelists of allowed data types.
- Context-Aware Output Encoding: This is the most important line of defense. Any user-supplied data, before being displayed in an HTTP response (an HTML context), must be properly encoded. HTML control characters must be converted to the appropriate entities:
<→<,>→>.
The Developer’s Golden Rule
When you send the user’s browser text that included parts of their own requests (e.g. error messages, search results), ALWAYS encode the output. The browser is a trusting beast — it will execute any code the server presents as valid HTML.

Summary
CVE-2024-47064 is a classic example of a Reflected XSS vulnerability, where a lack of input type validation and output encoding allowed injecting malicious JavaScript into the victim’s browser. The flaw in CVAT was rated CVSS 8.1 (High) due to the lack of required privileges and the high impact on confidentiality and integrity. It was responsibly disclosed and patched, but it remains an excellent lesson: never trust user input, and always encode your output.
Bibliography
The official NIST NVD entry describing the Reflected XSS vulnerability in CVAT, rated CVSS 8.1.
The security advisory published on GitHub by the CVAT team, containing details of the XSS vulnerability and information about the fix.
The OWASP compendium on Cross-Site Scripting attacks — types, vectors, defense mechanisms, and examples.
The OWASP Top 10 category covering injection attacks, including Cross-Site Scripting (XSS), as one of the most common web application security threats.
The open-source repository of the CVAT data-annotation tool for machine learning and Computer Vision projects.

