🌐 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 Storethat 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 12const 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 thatMongoDBis being used, and inapp.pywe will find a function that generates discount codes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17const 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
/redeemroute specified in theroutes.pyfile. 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 7const { 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
discountCodeis 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 Injectionto find a valid discount code. The query, as it is set, takes thediscountCodeand 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 get20of the50points needed. Continuing to read the code, to bypass this check, we can exploit another vulnerability. Usually, when atimeoutordelayis 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
delayvariable 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 secondsbefore the balance update is actually performed in the database. We can exploit this time delay by sending, for example, two more requests inparallelto accumulate60 pointsand 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 beenupdatedyet due to thetimeout, we canredeemmultiple codes without being blocked by therestrictionthat allows redeeming onlyone code per day. By sending the requests during thiswindow of time, we are able tobypassthe restriction and accumulate enough points topurchasethe 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 inPythonwithmultithreadingto send multiple requests concurrently. ThroughPayloadAllTheThings, I searched forNoSQL payloadshttps://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$nestands fornot equal. What could happen with this in the following portion of the code, which was also mentioned earlier?
1const 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 totallyrandomandalphanumeric, 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 multithreadingto take advantage of that one and a half secondstimeoutset 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,multithreadingto leverage theRace Condition. I create a pool of20 threadsthat run concurrently after a registration phase and account creation using theFakerlibrary, which generates randomfake credentials. Once that’s done, I take advantage of theNoSQL Injectionfor all the threads by visiting the/redeem?discountCode[$ne]=testroute, 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 usingBeautifulSoupand print it out.
🚩 Flag Capture
Flag
Proof of Execution
🔧 Tools Used
Tool Purpose Python Exploit
💡 Key Learnings
New Knowledge
I have learned
NoSQL Injectionoperators such as$ne,$lt, and how toexploitthem in anunsanitized query. I also learned how to create arace condition with multiple sessionsto exploit even themilliseconds of delay.
Time Optimization
Whenever a
delayor any kind oflagis introduced in the code, always consider the possibility of aRace Condition. Additionally, where there arecommentsin the code, it’s as if there arehintspointing 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