Security

SQL Injection

A code injection attack where malicious SQL is inserted into application queries through user input, enabling unauthorized data access or modification.

What Is SQL Injection?

SQL Injection (SQLi) is a code injection vulnerability that occurs when an application incorporates untrusted user input into SQL database queries without proper sanitization or parameterization. An attacker exploits this flaw by crafting input that changes the structure of the intended SQL query, allowing them to read sensitive data, modify or delete records, execute administrative operations, or in some cases gain command-line access to the underlying server.

SQL Injection has been one of the most prevalent and dangerous web application vulnerabilities since it was first described in 1998. Despite being well understood and straightforward to prevent, it consistently appears in the OWASP Top 10 and remains a leading cause of data breaches. The reason is simple: every application that interacts with a SQL database is potentially vulnerable, and a single overlooked input field can compromise the entire database.

The impact of SQL Injection ranges from information disclosure to complete system compromise. The 2008 Heartland Payment Systems breach, which exposed 130 million credit card numbers, was carried out through SQL Injection. The 2011 Sony PlayStation Network breach, which compromised 77 million user accounts, also began with a SQL Injection attack. These incidents demonstrate that SQLi is not a theoretical risk — it is actively exploited at massive scale.

How It Works

SQL Injection works by manipulating the structure of a SQL query through user-controlled input. Consider a login form that checks credentials with a dynamically constructed query:

# Vulnerable: string concatenation builds the SQL query
def authenticate(username, password):
    query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
    result = db.execute(query)
    return result.fetchone() is not None

A legitimate login with username=alice and password=secret123 produces:

SELECT * FROM users WHERE username = 'alice' AND password = 'secret123'

An attacker enters ' OR '1'='1' -- as the username:

SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = ''

The OR '1'='1' clause always evaluates to true, and the -- comments out the password check. The query returns the first user in the table, granting the attacker access without valid credentials.

More dangerous variants use UNION to extract data from other tables:

-- Attacker input: ' UNION SELECT credit_card, cvv, expiry FROM payments --
SELECT name, email FROM users WHERE id = '' UNION SELECT credit_card, cvv, expiry FROM payments --'

The fix is parameterized queries (also called prepared statements), which separate SQL structure from data:

# Fixed: parameterized query — user input is never part of the SQL structure
def authenticate(username, password):
    query = "SELECT * FROM users WHERE username = %s AND password = %s"
    result = db.execute(query, (username, password))
    return result.fetchone() is not None

Here is the same fix across common languages and frameworks:

// Node.js with pg (PostgreSQL)
const result = await pool.query(
  'SELECT * FROM users WHERE username = $1 AND password = $2',
  [username, password]
);
// Java with JDBC
PreparedStatement stmt = conn.prepareStatement(
    "SELECT * FROM users WHERE username = ? AND password = ?"
);
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
// C# with ADO.NET
var cmd = new SqlCommand(
    "SELECT * FROM users WHERE username = @user AND password = @pass", conn);
cmd.Parameters.AddWithValue("@user", username);
cmd.Parameters.AddWithValue("@pass", password);

ORMs like SQLAlchemy, Django ORM, ActiveRecord, and Entity Framework generate parameterized queries by default, making them inherently resistant to SQL Injection when used correctly.

Why It Matters

SQL Injection gives attackers direct access to the most valuable asset in most applications: the database. A successful SQLi attack can extract every record from every table, including user credentials, personal information, financial data, and proprietary business data. In many configurations, SQLi can also be escalated to write access, allowing attackers to modify records, insert backdoor accounts, or delete entire tables.

Beyond data theft, SQL Injection can serve as a pivot point for deeper compromise. Database features like xp_cmdshell in SQL Server or LOAD_FILE and INTO OUTFILE in MySQL allow an attacker who has achieved SQL Injection to execute operating system commands and read or write files on the server. This can escalate a web application vulnerability into full server compromise.

The regulatory consequences of a SQL Injection breach are severe. Under GDPR, a breach involving personal data can result in fines of up to 4% of annual global revenue. PCI DSS mandates that organizations processing credit card data protect against injection attacks, and a breach caused by SQLi will almost certainly result in loss of PCI compliance, fines, and increased processing fees.

Best Practices

  • Use parameterized queries everywhere. This is the single most effective defense against SQL Injection. Every database driver in every language supports parameterized queries. There is never a valid reason to construct SQL queries through string concatenation with user input.
  • Use an ORM for routine database operations. Object-relational mappers generate parameterized queries automatically, reducing the surface area for SQLi to the rare cases where raw SQL is needed.
  • Validate and sanitize all input. Even with parameterized queries, validate that user input matches expected formats. An age field should accept only integers, an email field should match email patterns, and a sort column should be compared against an allowlist.
  • Apply the principle of least privilege to database accounts. The database user your application connects with should have only the permissions it needs. A read-only endpoint should use a read-only database account so that even a successful SQLi cannot modify data.
  • Deploy SAST tools configured to detect SQLi patterns. Tools like Semgrep can catch string concatenation in SQL queries during code review, preventing vulnerable code from reaching production.

Common Mistakes

  • Using blocklist-based input filtering instead of parameterized queries. Stripping single quotes or blocking the word “SELECT” from input is trivially bypassed with encoding tricks, case variations, and alternative syntax. Parameterized queries are the only reliable defense.
  • Trusting hidden form fields or cookies as “safe” input. Attackers control everything the browser sends. Hidden fields, cookies, HTTP headers, and URL parameters are all attacker-controlled input that must be parameterized.
  • Assuming ORMs make SQL Injection impossible. ORMs protect against SQLi in their normal usage patterns, but most ORMs also provide methods for executing raw SQL. These raw query methods are just as vulnerable as bare database drivers if used with string concatenation.
  • Failing to test stored procedures for SQLi. Stored procedures that use dynamic SQL internally can be vulnerable to SQL Injection even when the application calls them with parameterized inputs. Audit stored procedure code with the same rigor as application code.

Related Terms

Learn More

Related Articles

Free Newsletter

Stay ahead with AI dev tools

Weekly insights on AI code review, static analysis, and developer productivity. No spam, unsubscribe anytime.

Join developers getting weekly AI tool insights.