We recently released a lab on MongoDB IDOR and how to guess ObjectIds. Basically, you need to find the ObjectId
of an account that was created 7 days ago. In those 7 days, a few accounts were created and deleted to make things a bit more fun.
./hack_objectid
to solve it, or at least not to solve it efficiently. This forces people to write their own script. Your script should take an ObjectId
, retrieve the current timestamp
and counter
from that ObjectId
, and then play with both values to try to find the admin account.
This is where you can see the power of two things when trying to exploit vulnerabilities: Invariants and Short Feedback Loops.
Basically, one of our subscribers will write a script that attacks the online lab to try to find the ObjectId
to get the key to solve the challenge. It won't work the first time; they will tweak the code, run it again; it won't work, they will tweak the code again and re-run the code. The problem is that every single change costs around 10 minutes of testing. To make matters worse, you have nothing to do during those 10 minutes, so people tend to get distracted—a few YouTube Shorts, some TikTok, and maybe a bit of social media... This quickly becomes a nightmare to stay focused on solving the lab.
There is a good chance that decoding and re-encoding the ObjectId
may go wrong, especially if you haven’t done it before. To make sure your code does the right thing, you need to find invariants that you can leverage—basically, ways to validate your code.
A mathematical example would be to try to compute 3x4
. You should get 12
. Then you can do 12/3
and 12/4
to make sure you get 4
and 3
. This allows you to check that the value 12
is correct.
We want to find something similar for our ObjectId
. Once we have written our decoding (let’s call it DEC
) and encoding (ENC
) functions, we take the ObjectId
and check that:
timestamp, uniq_value, counter = DEC(objectId)
if ObjectId == ENC(timestamp, uniq_value, counter)
// WIN
else
// There is something wrong with our decoding and encoding
A very simple check, but it will prevent a lot of problems and save a lot of testing time. It doesn’t prevent issues in both functions that may cancel each other out, though. To account for this, we can leverage a third-party website or check that the timestamp we just created and decoded matches the current time (that we can retrieve using date +%s
on Unix for example).
Now that we know our encoding/decoding seems to work, it’s time to leverage Short Feedback Loops. We know that the ObjectId
was created around 7 days ago. Let’s mimic that code and see if we can find it.
timestamp, uniq_value, counter = DEC(objectId)
timestamp -= 7*24*60*60 // 7 days
counter -= 10
newobjectid = ENC(timestamp, uniq_value, counter)
We get our new ObjectId
named newobjectid
, and we can now try to find it locally by writing code that will just search for this value:
timestamp, uniq_value, counter = DEC(objectId)
for timestamp_delta in range(-10, 10):
for counter_delta in range(-12, 0):
test = ENC(timestamp + timestamp_delta, uniq_value, counter + counter_delta)
if test == newobjectid
// WIN !!
...
This won’t guarantee that our exploit works, but it will increase the likelihood that it does and will speed up development significantly.
As a security tester, you have to leverage Invariants and Short Feedback Loops. This is the best way to limit the risk of mistakes and ensure that mistakes are quickly detected. This will save you a ton of time, work, and frustration. Next time you are testing or exploiting a vulnerability, ask yourself: "Can I find invariants to check that my code does what I think it does?" and "How can I create a short feedback loop?" to speed up the process and limit frustration.