On a recent web application pentest I was given two different subdomains in scope for the engagement. One of the subdomains had only 1 functionality available for testing (as the rest were out-of-scope) which contained an input field for commenting and the ability to attach a file alongside it. I tested for various vulnerabilities on the comment field and only confirmed Insufficient Input Validation to be valid – which is pretty much an informative level finding. I spent a lengthy amount of time trying to get Stored XSS but to no avail! The security was very well implemented there. I did, however, manage to find a Self-XSS on the upload feature. By attaching a filename as such:

“><img src=x onerror=alert(document.domain)>.txt

This value would reflect on the page after submitting and the JavaScript would execute. A nice way of getting XSS in my opinion – but IMPACT is what matters as a Penetration Tester. Realistically, clients want their application/s tested in the hope to unveil any High or Critical vulnerabilities before the bad guys do so they can provide an instant fix. Nobody wants their applications at high risk and customer data vulnerable to theft therefore it’s important as a Bug Bounty hunter or Penetration Tester to look for ways to increase the impact of whatever vulnerability you’ve found. Reflecting back to this point – the Self XSS is another informative/low-level finding as there’s no likelihood of this ever occurring from an ordinary user. I tried chaining this with CSRF to increase the impact however again – no success.

When I eventually moved onto the other subdomain – I struck gold.

Tons of functionality, tons of input fields, and tons of Stored XSS vulnerabilities! I found so many cases of XSS I lost count at one point. At this stage though, it was still considered a Medium severity and I wanted a High at least from this. I thought about different methods of chaining XSS and I considered two methods which may work:

Session Hijacking

Session Fixation

Both cases essentially stem from session management and are based around how the application identifies the users’ session. If you’re unaware – Hijacking and Fixation are similar in a sense that the end result is hijacking a user’s account, however, these are still two separate vulnerabilities. Feel free to read more about it on https://owasp.org/www-community/attacks/Session_fixation.

If the application has appropriate cookie security applied, then these attacks are rendered useless. The web app I was testing against had several cookies when logged in but the only one that was actually being checked for authentication was PHPSESSID. This did not have Secure or HttpOnly applied which ticks the checklist for a Session Hijack. After changing the payload around a little I managed to grab the PHPSESSID successfully and confirmed Session Fixation to be valid. What’s next? Hijacking the accounts!

The client required authenticated testing to be done so I was given a typical admin account and a low-level user account. The low-level account had a considerably less number of actions available to be performed but I still found a different way of achieving Stored XSS and affecting all users.

In the end, I was successfully able to switch to the admin’s session via Session Hijacking and Fixation.

At this point the finding was:

Stored XSS > Session Hijacking = High severity.

Due to the nature of “Session” tokens – the session was immediately nullified after the original admin user logged out, so there was no form of persistence yet. I wanted to increase the severity to a Critical, so I kept thinking about how I’d be able to chain this further and increase the impact. There was a “Change Password” feature, but it required you to enter the current password before applying a new one. I couldn’t find a way to bypass this so I nearly gave up and almost concluded Account Takeover wouldn’t be possible.

However – that night before I slept, I had been thinking about a way to increase the severity and I remembered there was a feature which allowed you to manage all user accounts, so there might be a way to manage the current administrator account via this way and maybe even change the assigned e-mail address to a new one without confirmations.

The next morning came, I resumed the test, and my thoughts became reality.

I was able to change the password without knowing the current one. This was an intended piece of functionality although it was a flawed design. One area required you to enter the current password, whereas another area on the application did not ask for a current password. Not only this, but it was, therefore, possible to change the privilege level to any account on the system, so I made my low-level restricted user to an administrator with full permissions on the dashboard.

The roadmap now looked like this:

Stored XSS > Session Hijacking > Account Takeover + Privilege Escalation = Critical severity.

An interesting thing to note also is that I eventually managed to get Stored XSS on the initial subdomain via the current subdomain. The comment thread on subdomain A interacted with subdomain B, and therefore the administrator was able to reply on subdomain B – I replied with

“><img src=x onerror=alert(window.location)>

Going back to visit this on subdomain A as the low-level user – the alert box popped up confirming another case of Stored XSS!


The moral of this blog is to encourage folk to be security-minded around their cookies! Especially session tokens. This client had effective security mechanisms in place overall, but one slip-up of missing cookie security and input validation on subdomain B caused destruction!

There’s a lot more to cookies than what meets the eye and what was discussed in this blog, I highly recommend reading up more about it here:



Written by Penetration Tester Hammad Amjad.