JurryHurry

webxsssession-hijackingbots

A small law-firm site with a contact form nobody reads — except a clerk, every morning, who turns out to be a headless browser holding a logged-in session.

Spoiler-light notes — no flags, credentials or copy-paste exploits. Solve it yourself first.

Recon

A single port is open. ICMP is filtered, so the scan needs to skip host discovery. The web root answers with a redirect to a virtual hostname, which means the interesting content is name-based, not path-based — add the vhost locally and the site resolves properly.

Fingerprinting gives a reverse proxy in front of an application server, and a session cookie that is the obvious prize. Enumerating the pages finds the usual small-business set — a home page, a staff directory, a FAQ, a contact form, and a staff portal that redirects to a login.

The FAQ is the actual finding. One line says a clerk reviews new enquiries from the contact form every morning. That single sentence reframes everything: user-submitted input is going to be rendered in front of an authenticated browser, automatically, on a schedule. If that rendering is unsafe, we don’t need to attack the login at all — we can attack the reviewer.

What didn’t work

Worth recording, because it’s where most of the time went:

  • Common credentials on the staff login — all rejected identically.
  • NoSQL-style operator injection in the login body — the application refused the JSON shape outright, and the bracket-notation form failed too. The body parser clearly isn’t configured for nested objects.
  • Classic SQL injection — no change in response size or content.
  • Reflected template injection through the contact form — several template syntaxes were tried, and none appeared in the response. That’s the clue: the input isn’t reflected back to us, it’s stored for someone else. Re-testing for reflected output was the wrong test.

The bug

The contact form’s message field is stored and later rendered as raw markup on the internal review page, with no output encoding. That makes it stored cross-site scripting, and because the reviewer is an automated browser rather than a person, it needs no social engineering or user interaction to fire.

Two design decisions turn a scripting bug into a full account takeover:

  1. The reviewer’s session cookie isn’t HttpOnly, so the page’s own JavaScript can read it.
  2. The reviewer’s session is a normal, long-lived authenticated session with access to the staff area — not a scoped, short-lived service identity.

Exploitation

The whole chain is four steps:

  1. Stand up an HTTP listener on the attack host.
  2. Submit a contact message whose body is a script tag that ships the reader’s document cookie (and current URL) to that listener.
  3. Wait for the reviewer to process the queue. The script executes in the reviewer’s browser context, so the cookie that arrives is its cookie, not ours. It also reveals the internal address of the staff area, which is only reachable from inside the host.
  4. Replay the captured cookie against the staff portal through the proxy. The session is accepted and the protected page renders — including the flag.

No password was guessed and no vulnerability in the login itself was needed. We borrowed a session by making the application render our input for someone else.

Why it worked

  • Stored, not reflected. Blind XSS is easy to conclude “not vulnerable” on if you’re only looking at your own response. The signal here was a form with no visible output at all — that’s exactly when to reach for the stored class.
  • Unencoded output in an internal view. The review page was treated as trusted because “only the clerk sees it”. That’s backwards: the clerk is the highest-privileged reader.
  • Cookie scope. Missing HttpOnly is what converted “I can run script as an admin” into “I have the admin’s session”.
  • Automation improves reliability. A human might not click the link. A bot runs every morning, on every submission, forever.

Takeaways

  • A FAQ or marketing line describing an internal process is reconnaissance gold — “a clerk reviews this daily” is a statement about an automated consumer of your input.
  • When a form produces no visible output, test for stored injection, not reflected.
  • Stored XSS against a bot is a session-theft primitive, not a popup.
  • HttpOnly on session cookies is the control that breaks the exfiltration step even when the XSS exists.
  • Automated reviewers should run sandboxed with minimal, short-lived credentials — never with a long-lived authenticated session.
  • A restrictive Content Security Policy is worth having, but the exfiltration here happened through a plain image/URL request, so treat CSP as one layer, not the answer.