Vulnlab: Insecure Direct Object Reference (IDOR) Writeup
How to Exploit IDOR Vulnerabilities in a Vulnerable Web Application
IDOR, what is it 🤔?
IDOR, is a vulnerability that occurs when a web application allows users to access data or objects they shouldn’t have permission to, simply by manipulating URL param or other inputs.
For example, imagine we have a single profile account where we can view or edit our own personal information. But then, a hacker discovers a security loophole that allows them to access someone else’s profile. Even worse, they can edit both our information and other people’s data without our permission 🙀. Here’s what that might look like
😜 Hacker’s POV:
We’ve just hit the jackpot. an IDOR (Insecure Direct Object Reference) vulnerability.
With this, the game changes. By simply manipulating a request parameter or tampering with a cookie value, we can peek into anyone’s personal profile. Names, emails, phone numbers, all laid bare.
And we don’t stop there, we can edit those details in real time, rewriting someone’s digital identity as if they owned it.
And here’s the scary part (the result). Since this is just a pentest, we only had a little fun by changing the job title. But hey, don’t underestimate this bug In the wrong hands, it could turn into a digital nightmare. So yeah… report it ASAP before things get wild.
Alright, enough theory , let’s jump into a real-world scenario using a lab from VulnLab, developed by the legend himself, Yavuzlar. So, grab your coffee ☕, fire up your terminal 💻, and let’s dive right in 🚀
#1 Invoices
Gain unauthorized access to other users invoices.
Let’s say we have an invoice page like this. But something about this page seems ‘sus,’ can you spot it?
That’s right, we can see the parameter
?invoice_id=1
Here. We can try to manipulate this query string. What do you think we’ll find?
Turns out… we can view someone else’s invoice! 😱, ofc we can also see detailed personal info like their name, items they purchases, and even their address. Lets move on to the next level
#2 Ticket Sales
Buy tickets for less than the regular price.
Here, we’re faced with a case where we can buy tickets, let’s say we want to grab IVE concert tickets in Jakarta, with each ticket priced at $10. We’ve got $1,000 in hand and plan to treat 10 friends, which should cost us $100 in total. But when we go to purchase the tickets, we notice something suspicious. this site has a vulnerability!
As we can see in the request body, it sends raw data containing the ticket price along with the number of tickets ordered.
{ amount:”10”, price: “10” }
Now this… looks juicy 😏 Let’s fire up Burp Suite and have some fun manipulating that request body.
Once we’ve intercepted the request and tweaked the values, our hope is simple — we score 10 extra tickets for $0. In other words, free tickets, baby 😁
And voila, just as we thought, we scored 10 extra tickets for a $0 🦀 Let’s go, time to buy 100 more tickets for out beloved friends 😋
#3 Changing Password
Change another users password without permission.
In this level’s case, we’re faced with our own password reset page. Just like in the previous level, we first need to check how the application’s flow works. While making a request, I noticed a payload being sent to the server that looked like this:
{
"password: 123456"
"user_id: 1" // this one's the real suspect 😈
}As usual, we fire up our baby (Burp Suite ) and have some fun manipulating that request body 😏.
Look at it, I’m pretty sure user_id=1 refers to my own account. Naturally, that means other users data is probably referenced by incrementing this ID: 1 → 2 → 3 → and so on.
So, just for fun, I tried changing the password for the user with user_id=2.
And sure enough, user_id=2, who turns out to be Pierre, we’ve successfully hacked his password 😁.
#4 Money Transfer
Transfer money from another user’s account to your own account without any permission.
Di Suatu hari yang cerah, gw ingin transfer uang gw ke temen gw yang adalah user_id=2tapi ketika gw cek ke tab network, gw menemukan sesuatu yang sus terjadi, kok data yang dikirim ke server kayak gini ya 🤔
{
"transfer_amount": 100
"recipient_id": 2
"sender_id": 1
}Mari kita coba intercept lagi request nya 😏
From the data that being sent to the server, we can see that I or the one with recipient_id=1about to transfer $500 to the user with user_id=2.
But wait 🤚, what if we flip the script and make someone else send the money to me instead? 😋
Let’s try “tweaking” that request body (again😴😎).
{
"transfer_amount": 1000
"recipient_id": 1 // this is my id
"sender_id": 3
}Damn, I feel like a corrupt politician embezzling public funds right now 👹
#5 Address Entry
View other users address information without any permission.
In this level, we’re faced with a case where we need to proceed to checkout. Here, we have to enter our address first for the shipping process.
Hold on, when i press the order button, i can see this payload is little bit SUS 😈
{
"address": "New York, NY 10001",
"addressID": "1", // this is our address-id
"order": "",
}Here’s where I get curious 🤔, what if we mess with the addressID value? If addressID=1 points to my address, then there’s a chance addressID=2, addressID=3, and so on might just spill someone else’s address. Let’s give it a shot 😏
Well well well… turns out I was right, right?
we can actually see other people’s addresses. Time to dox####? Just kidding… or am I? 😁🧐😜
#6 About
Change other users profile information.
So actually, I already demoed and explained in the intro, which was basically for this level too, so let’s just continue from the attacker’s POV 🤠.
I’ll keep it short since, you know, time’s ticking ⌚.
let’s try updating the bio first, just to make my profile look cooler (not that it wasn’t already cool from the start 😎).
As usual, looks like we can play around with the request body again hehehe. One thing to highlight here, if puserid: 3 points to our own profile, that’s already dangerous. Why? Because it means user profile IDs are predictable, it just incremented one by one.
With that in mind, and from what you’ve read so far, you should already be able to pull off the exploit yourself, right? Go ahead, give it a shot first then come back here for the final level 😁.
Done already? If not, let’s finish this together. When you hit the save button, don’t forget to intercept the request first, and as you can see in this screenshot, here’s the request body that’s about to be sent. Let’s go ahead and change it to the user with puserid: 1.
I was supposed to just edit my own bio, but seeing a request like this… hmm who wouldn’t be tempted? Let’s mess around with the guy who owns puserid: 1 🗿
So, I just changed his job info and bio and ofc, don’t forget to add a little hacked tag to make it look extra hangkerjsz 😎.
When I checked the response tab, something interesting popped up. there’s a function where, whenever we click the about-button (the Edit Profile button), it adds a cookie userid=3, which is our own user ID. So, what if we change that cookie to userid=1 instead?
Well, well, well… turns out not only we can change someone else’s personal information, but we can also directly access their profile page 😹
#7 Final Boss (Shopping Cart)
Purchase products for free.
We’re about to checkout an item. As usual, let’s follow the flow first, this time, I’ll try to buying one thieft cat.
During checkout, I need to verify before moving to the next step. But there’s something interesting here, so let’s keep this info for later.
?code=SSBoZWFyZCB0aGUgYWRtaW4gaXMgZm9yZ2V0ZnVs
We get an OTP code, just verify it and the purchase goes through, easy.
But hold on… what’s this? There’s something called hesoyam showing up 🤔. Looks like it’s encoded in Base64. Let’s decode it, JGJhbGFuY2U9MTAw.
So, after decoding it, we get $balance=100 🤔. Interesting. what’s this supposed to mean?
Let’s try running through the flow again, but this time we’ll intercept the request.
Wait a sec, the code parameter looks exactly the same as when I checked out the first time.3Dvalid.php?code=SSBoZWFyZCB0aGUgYWRtaW4gaXMgZm9yZ2V0ZnVs .
When intercepting the request, we can see a query param called hesoyam, with the value we just decoded is$balance=100.
Since we now know that the value of this hesoyam param is just Base64 encoded, let’s try modifying the value and then re-encode it back to Base64.
And yep. nothing happened. Instead, my balance went minus $100 :V. But don’t worries, we still have another variable to play with such as
3Dvalid.php?code=SSBoZWFyZCB0aGUgYWRtaW4gaXMgZm9yZ2V0ZnVs Let’s try checking out again 😊.
Here’s the idea. in our first two attempts, whenever we bought an item, we had to confirm it with an OTP first. And each time, the URL we got was exactly the same, it contained a code param with the same value.
So what if… instead of clicking the “Buy The Items” button, we just directly access that URL?
Turns out, it works! I can go straight to the verify-otp page without ever pressing the button. This clearly shows that the vulnerability lies in the 3Dvalid.php?code=SSBoZWFyZCB0aGUgYWRtaW4gaXMgZm9yZ2V0ZnVs endpoint, which lets us bypass the normal flow and hit the verify-otp page directly.
Let’s try it again. When attempting to buy an item that costs more than our balance, we get an error like the one in this screenshot.
But since we know that directly accessing 3Dvalid.php?code=SSBoZWFyZCB0aGUgYWRtaW4gaXMgZm9yZ2V0ZnVs takes us straight to the verify-otp page, it looks like we can bypass the validation just like in the screenshot above.
Well, well, well… turns out it actually works!. we bypassed the validation! From here, all we need to do is enter the OTP code we got from the admin chat.
And wow 🙀. we just bought the item with a negative balance kwkwkw 😹. Plus, you’ve also successfully grabbed the flag. Just submit it in Yavuzlar’s chat box, nothing fancy, it just gives you a link to his documentation site.
#7 Conclusion
What can we take away from today?
Now, the key lesson here is IDOR (Insecure Direct Object Reference). It might look like a “simple” bug at first, but the impact can be massive — from unauthorized access to sensitive information, to account takeovers, and even financial fraud.
How do we prevent it?
- Never trust user-controlled input (like IDs in URLs or request bodies).
- Always implement proper authorization checks on the server side.
- Use indirect references (like UUIDs or tokens) instead of incremental IDs.
- Apply the principle of least privilege and test access controls thoroughly.
Thank you for reading this until the end! This write-up serves as my personal journal while working through this room, documenting my journey, challenges, and learnings along the way. I hope it can also be a helpful resource for others exploring similar paths.
Happy Hacking and learning~! 👨💻
Source
Vulnlab: https://github.com/Yavuzlar/VulnLab
cyberchef: https://toolbox.itsec.tamu.edu/
#cybersecurity #websecurity #pentesting
