Cheesy Does It: SQL Injection Authentication Bypass
Overview
- Platform: BugForge
- Vulnerability: SQL Injection (Authentication Bypass), Client-Side Price Manipulation
- Key Technique: Classic SQLi on login username field — string concatenation in SQL query allows
OR 1=1tautology to bypass authentication and return admin session - Result: Full authentication bypass as admin (id:1), flag captured from login response
Objective
Find and exploit vulnerabilities in a pizza ordering web application (same app as prior weeks, new instance).
Initial Access
# Target Application
URL: https://lab-1774911155697-8ep208.labs-app.bugforge.io
# Auth details
Registered user: standard registration
Auth mechanism: JWT HS256 (payload: {id, username, iat})
Admin access obtained via SQLi bypass
Key Findings
-
SQL Injection on Login (CWE-89: Improper Neutralization of Special Elements used in an SQL Command) — The
usernameparameter inPOST /api/loginis interpolated directly into a SQL query via string concatenation. Injectingadmin' OR 1=1--bypasses authentication entirely and returns a valid admin session with the flag. -
Client-Side Price Manipulation (CWE-602: Client-Side Enforcement of Server-Side Security) — Server trusts client-supplied
unit_priceandtotal_priceinPOST /api/orders. Also trusts client-suppliedamountinPOST /api/payment/process. Accepted $0 and negative values.
Attack Chain Visualization
┌──────────────┐ ┌───────────────────────────┐ ┌──────────────┐
│ Register │ │ Extract API routes from │ │ Test SQLi │
│ account, │────>│ JS bundle recon │────>│ on /api/ │
│ map surface │ │ (main.db402bda.js) │ │ login │
└──────────────┘ └───────────────────────────┘ └──────┬───────┘
│
v
┌───────────────────────────┐
│ POST /api/login │
│ {"username": │
│ "admin' OR 1=1--", │
│ "password":"x"} │
└─────────────┬─────────────┘
│
v
┌───────────────────────────┐
│ Server returns: │
│ - Admin JWT (id:1) │
│ - role: admin │
│ - FLAG in success field │
└───────────────────────────┘
Application Architecture
| Component | Path | Description |
|---|---|---|
| Frontend | React SPA (main.db402bda.js) | Material UI pizza ordering interface |
| Backend | Express/Node.js | REST API, JWT auth |
| Database | SQLite (assumed, sequential IDs) | User and order storage |
| Auth | JWT HS256 | Token has {id, username, iat} — no role claim, role is DB-side |
Exploitation Path
Step 1: Reconnaissance — Map the API Surface
Registered an account and captured traffic to enumerate all API endpoints. Additionally extracted routes from the bundled JavaScript file (main.db402bda.js).
Key endpoints discovered:
POST /api/register— User registrationPOST /api/login— AuthenticationGET /api/verify-token— Token validationPUT /api/profile— Profile updatesPOST /api/orders— Order creation (accepts client-supplied prices)POST /api/payment/validate/POST /api/payment/process— Payment flowGET /api/admin/*— Admin endpoints (stats, users, orders)GET /api/menu/*— Menu data (pizzas, bases, sauces, toppings)
Key observations:
- JWT contains
{id, username, iat}with no role — role stored server-side only - Admin endpoints return 403 for regular users (properly enforced)
- Price calculation is entirely client-side with server trusting submitted values
Step 2: Test Admin Escalation Paths (Dead Ends)
Tested several privilege escalation approaches before finding the SQLi:
Mass assignment — Attempted adding role, is_admin, admin, userRole fields to both POST /api/register and PUT /api/profile. All extra fields silently ignored.
JWT manipulation — Tested none algorithm (returned “Invalid token”). Brute-forced 10 common secrets — all returned 403. JWT secret never cracked.
NoSQL injection — Tested $gt operator on login — returned 400.
Double-quote SQLi — Tested admin" OR 1=1-- — returned 400 Invalid credentials (query uses single quotes).
Step 3: SQL Injection on Login
Tested single-quote based SQL injection on the username field:
POST /api/login HTTP/1.1
Host: lab-1774911155697-8ep208.labs-app.bugforge.io
Content-Type: application/json
{"username":"admin' OR 1=1--","password":"x"}
The server concatenates the username directly into the SQL query (e.g., SELECT * FROM users WHERE username='<input>' AND password='...'). The injected payload transforms the query into:
SELECT * FROM users WHERE username='admin' OR 1=1--' AND password='x'
The OR 1=1 tautology causes the query to return all rows. The first row is the admin user (id:1), so the server issues a valid admin JWT and returns the flag in the success response field.
Step 4: Flag Captured
The login response included:
- Valid admin JWT (id:1, username:admin)
- User object with
role: admin - Flag in the
successfield
bug{tNcu9Rw6Y7ou89Z4TX3OqYzAmrPcz5rv}
Step 5: Price Manipulation (Bonus Finding)
Also confirmed the server has no server-side price validation:
POST /api/orders HTTP/1.1
Authorization: Bearer <jwt>
Content-Type: application/json
{"items":[{"pizza_id":1,"base_id":1,"sauce_id":1,"toppings":[1],
"unit_price":0,"quantity":1}],"total_price":0,
"delivery_address":"123 Free St","phone":"555-0000"}
Accepted with unit_price: 0 and total_price: 0. Also accepted negative values. Payment processing endpoint (POST /api/payment/process) similarly accepted amount: 0 and amount: -100.
Flag / Objective Achieved
bug{tNcu9Rw6Y7ou89Z4TX3OqYzAmrPcz5rv}
Obtained from the login response after SQLi authentication bypass as admin.
Key Learnings
- Always test SQLi on login forms — Even in 2026, string concatenation in authentication queries exists. Login forms are high-value SQLi targets because successful injection yields immediate session access, not just data leakage.
- Single vs. double quote matters — Double-quote injection returned a generic 400, while single-quote injection worked. This indicates the SQL query wraps the value in single quotes. Testing both quote types is essential.
- Inconsistent security posture is a signal — This application uses parameterized queries on the orders endpoint but string concatenation on login. When you find one well-protected endpoint, don’t assume all endpoints are equally hardened.
Failed Approaches
| Approach | Result | Why It Failed |
|---|---|---|
| Mass assignment (role=admin) on register/profile | Fields silently ignored | Server only accepts known profile fields |
| JWT none algorithm | “Invalid token” | Server validates algorithm header |
| JWT secret brute force (10 common secrets) | All returned 403 | Secret is not a common/weak value |
| NoSQL injection ($gt operator) | 400 Bad Request | Backend uses SQL, not MongoDB |
| Double-quote SQLi on login | 400 Invalid credentials | SQL query uses single-quote string delimiters |
| Price manipulation for flag trigger | Orders accepted but no flag | Price bugs are incidental, flag tied to auth bypass |
| IDOR on /api/orders/:id | Only own orders (1-5), order 6 = 404 | No other user orders exist to access |
Tools Used
| Tool | Purpose |
|---|---|
| Caido | HTTP interception and request editing (Session 49) |
| Browser DevTools | JS bundle analysis for API route extraction |
Remediation
1. SQL Injection on Login (CVSS: 9.8 - Critical)
Issue: The login query concatenates user-supplied username directly into the SQL string, allowing injection of arbitrary SQL.
CWE Reference: CWE-89 - Improper Neutralization of Special Elements used in an SQL Command (‘SQL Injection’)
// BEFORE (Vulnerable)
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;
const query = `SELECT * FROM users WHERE username='${username}' AND password='${password}'`;
const user = await db.get(query);
// ...
});
// AFTER (Secure)
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;
const user = await db.get(
'SELECT * FROM users WHERE username = ? AND password = ?',
[username, password]
);
// ...
});
Additional hardening: Use bcrypt or argon2 for password hashing instead of storing/comparing plaintext passwords. Parameterized queries eliminate the injection vector entirely.
2. Client-Side Price Manipulation (CVSS: 8.1 - High)
Issue: Server trusts client-supplied unit_price and total_price in order creation. Payment processing also trusts client-supplied amount.
CWE Reference: CWE-602 - Client-Side Enforcement of Server-Side Security
// BEFORE (Vulnerable)
app.post('/api/orders', authenticate, async (req, res) => {
const { items, total_price } = req.body;
await db.createOrder({ user_id: req.user.id, items, total_price });
});
// AFTER (Secure)
app.post('/api/orders', authenticate, async (req, res) => {
const { items, delivery_address, phone } = req.body;
const pricedItems = await Promise.all(items.map(async (item) => {
const base = await db.getBase(item.base_id);
const sauce = await db.getSauce(item.sauce_id);
const toppings = await db.getToppings(item.toppings);
const unit_price = calculatePrice(base, sauce, toppings, item.size);
return { ...item, unit_price };
}));
const total_price = pricedItems.reduce(
(sum, i) => sum + i.unit_price * i.quantity, 0
);
await db.createOrder({ user_id: req.user.id, items: pricedItems, total_price });
});
OWASP Top 10 Coverage
- A03:2021 - Injection — SQL injection on login endpoint via string concatenation
- A04:2021 - Insecure Design — Client-side price enforcement with no server-side validation
References
- OWASP SQL Injection Prevention Cheat Sheet
- CWE-89: SQL Injection
- CWE-602: Client-Side Enforcement of Server-Side Security
- OWASP Testing Guide: SQL Injection
- PortSwigger: SQL Injection
Tags: #sqli #authentication-bypass #price-manipulation #injection #bugforge #api-security
Document Version: 1.0
Last Updated: 2026-03-30