We recently ran a web app pen test and found a flaw that proves old vulnerabilities are still alive and well in modern apps.
The application we were testing looked well-built. A slick Single Page Application (SPA) with OpenID Connect (OIDC) authentication — the kind of setup you’d expect to be airtight. But one small oversight left the application exposed.
A textbook DOM (Document Object Model) based Cross-Site Scripting (XSS) bug was sitting quietly in the code, waiting to be triggered. And once it was, it gave us everything we needed to steal a user’s authentication tokens. With those tokens in hand, we could log in as that user, bypassing passwords entirely.
If this sounds like something that should have been left behind in the early 2000s, you’re right — but the truth is, old vulnerabilities never really go away. They resurface when assumptions are made and baseline protections are missed.
In this blog, we’ll walk through exactly how the exploit worked, why it’s still relevant in 2025, and the practical steps you can take to make sure it doesn’t happen on your watch.
Inside the App: Where the Weakness Lurked
On the surface, the client’s application ticked all the right boxes. It was a modern Single Page Application (SPA), designed for speed and a smooth user experience. Authentication was handled through OpenID Connect (OIDC), a trusted and widely used protocol.
Once logged in, the application stored the user’s authentication tokens in the browser’s sessionStorage — a temporary storage area that keeps data for as long as the tab is open and is fully accessible to JavaScript.
At first glance, this might seem harmless. sessionStorage clears itself when the browser closes, and unlike cookies, it doesn’t automatically get sent with every request. Many developers see it as a safe and convenient option for storing tokens in SPAs.
But there’s a catch. sessionStorage is fully accessible to JavaScript running in the browser. If an attacker manages to inject their own script — through a vulnerability like Cross-Site Scripting (XSS) — they can read those tokens directly. No extra hurdles, no password guessing, no brute-force attempts.
In OIDC, an access token lets you interact with protected APIs on behalf of the user, while an ID token proves who that user is. If stolen, they can be used to impersonate the victim, access their data, and perform any action they’re allowed to do — until the tokens expire. In some setups, that could mean hours of access. In poorly configured ones, it could mean much longer.
In this case, the combination of storing valuable tokens in sessionStorage and a hidden XSS vulnerability created the perfect conditions for a full account takeover. All it needed was the right payload to bring the problem to life.
From Flaw to Full Takeover: How the XSS Script Attack Unfolded
Once we spotted the risky token storage, the next step was to see if it could be exploited. It didn’t take long to find the way in.
While testing different inputs, we discovered that the application was taking what we typed and inserting it directly into the DOM without proper checks. Specifically, it used Aurelia’s innerhtml.bind feature — a method that injects raw HTML into the DOM. When used with untrusted input, this can introduce a serious security risk — including the potential for cross-site scripting (XSS) attacks — as it allows an attacker to inject and execute arbitrary HTML or JavaScript code in the user’s browser.
Here’s how the attack came together:
1. Finding the opening – We entered crafted text into an input field, knowing the app would display it back to us.
3. Reading the tokens – The JavaScript accessed sessionStorage and pulled out the OIDC tokens stored there.
4. Sending the tokens out – The script sent the stolen tokens to a server we controlled.
5. Logging in as the victim – With those tokens, we could impersonate the user, bypassing the need for their password entirely.
This chain of events is a classic example of how a single weak point — in this case, stored XSS — can be the first domino that knocks over the rest of your security controls.
Why Old Bugs Still Bite in 2025
Cross-Site Scripting has been around for decades, yet it still shows up in modern applications.
Here’s why:
- False sense of security from modern frameworks: Tools like Aurelia, React, or Angular can give the impression that vulnerabilities are handled automatically. But unsafe coding patterns — like injecting raw HTML from user input — can still slip through.
- Convenience over caution: Storing tokens in sessionStorage is quick and easy for developers. But if an attacker injects malicious JavaScript, those tokens are instantly exposed.
- Missing browser-level protections: Many applications skip key security headers such as Content Security Policy (CSP). While CSP wouldn’t stop the XSS bug itself, it could make exploitation much harderby blocking inline scripts or preventing data from leaving the site.
- Why Manual Testing Still Matters: Modern scanners can catch a lot, but not everything. In this case, the vulnerability wouldn’t have been picked up by an automated tool. It took a manual penetration test and careful code review to spot how the application handled input. With a properly configured Content Security Policy (CSP), the exploit would also have been much harder to pull off — inline scripts would have been blocked, forcing an attacker to compromise an allowed script source instead.
- Security fundamentals are important: Input validation, output encoding, secure data storage, and browser protections remain essential. Skipping them, even in a cutting-edge application, leaves the door wide open.
Shutting the Door on XSS and Token Theft
Stopping this kind of attack isn’t about finding a single silver bullet. It’s about layering defences so that even if one measure fails, others are there to protect you.
1. Fix the XSS
- Validate and sanitise all user input on the server before it’s processed.
- Encode output so any potentially dangerous characters are displayed as text, not executed as code.
- Avoid risky methods like innerhtml.bind when handling untrusted data. Use safer alternatives such as text.bind.
2. Store Tokens More Securely
Prefer secure cookies with the HttpOnly, Secure, and SameSite flags — making them inaccessible to JavaScript.
- Consider storing tokens in memory rather than in persistent browser storage.
3. Add Security Headers
- Implement a strong Content Security Policy (CSP) to restrict script execution and block data exfiltration to unknown domains.
4. Test Regularly
- Schedule regular web app penetration testing to identify vulnerabilities before attackers do.
A single overlooked coding choice was all it took to open the door in this case study. By combining secure coding practices, strong browser protections, and ongoing testing, you make that door much harder to find — and even harder to force open.
Lessons Cyber Security Leaders Can’t Ignore
This case study isn’t just about one vulnerable application — it’s a reminder of how quickly small oversights can escalate into full-scale compromise. For Cyber Security leaders, the value lies in recognising where the gaps appear and ensuring the right controls are in place before an attacker has the chance to exploit them.
Key points to take away:
- Legacy vulnerabilities still matter – An attack method’s age does not reduce its potential impact. Given the right circumstances, even decades-old flaws can be weaponised.
- Token storage decisions are critical – Poor storage choices can turn a single coding flaw into a direct route to account takeover.
- Frameworks don’t guarantee safety – Modern development tools reduce some risks but won’t protect against insecure practices introduced by the code itself.
- Defence in depth works – Combining secure coding, strict token handling, and strong browser protections creates multiple barriers that attackers must bypass.
- Testing should reflect reality – Simulate realistic, chained attack paths such as XSS leading to token theft, not just isolated vulnerabilities.
By focusing on these principles, leaders can better prepare their teams and their applications for the threats that persist — even in today’s most modern environments.
Modern Apps, Old Mistakes:
Even the most up-to-date applications can fall to vulnerabilities we’ve known about for decades. The difference between a secure system and a compromised one often comes down to attention to the fundamentals.
Don’t wait for an attacker to find the gaps in your defences. Let our experts uncover them first with our web application penetration testing services.
Contact Equilibrium Security on 0121 663 0055 or email enquiries@equilibrium-security.co.uk to discuss how penetration testing can strengthen your security posture.
Ready to achieve your security goals? We’re at your service.
expertise to help you shape and deliver your security strategy.