🌐 Focus. Speed. I Am Speed.
A detailed write-up of the Web challenge 'Focus. Speed. I Am Speed.' from SrdnlenCTF - 2025
📊 Challenge Overview
Category Details Additional Info 🏆 Event SrdlenCTF - 2025 Event Link 🔰 Category Web 🌐 💎 Points 50 Out of 500 total ⭐ Difficulty 🟢 Easy Personal Rating: 3/10 👤 Author Octaviusss Profile 🎮 Solves (At the time of flag submission) 189 XX% solve rate 📅 Date 19-01-2025 SrdlenCTF - 2025 Day X 🦾 Solved By aquila2 Team: Team Aetruria
📝 Challenge Information
Welcome to Radiator Springs’ finest store, where every car enthusiast’s dream comes true! But remember, in the world of racing, precision matters—so tread carefully as you navigate this high-octane experience. Ka-chow!
Website: http://speed.challs.srdnlen.it:8082
🎯 Challenge Files & Infrastructure
Provided Files
Files:
🔍 Initial Analysis
First Steps
Initially, the website appears as follows:
we notice by going to the
Official Store
that it is an online store.So, most likely, the intent of this challenge will be to purchase the flag. In fact, by opening the attached files, specifically
app.js
, we can notice these lines of code in the population of the database products:
1 2 3 4 5 6 7 8 9 10 11 12
const products = [ { productId: 1, Name: "Lightning McQueen Toy", Description: "Ka-chow! This toy goes as fast as Lightning himself.", Cost: "Free" }, { productId: 2, Name: "Mater's Tow Hook", Description: "Need a tow? Mater's here to save the day (with a little dirt on the side).", Cost: "1 Point" }, { productId: 3, Name: "Doc Hudson's Racing Tires", Description: "They're not just any tires, they're Doc Hudson's tires. Vintage!", Cost: "2 Points" }, { productId: 4, Name: "Lightning McQueen's Secret Text", Description: "Unlock Lightning's secret racing message! Only the fastest get to know the hidden code.", Cost: "50 Points", FLAG: process.env.FLAG || 'SRDNLEN{fake_flag}' } ];
As we can see, in
productId: 4
(the last and most expensive one, the flag is contained). Now we just need to figure out how to accumulate points. By continuing to analyze the attached files, we’ll realize thatMongoDB
is being used, and inapp.py
we will find a function that generates discount codes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
const createDiscountCodes = async () => { const discountCodes = [ { discountCode: generateDiscountCode(), value: 20 } ]; for (const code of discountCodes) { const existingCode = await DiscountCodes.findOne({ discountCode: code.discountCode }); if (!existingCode) { await DiscountCodes.create(code); console.log(`Inserted discount code: ${code.discountCode}`); } else { console.log(`Discount code ${code.discountCode} already exists.`); } } };
This function generates 10 random discount codes for the purchase, which will be used to acquire points that can then be spent on purchasing items, via the
/redeem
route specified in theroutes.py
file. Here, there is a vulnerable part of the code, as it accepts a parameterdiscountCode
, which can be used to redeem a valid code and acquire points. The vulnerability is found exactly in this portion of the code:
1 2 3 4 5 6 7
const { discountCode } = req.query; if (!discountCode) { return res.render('error', { Authenticated: true, message: 'Discount code is required!' }); } const discount = await DiscountCodes.findOne({ discountCode });
In fact, since
discountCode
is a parameter that can be manipulated by the user and is directly used in the query as it is entered by the user, we can send a malicious payload using aNoSQL Injection
to find a valid discount code. The query, as it is set, takes thediscountCode
and checks whether it matches one of those 10 randomly generated discount codes seen earlier. By sending a malicious payload, we can create a condition that returnsTrue
, allowing us to redeem a discount code even without knowing it. Later, there is a check where only one discount code can be redeemed per day. So, even if we try to redeem it, we would only get20
of the50
points needed. Continuing to read the code, to bypass this check, we can exploit another vulnerability. Usually, when atimeout
ordelay
is applied, it is related to aRace Condition
. As we can see in the following lines of code:
1 2 3 4 5 6 7 8
// Apply the gift card value to the user's balance const { Balance } = await User.findById(req.user.userId).select('Balance'); user.Balance = Balance + discount.value; // Introduce a slight delay to ensure proper logging of the transaction // and prevent potential database write collisions in high-load scenarios. new Promise(resolve => setTimeout(resolve, delay * 1000)); user.lastVoucherRedemption = today; await user.save();
The account balance is extracted, then increased by adding the discount code that we have redeemed. A timeout is applied based on the
delay
variable declared at the beginning of the file (let delay = 1.5;
), which is then multiplied by1000
(to convert it tomilliseconds
). This results in a totaltimeout of 1.5 seconds
before the balance update is actually performed in the database. We can exploit this time delay by sending, for example, two more requests inparallel
to accumulate60 points
and purchase the flag. This is possible because the subsequent two requests will be sent in less than1.5 seconds
, and therefore, they will pass the checks done previously. These checks are based on the values present in the database, and since those values have not beenupdated
yet due to thetimeout
, we canredeem
multiple codes without being blocked by therestriction
that allows redeeming onlyone code per day
. By sending the requests during thiswindow of time
, we are able tobypass
the restriction and accumulate enough points topurchase
the flag.
🎯 Solution Path
Exploitation Steps
Initial setup
After understanding the vulnerabilities we are dealing with, I did a couple of searches on the internet to exploit the
NoSQL Injection
, as for theRace Condition
, it’s enough to create an exploit script inPython
withmultithreading
to send multiple requests concurrently. ThroughPayloadAllTheThings
, I searched forNoSQL payloads
https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/NoSQL%20Injection/README.md. I tried the following payload:http://speed.challs.srdnlen.it:8082/redeem?discountCode[$ne]=test
, where$ne
stands fornot equal
. What could happen with this in the following portion of the code, which was also mentioned earlier?
1
const existingCode = await DiscountCodes.findOne({ discountCode: code.discountCode });
As we mentioned, in this case, the above code will create a
valid condition
, so it’s as if we’ve found the correctdiscountCode
. If we were to express this innatural language
, the action would befind me a discountCode that is not equal to 'test'
. As we saw during the generation phase, the discountCodes are totallyrandom
andalphanumeric
, so it’s impossible for the database to have a discountCode equal totest
. Therefore, the condition will returnTrue
, allowing us to redeem the code and add 20 points to our balance.
Exploitation
The exploitation phase doesn’t stop here. Now that we’ve figured out how to
redeem points
, we need to understand how toredeem more than 20 points per day
, as mentioned earlier. We can usePython's multithreading
to take advantage of that one and a half secondstimeout
set for making multiple requests. So, I’ll write a script that does this and retrieves the flag, because doing it manually would be impossible.
Flag capture
🛠️ Exploitation Process
Approach
The Python exploit utilizes
requests
,bs4
, and finally,multithreading
to leverage theRace Condition
. I create a pool of20 threads
that run concurrently after a registration phase and account creation using theFaker
library, which generates randomfake credentials
. Once that’s done, I take advantage of theNoSQL Injection
for all the threads by visiting the/redeem?discountCode[$ne]=test
route, which will redeem a code and add a total of 20 points to the account balance each time, taking advantage of theone-and-a-half-second timeout
. Once all of that is done, I visit the root (/
) of the site where the flag is displayed (as shown in the image above), and then I extract it usingBeautifulSoup
and print it out.
🚩 Flag Capture
Flag
Proof of Execution
🔧 Tools Used
Tool Purpose Python Exploit
💡 Key Learnings
New Knowledge
I have learned
NoSQL Injection
operators such as$ne
,$lt
, and how toexploit
them in anunsanitized query
. I also learned how to create arace condition with multiple sessions
to exploit even themilliseconds of delay
.
Time Optimization
Whenever a
delay
or any kind oflag
is introduced in the code, always consider the possibility of aRace Condition
. Additionally, where there arecomments
in the code, it’s as if there arehints
pointing to where the vulnerability might be located.
Skills Improved
- Binary Exploitation
- Reverse Engineering
- Web Exploitation
- Cryptography
- Forensics
- OSINT
- Miscellaneous
📚 References & Resources
Learning Resources
- https://www.invicti.com/learn/nosql-injection/
- https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/NoSQL%20Injection/README.md
- https://www.guidepointsecurity.com/blog/race-conditions-in-modern-web-applications/#:~:text=RACE%20conditions%20occur%20when%20we,RACE%20condition%20may%20be%20present.
📊 Final Statistics
Metric | Value | Notes |
---|---|---|
Time to Solve | 00:15 | From start to flag |
Global Ranking (At the time of flag submission) | 26/1577 | Challenge ranking |
Points Earned | 500 | Team contribution |
Created: 19-01-2025 • Last Modified: 19-01-2025 Author: mH4ck3r0n3 • Team: Team Aetruria