July 16, 2026
Stored XSS via SVG File Upload Leading to Account Takeover in an LMS Platform
A few days ago, I was performing a security assessment on a foreign Learning Management System (LMS) platform. The application was…

By Hossein Zarei
9 min read
A few days ago, I was performing a security assessment on a foreign Learning Management System (LMS) platform. The application was developed using a RESTful API architecture, so I initially focused my testing efforts on analyzing different API endpoints.
Considering that the application's business logic was relatively limited and it was mainly designed for educational purposes, I did not find any significant vulnerabilities within the API layer.
After completing the API assessment, I decided to analyze other parts of the application for common security issues. One of the areas that always deserves attention during web application testing is the file upload functionality, especially when users are allowed to change their profile pictures.
During further investigation, I discovered that users could upload their profile images through a dedicated upload endpoint. This functionality became the starting point for identifying the main vulnerability discussed in this report.
Important Note 1
In many modern web applications, uploaded files are not directly executed on the server like traditional executable files. Because of this, obtaining a Web Shell through File Upload vulnerabilities is less common compared to older applications.
Modern frameworks such as Node.js, Django, Laravel, Spring Boot, and similar technologies usually provide better default security practices around file handling.
However, this behavior is not related to the programming language or framework itself. It completely depends on how developers implement file upload functionality. If uploaded files are stored or served without proper validation and security controls, exploitation can still be possible even in modern frameworks.
Therefore, when testing file upload functionality in modern applications, it is important not only to check for executable file uploads but also to analyze files such as SVG and HTML for potential Stored XSS vulnerabilities.
Important Note 2
Vulnerabilities related to uploading SVG files are usually categorized as Stored Cross-Site Scripting (Stored XSS).
The severity of this vulnerability highly depends on how the application manages user sessions and authentication data.
If the application uses HttpOnly Cookies to store session information, JavaScript executed through XSS cannot directly access those cookies.
However, if authentication data such as JWT tokens are stored inside LocalStorage or SessionStorage, JavaScript executed through XSS can directly access these tokens. Under the right conditions, this can lead to Account Takeover, significantly increasing the overall impact of the vulnerability.
Vulnerability Description
Within the user profile settings, users had the ability to change their profile picture.
For example, the profile page was available at:
https://app.target.com/student/https://app.target.com/student/After uploading a normal image, the file was stored at:
https://app.target.com/uploads/avatars/pic.jpghttps://app.target.com/uploads/avatars/pic.jpgImportant Point
In this scenario, the uploaded file was served from the same Origin as the main application.
This detail is extremely important because if an SVG file is rendered without proper sanitization or security restrictions, any JavaScript code inside that SVG file will execute under the same Origin as the application.
In such conditions, the malicious script can potentially access resources belonging to the same Origin, including the DOM, LocalStorage, and other JavaScript-accessible data.
In this application, JWT tokens were stored inside LocalStorage. Therefore, if XSS execution was successful, these tokens could potentially be accessed and transmitted by the attacker.
On the other hand, if uploaded files are served from a completely separate Origin (for example, cdn.target.com or usercontent.targetcdn.com), JavaScript access to sensitive resources belonging to the main application becomes significantly restricted, reducing the overall impact of the vulnerability.
The Origin where the uploaded SVG is served plays a critical role in determining the real impact of SVG-based XSS vulnerabilities.
After identifying the endpoint responsible for profile image uploads, I intercepted the upload request using Burp Suite and replaced the normal image file with an SVG file containing the desired payload.
The server accepted the file without performing any content validation or SVG sanitization and returned the uploaded file path in the response.
Additionally, by right-clicking on the profile image and selecting Open image in new tab, I was able to access the URL of the uploaded SVG file.
After opening the same URL in the browser, the JavaScript payload embedded inside the SVG file was executed.
At this stage, it was confirmed that:
- The SVG file was stored without any sanitization.
- The file was directly rendered by the browser.
- JavaScript code inside the SVG file was executed.
- The vulnerability was a Stored Cross-Site Scripting (Stored XSS) issue.
Since user profile pictures were visible to other users, anyone viewing the malicious SVG file would also trigger the JavaScript payload in their browser.
Therefore, this vulnerability was not limited only to the attacker's account and could affect other users of the platform as well.
This behavior is one of the main characteristics of Stored XSS vulnerabilities: the malicious payload is stored by the application and executed whenever another user accesses the affected content.
Proof of Payload Execution
In the following image, you can see that the payload was successfully embedded inside the SVG file. After opening the uploaded file, the JavaScript code was executed without any restriction.
Analyzing Authentication Data Storage
After confirming that the XSS vulnerability was successfully executed, the next step was to analyze how user sessions were managed.
In this application, the authentication mechanism was based on JWT (JSON Web Token).
Therefore, I first investigated where the JWT token was stored.
Using the browser Developer Tools, I discovered that the application stored the JWT token inside LocalStorage.
This was a critical finding because, unlike Cookies with the HttpOnly attribute, data stored in LocalStorage can be directly accessed by JavaScript.
As a result, any JavaScript code executed through an XSS vulnerability could read this token and send it to a server controlled by an attacker. This is exactly why Stored XSS vulnerabilities can potentially lead to Account Takeover in such scenarios
Testing JavaScript Access to JWT
Before creating the final payload, I first verified through the browser console whether JavaScript had access to the JWT token stored inside LocalStorage.
As shown in the image below, the token was accessible without any restriction.
Preparing a Server to Receive JWT Tokens
To fully demonstrate the impact of the vulnerability, I prepared a personal VPS server capable of receiving and logging tokens sent from the victim's browser.
For this purpose, I created a simple receiver on my server that accepted incoming requests and stored the received JWT token inside a text file.
Before using the final payload inside the SVG file, I first tested this mechanism separately.
From the browser console, I sent a fetch() request to my own server to verify that the communication between the browser and the external server was working correctly.
The purpose of this step was only to confirm that the receiving infrastructure was functioning properly, and the actual Stored XSS payload had not been used yet.
The result of executing the test fetch() request can be seen in the image below.
Successful JWT Token Retrieval
After executing the test request, reviewing the server logs confirmed that the JWT token was successfully sent from the browser and stored inside a text file on the server.
This confirmed that:
- The JavaScript code was executed successfully.
- Access to LocalStorage was possible.
- Communication with the attacker-controlled server was established.
- Sensitive information could be extracted without any additional restrictions.
In the image below, you can see that the JWT token was successfully stored inside the log.txt file on my personal server.
Receiver Used
To log the received information, I used a very simple Receiver that stored the transmitted value inside a text file.
An example of this code is shown below:
<?php
$token = $_GET['token'] ?? '';
if ($token) {
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$time = date('Y-m-d H:i:s');
file_put_contents(
'log.txt',
"[$time] IP: $ip | Token: $token" . PHP_EOL,
FILE_APPEND | LOCK_EX
);
http_response_code(200);
}
?><?php
$token = $_GET['token'] ?? '';
if ($token) {
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$time = date('Y-m-d H:i:s');
file_put_contents(
'log.txt',
"[$time] IP: $ip | Token: $token" . PHP_EOL,
FILE_APPEND | LOCK_EX
);
http_response_code(200);
}
?>The purpose of showing this code is only to demonstrate how the received data was logged for Proof of Concept (PoC) purposes.
In a real-world scenario, an attacker could use any other mechanism to receive and process the stolen data.
Executing the Final Payload
After confirming that the Receiver was working correctly and that communication between the browser and my personal server was successfully established, I placed the final payload inside the SVG file and uploaded it again through the profile image upload endpoint.
After a successful upload, the SVG file was stored at the same location returned by the server in the response.
Every time this file was accessed through its public URL, the JavaScript code inside the SVG file was executed and automatically extracted the user's JWT token before sending it to my personal server.
The image below shows that the malicious SVG XSS file was successfully uploaded and the server returned the file path in the response.
After visiting the uploaded file URL (which was accessible to all users), the payload executed successfully and the user's JWT token was sent to my server.
Example file URL:
https://app.target.com/uploads/avatars/68c8a40cfbe71c166b6aee2f-1758021658314-t.svghttps://app.target.com/uploads/avatars/68c8a40cfbe71c166b6aee2f-1758021658314-t.svgImpact Validation
To verify that this vulnerability was not only exploitable against my own account, I created another test account.
Then, using the second account, I logged into the application and viewed the malicious SVG file that I had previously uploaded.
Immediately after opening the file, the payload executed again, and this time the JWT token belonging to the second account was also sent to my personal server.
This test confirmed that the vulnerability was not limited to the attacker's account. Any user who viewed the malicious SVG file would be affected by the execution of the payload.
After receiving the JWT token belonging to the second account, I was able to send authenticated requests and access the account without knowing the username or password, using only the stolen token.
This demonstrated that the complete attack chain could be executed as shown in the following diagram:
Therefore, the impact of this vulnerability was not limited to simply executing a JavaScript alert. When authentication information is stored inside LocalStorage and additional security controls are missing, this type of vulnerability can potentially lead to full Account Takeover.
Such attack chains can significantly increase the severity of a vulnerability during security assessments, because a Stored XSS issue can become a complete compromise of user accounts depending on the application's authentication architecture.
To prevent this vulnerability, the application should address both the insecure file upload handling and the unsafe storage of authentication tokens.
First, uploaded files should be properly validated and sanitized before being stored. In this scenario, allowing SVG files without sanitization enabled JavaScript execution inside the uploaded file. If SVG uploads are not required, they should be blocked. Otherwise, SVG files should be processed and sanitized to remove active content before being served to users. Additionally, user-uploaded files should ideally be served from a separate Origin to prevent them from executing within the security context of the main application.
Second, sensitive authentication data such as JWT tokens should not be stored in LocalStorage. Since LocalStorage is accessible by JavaScript running on the same Origin, any successful XSS vulnerability can expose stored tokens and potentially lead to Account Takeover. Using secure HttpOnly cookies for session management can significantly reduce this risk because JavaScript cannot directly access these cookies.
In this case, the vulnerability impact was increased by the combination of three factors:
- Unsafe SVG file handling.
- Serving uploaded files from the same Origin as the main application.
- Storing authentication tokens inside LocalStorage.
Fixing only one of these issues would reduce the risk, but addressing the complete attack chain is necessary to prevent similar Account Takeover scenarios.
Remediation
To prevent this vulnerability, the application should properly handle uploaded files and prevent the execution of untrusted JavaScript content.
In this scenario, the main issue was that SVG files were accepted without proper sanitization and were served from the same Origin as the main application. Uploaded SVG files should either be blocked if they are not required, or sanitized before being stored and displayed to users.
Additionally, security-sensitive data stored on the client side should be carefully considered. While storing tokens in browser storage is a design choice used by many modern applications, developers should understand that any successful XSS vulnerability can potentially access data available to JavaScript, including LocalStorage contents.
Therefore, preventing XSS through proper input handling, output encoding, file validation, and additional security controls such as Content Security Policy (CSP) is critical to reducing the impact of such attacks.
In this case, the combination of unsafe SVG handling and same-origin file serving allowed a Stored XSS vulnerability to escalate into a potential Account Takeover scenario.