Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
Description
SQL Injection is a vulnerability that occurs when software constructs all or part of an SQL command using externally-influenced input from an upstream component, but does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command. Attackers exploit this by inserting malicious SQL code into input fields, URL parameters, cookies, or HTTP headers that are incorporated into database queries. This enables unauthorized access to database contents, modification or deletion of data, execution of administrative operations, and in some cases, command execution on the underlying operating system. SQL injection consistently ranks as one of the most dangerous and prevalent web application vulnerabilities.
Risk
SQL injection poses catastrophic risks to data confidentiality, integrity, and availability. Attackers can bypass authentication, extract entire database contents including credentials and personal information, modify or delete records, escalate privileges to database administrator level, and potentially execute operating system commands through database features like xp_cmdshell in SQL Server. The Heartland Payment Systems breach exposed 130 million credit card numbers, while the TalkTalk attack compromised 157,000 customer records. The vulnerability affects any application that constructs SQL queries from user input, spanning web applications, mobile backends, and APIs. According to OWASP, SQL injection shares the top position among web application security risks and is typically one of the first attack methods criminals attempt.
Solution
Use parameterized queries (prepared statements) exclusively for all database operations. Parameterized queries separate SQL code from data, making injection impossible regardless of input content. In languages with ORM frameworks (Entity Framework, Hibernate, SQLAlchemy), use the ORM's query builders rather than raw SQL. If dynamic SQL is absolutely necessary, use strict allowlist validation for structural elements (table names, column names, sort directions) and proper parameterization for values. Implement least privilege for database accounts - application accounts should only have permissions necessary for their function. Deploy web application firewalls as defense in depth, but never rely on them as the primary defense.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Attackers can extract entire database contents including user credentials, personal information, financial data, and proprietary business information through UNION-based or blind SQL injection techniques. |
| Integrity | Scope: Integrity SQL injection enables insertion, modification, or deletion of database records. Attackers can modify prices, change account balances, alter user privileges, or corrupt critical data. |
| Availability | Scope: Availability Attackers can delete tables, corrupt databases, execute resource-intensive queries causing denial of service, or deploy ransomware through database server access. |
| Access Control | Scope: Access Control, Full Compromise Authentication bypass, privilege escalation to DBA, and potential OS command execution through database features can lead to complete system compromise. |
Example Code + Solution Code
Vulnerable Code
import sqlite3
from flask import Flask, request
app = Flask(__name__)
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
# VULNERABLE: String concatenation in SQL query
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# Attacker input: username=' OR '1'='1'-- password=anything
# Results in: SELECT * FROM users WHERE username='' OR '1'='1'--' AND password='anything'
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
user = cursor.fetchone()
if user:
return "Login successful"
return "Invalid credentials"
@app.route('/search')
def search():
product = request.args.get('product')
conn = sqlite3.connect('products.db')
cursor = conn.cursor()
# VULNERABLE: User input directly in query
# Attack: product='; DROP TABLE products;--
cursor.execute(f"SELECT * FROM products WHERE name LIKE '%{product}%'")
return str(cursor.fetchall())
Fixed Code
import sqlite3
from flask import Flask, request, abort
import re
app = Flask(__name__)
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username', '')
password = request.form.get('password', '')
# Input validation (defense in depth, not primary defense)
if not username or not password:
abort(400, 'Missing credentials')
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# SAFE: Parameterized query - SQL and data are separated
# The ? placeholders are replaced with properly escaped values
query = "SELECT * FROM users WHERE username = ? AND password = ?"
cursor.execute(query, (username, password))
user = cursor.fetchone()
conn.close()
if user:
return "Login successful"
return "Invalid credentials", 401
@app.route('/search')
def search():
product = request.args.get('product', '')
# Validate input length
if len(product) > 100:
abort(400, 'Search term too long')
conn = sqlite3.connect('products.db')
cursor = conn.cursor()
# SAFE: Parameterized query with LIKE
# The % wildcards are part of the parameter value, not the query structure
search_term = f'%{product}%'
cursor.execute("SELECT * FROM products WHERE name LIKE ?", (search_term,))
results = cursor.fetchall()
conn.close()
return str(results)
@app.route('/sort')
def sort_products():
# For dynamic table/column names, use allowlist validation
sort_column = request.args.get('sort', 'name')
sort_direction = request.args.get('dir', 'ASC')
# Allowlist validation for structural elements
allowed_columns = ['name', 'price', 'date_added']
allowed_directions = ['ASC', 'DESC']
if sort_column not in allowed_columns:
sort_column = 'name'
if sort_direction.upper() not in allowed_directions:
sort_direction = 'ASC'
conn = sqlite3.connect('products.db')
cursor = conn.cursor()
# Safe because column/direction are from allowlist
query = f"SELECT * FROM products ORDER BY {sort_column} {sort_direction}"
cursor.execute(query)
results = cursor.fetchall()
conn.close()
return str(results)
The fixed code uses parameterized queries where ? placeholders are replaced with properly escaped values, making injection impossible. For structural elements like column names that cannot be parameterized, strict allowlist validation ensures only known-safe values are used.
Exploited in the Wild
Heartland Payment Systems (Payment Industry, 2008)
SQL injection was used to breach Heartland Payment Systems, one of the largest payment processors in the US. Attackers led by Albert Gonzalez compromised approximately 130 million credit and debit card numbers, with total losses estimated at $300 million across affected financial institutions. Gonzalez was sentenced to 20 years in prison for what was at the time the largest data breach in US history.
- https://news.sophos.com/en-us/2018/02/19/hackers-sentenced-for-sql-injections-that-cost-300-million/
Sony PlayStation Network (Sony, 2011)
SQL injection attacks against Sony's PlayStation Network and related services compromised approximately 77 million user accounts, exposing personal information including names, addresses, email addresses, birthdates, passwords, and potentially credit card data. The attack cost Sony an estimated $170 million and resulted in a month-long service outage affecting millions of gamers worldwide.
TalkTalk Data Breach (TalkTalk, 2015)
A SQL injection attack on British telecommunications company TalkTalk compromised personal data of 156,959 customers. The attack exploited a vulnerability in a legacy web portal, resulting in theft of names, addresses, dates of birth, phone numbers, email addresses, and in some cases financial information. TalkTalk was fined £400,000 by the ICO and faced costs exceeding £77 million.
Tools to test/exploit
-
SQLMap — automated SQL injection detection and exploitation tool supporting multiple database platforms, with capabilities for data extraction, database fingerprinting, and OS command execution through database features.
-
Burp Suite — web security testing platform with SQL injection scanning, manual testing capabilities, and payload customization for various database types.
-
jSQL Injection — Java-based SQL injection tool with automatic database detection and multiple injection techniques including error-based, blind, and time-based.
CVE Examples
-
CVE-2017-5638 — Apache Struts vulnerability (Equifax breach) enabling SQL injection through Content-Type header manipulation.
-
CVE-2023-34362 — MOVEit Transfer SQL injection leading to authentication bypass and data theft affecting thousands of organizations.
-
CVE-2023-22518 — Atlassian Confluence SQL injection enabling unauthorized database access.
-
CVE-2024-23897 — Jenkins arbitrary file read leading to credential theft and subsequent SQL injection attacks.
References
-
MITRE. "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')." https://cwe.mitre.org/data/definitions/89.html
-
OWASP. "SQL Injection." https://owasp.org/www-community/attacks/SQL_Injection
-
OWASP. "SQL Injection Prevention Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
-
PortSwigger. "SQL injection." Web Security Academy. https://portswigger.net/web-security/sql-injection