Dissecting a Real-World SQL Injection: From Discovery to Remediation
Introduction
During a recent penetration testing engagement, I discovered a critical SQL injection vulnerability in a client's authentication system. This writeup walks through the discovery process, exploitation, impact assessment, and remediation — with all identifying details changed to protect the client.
Discovery
The target application was a B2B SaaS platform with a standard login form. Initial reconnaissance with Burp Suite revealed that the login endpoint accepted JSON payloads:
POST /api/v1/auth/login HTTP/1.1
Content-Type: application/json
{
"email": "user@example.com",
"password": "test123"
}
What caught my attention was the error response when I submitted a malformed email:
{
"error": "Invalid credentials",
"debug": "SELECT * FROM users WHERE email = 'user@example.com' AND active = 1"
}
The application was leaking the SQL query in debug output. This is a classic information disclosure vulnerability that immediately suggests the possibility of SQL injection.
Confirming the Vulnerability
I tested with a simple payload in the email field:
' OR '1'='1' --
The server responded with a 200 status and returned a valid JWT token — confirming the SQL injection. The application was concatenating user input directly into the SQL query without parameterization.
Escalation
With confirmed SQLi, I escalated methodically:
Step 1: Database Enumeration
Using UNION-based injection, I enumerated the database schema:
' UNION SELECT table_name, NULL, NULL, NULL FROM information_schema.tables --
This revealed tables including users, api_keys, billing, and audit_logs.
Step 2: Data Extraction
I extracted the users table structure and found passwords were stored as unsalted MD5 hashes — another critical finding:
' UNION SELECT email, password_hash, role, NULL FROM users LIMIT 10 --
Step 3: Privilege Escalation
The role column contained values like user, admin, and superadmin. By authenticating as a superadmin account (whose MD5 hash I cracked in under a minute using hashcat), I gained full administrative access.
Impact Assessment
The vulnerability chain allowed:
- Authentication bypass for any user account
- Full database read access including PII, billing data, and API keys
- Privilege escalation to superadmin via weak password hashing
- Potential RCE via SQL injection into system commands (not tested per scope)
This was rated as Critical (CVSS 9.8).
Remediation
I provided the client with the following remediation steps:
- Parameterized queries: Replace all string concatenation with parameterized queries or an ORM
- Remove debug output: Never expose SQL queries in API responses, even in development
- Password hashing: Migrate from MD5 to bcrypt or Argon2id with per-user salts
- Input validation: Implement strict input validation on all user-supplied data
- WAF rules: Deploy SQL injection detection rules as a defense-in-depth measure
# Before (vulnerable)
query = f"SELECT * FROM users WHERE email = '{email}'"
cursor.execute(query)
# After (parameterized)
query = "SELECT * FROM users WHERE email = %s"
cursor.execute(query, (email,))
Lessons Learned
This engagement reinforced several important principles:
- Never trust user input — even in fields that seem innocuous like email addresses
- Defense in depth matters — the debug output made discovery trivial, but the SQLi would have been found regardless
- Password security is fundamental — MD5 hashing without salting is effectively plaintext in 2026
- Automated scanners aren't enough — this vulnerability was in a JSON API endpoint that many automated tools would have missed
The client patched the vulnerability within 48 hours and implemented all recommended remediations within two weeks. A follow-up assessment confirmed the fixes were effective.