SQL injection (SQLi) is a cyberattack technique where an attacker inserts malicious SQL code into an input field, tricking the database into executing unintended commands. It remains the most documented web vulnerability and consistently appears in the OWASP Top 10 — not because developers don't know about it, but because it keeps getting overlooked in fast-moving Indian development teams shipping under deadline pressure. The good news: it is almost entirely preventable with parameterized queries and disciplined input handling.
What Is SQL Injection
When your application builds a database query by directly concatenating user input, you hand the attacker a keyboard wired to your database. Consider a simple login check in PHP:
// Vulnerable
$query = "SELECT * FROM users WHERE email='" . $_POST['email'] . "' AND password='" . $_POST['password'] . "'";An attacker enters ' OR '1'='1 as the email. The query becomes:
SELECT * FROM users WHERE email='' OR '1'='1' AND password=''Because '1'='1' is always true, the query returns the first row — usually an admin account. The attacker is now logged in without a valid password.
This is authentication bypass, one of several attack classes that SQLi enables.
How SQL Injection Attacks Work — The Full Flow
graph TD
A[Attacker identifies input field] --> B[Injects SQL payload into input]
B --> C{Application builds query by string concat}
C --> D[Malicious SQL reaches DB engine]
D --> E{Attack type}
E --> F[In-Band Error-Based]:::danger
E --> G[In-Band Union-Based]:::danger
E --> H[Blind Boolean-Based]:::danger
E --> I[Blind Time-Based]:::danger
F --> J[Full schema extracted]:::danger
G --> J
H --> J
I --> J
J --> K[Tables listed, rows dumped]:::danger
K --> L[Credentials, PII, financial data exfiltrated]:::danger
classDef danger fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0The Four Main SQLi Techniques
In-Band — Error-Based
The attacker forces the database to produce an error message containing sensitive information. MySQL, for example, will sometimes return table names and column values inside error text. A single payload like ' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version()))) -- can reveal the database version, which narrows down which known CVEs to exploit next.
In-Band — Union-Based
The UNION SQL operator combines the results of two SELECT statements. If the attacker can guess the number of columns and their data types, they can append a second query that pulls from any table:
' UNION SELECT username, password, NULL FROM admin_users --The result set gets returned to the page as if it were normal application data.
Blind — Boolean-Based
When the application returns no visible error or data, an attacker still extracts information by asking true/false questions. If the page renders differently for 1=1 versus 1=2, the attacker can iterate through character codes one bit at a time to reconstruct entire column values. Automated tools like sqlmap do this at machine speed.
Blind — Time-Based
When the application returns identical output regardless of the query, the attacker injects a time-delay function:
'; IF (1=1) WAITFOR DELAY '0:0:5' --A five-second response confirms the condition is true. Like boolean blind, this can fully enumerate a database — it just takes more requests.
Know your vulnerabilities before attackers do
Run a free VAPT scan — takes 5 minutes, no signup required.
Book Your Free ScanReal-World Impact
SQL injection enables attackers to:
- Bypass authentication — log in without credentials
- Exfiltrate data — dump entire tables including PII, passwords, and financial records
- Modify or delete records — UPDATE or DROP TABLE changes that are irreversible
- Execute OS commands — on misconfigured databases, xp_cmdshell (MSSQL) or load_file (MySQL) can spawn shell access
pie title OWASP Top 10 A03 Injection — Relative Severity Share by CWE
"SQL Injection CWE-89" : 45
"Command Injection CWE-77" : 25
"LDAP Injection CWE-90" : 15
"XPath Injection CWE-643" : 10
"Other Injection" : 5Prevention — Parameterized Queries and Prepared Statements
The single most effective control is separating code from data. A parameterized query passes user input as a bound parameter, never as part of the SQL string.
Node.js with mysql2 — vulnerable vs fixed:
// VULNERABLE — direct string interpolation
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`;
db.query(query, callback);
// FIXED — parameterized query
const query = "SELECT * FROM users WHERE email = ?";
db.query(query, [req.body.email], callback);Python with psycopg2:
# VULNERABLE
cursor.execute(f"SELECT * FROM accounts WHERE id = {user_id}")
# FIXED
cursor.execute("SELECT * FROM accounts WHERE id = %s", (user_id,))Java with PreparedStatement:
// VULNERABLE
Statement stmt = conn.createStatement();
stmt.executeQuery("SELECT * FROM orders WHERE customer_id = " + customerId);
// FIXED
PreparedStatement ps = conn.prepareStatement("SELECT * FROM orders WHERE customer_id = ?");
ps.setInt(1, customerId);
ps.executeQuery();The database driver treats bound parameters as literal data, never as executable SQL — regardless of what characters they contain.
.query( and raw( calls and audit each one.Full Prevention Checklist
| Control | Why It Helps | Implementation Notes |
|---|---|---|
| Parameterized queries / prepared statements | Eliminates code-data confusion at the DB layer | Mandatory for ALL user-facing inputs |
| ORM query builder (no raw SQL) | Automatically parameterizes most queries | Audit raw(), literal(), query() overrides |
| Input validation and allowlisting | Rejects structurally invalid input before it reaches the DB | Validate type, length, format — not just "no quotes" |
| Least-privilege DB user | Limits blast radius if injection succeeds | App DB user must not have DROP, CREATE, or FILE grants |
| Web Application Firewall (WAF) | Detects and blocks common SQLi payloads at the edge | Defense-in-depth; not a substitute for parameterization |
| Error handling — suppress DB errors | Prevents error-based extraction | Never expose raw DB errors to end users |
| Automated VAPT scanning | Finds injection points before attackers do | Run on every release, not just at launch |
', --, or UNION — is not a viable defense. Attackers use encoding, case variations, and comment syntax to bypass keyword filters. Parameterized queries are the only reliable control at the query layer.Least-Privilege Database Users
Most Indian startup applications connect to the database with a root or admin user because it's the path of least resistance during setup. This is a critical misconfiguration. If an attacker achieves SQLi on an admin-credentialed connection, they can drop tables, create backdoor accounts, or read files from the server filesystem.
Create a dedicated application DB user with only the permissions the application actually needs:
-- Create restricted app user
CREATE USER 'bachao_app'@'localhost' IDENTIFIED BY 'strong_random_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON bachao_db.* TO 'bachao_app'@'localhost';
-- No GRANT OPTION, no DROP, no CREATE, no FILE
FLUSH PRIVILEGES;A read-only reporting user should receive only SELECT. Admin migrations should run under a separate privileged user that is never used by the live application.
Why Indian Developers Keep Missing This
Several patterns specific to the Indian SMB and startup ecosystem make SQLi disproportionately common:
- Tight sprint cycles — security testing is deferred to "after launch"
- Legacy codebases — older PHP and raw JDBC codebases predate ORM adoption
- Outsourced development — vendor-built applications rarely include security deliverables in scope
- Shared hosting — single-user DB setup by default, no separation between app and admin credentials
- No automated scanning in CI/CD — deploys ship without injection testing
ORMs Are Not Magic — Know the Exceptions
Prisma, Sequelize, SQLAlchemy, and Hibernate all parameterize standard model queries. But every ORM exposes an escape hatch for raw SQL — and that is where injection vulnerabilities re-enter modern codebases.
Prisma raw query — vulnerable vs fixed:
// VULNERABLE — template literal bypasses Prisma's parameterization
const users = await prisma.$queryRaw`SELECT * FROM users WHERE name = '${name}'`;
// FIXED — use Prisma.sql tagged template with proper interpolation
import { Prisma } from "@prisma/client";
const users = await prisma.$queryRaw(
Prisma.sql`SELECT * FROM users WHERE name = ${name}`
);The Prisma.sql tagged template correctly parameterizes the value; the plain template literal does not.
Bachao.AI, built by Dhisattva AI Pvt Ltd, includes SQL injection detection as part of its automated VAPT scan suite. A free VAPT scan will identify injectable endpoints across your web application without requiring manual testing.
External References
- OWASP — SQL Injection Prevention Cheat Sheet
- CERT-In — Vulnerability Notes and Advisories