OWASP Web Top 10 — A03

SQL Injection

SQL Injection is one of the most dangerous and prevalent web application vulnerabilities. It allows attackers to interfere with the queries an application makes to its database, potentially giving them access to data they should never be able to retrieve, modify, or delete.

What Is SQL Injection?

SQL Injection (SQLi) is a code injection technique that exploits a security vulnerability in an application's database layer. It occurs when user input is incorrectly filtered or not properly parameterized before being included in SQL queries. When an application concatenates user-supplied data directly into SQL statements, an attacker can inject their own SQL commands that the database engine will execute as part of the legitimate query.

The vulnerability arises from a fundamental trust issue: the application treats user input as data, but the database engine cannot distinguish between the intended SQL code and the injected malicious code. This means that any input field, URL parameter, cookie, or HTTP header that eventually ends up in a SQL query can potentially serve as an injection point.

SQL Injection has been a top-ranked vulnerability in the OWASP Top 10 since its inception. Despite being well-understood and having straightforward prevention methods, it continues to be one of the most exploited vulnerabilities in the wild. According to OWASP, injection flaws (which include SQL Injection) were ranked as A03 in the 2021 edition of the Top 10, reflecting their ongoing prevalence and severity.

How It Works

1
Application accepts user input

The application presents a form, URL parameter, or other input mechanism that collects data from the user. This could be a login form, a search field, a product filter, or any other feature that accepts input.

2
Input is embedded into a SQL query

The application takes the user-supplied input and directly concatenates it into a SQL query string without proper sanitization or parameterization. For example, a login query might be built as: SELECT * FROM users WHERE username = ' + userInput + '.

3
Attacker crafts malicious input

Instead of providing a normal username, the attacker enters a specially crafted string such as ' OR '1'='1' --. This string is designed to alter the structure of the SQL query when it is concatenated.

4
Query structure is altered

The resulting SQL query becomes SELECT * FROM users WHERE username = '' OR '1'='1' --'. The injected OR '1'='1' condition is always true, and the -- comments out the rest of the query, effectively bypassing the authentication check.

5
Database executes the modified query

The database engine processes the modified query without any awareness that it has been tampered with. It returns all user records because the WHERE clause now evaluates to true for every row, granting the attacker unauthorized access.

Vulnerable Code Example

Vulnerable — Java / Spring Boot
@RestController
public class AuthController {

    @Autowired
    private DataSource dataSource;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest req) {
        Connection conn = dataSource.getConnection();

        // VULNERABLE: User input is directly concatenated
        // into the SQL query string
        String query = "SELECT * FROM users WHERE username = '"
            + req.getUsername() + "' AND password = '"
            + req.getPassword() + "'";

        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(query);

        if (rs.next()) {
            return ResponseEntity.ok(Map.of("user", rs.getString("username")));
        }
        return ResponseEntity.status(401).body("Invalid credentials");
    }
}

// An attacker can log in as any user by submitting:
// username: admin' --
// password: anything
//
// Resulting query:
// SELECT * FROM users WHERE username = 'admin' --'
// AND password = 'anything'
//
// The -- comments out the password check entirely.

Secure Code Example

Secure — Java / Spring Boot (PreparedStatement)
@RestController
public class AuthController {

    @Autowired
    private DataSource dataSource;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest req) {
        Connection conn = dataSource.getConnection();

        // SECURE: Use PreparedStatement with parameterized queries
        // User input is passed as parameters, never concatenated
        String query = "SELECT * FROM users WHERE username = ? AND password = ?";

        PreparedStatement pstmt = conn.prepareStatement(query);
        pstmt.setString(1, req.getUsername());
        pstmt.setString(2, req.getPassword());

        ResultSet rs = pstmt.executeQuery();

        if (rs.next()) {
            return ResponseEntity.ok(Map.of("user", rs.getString("username")));
        }
        return ResponseEntity.status(401).body("Invalid credentials");
    }
}

// With PreparedStatement, the database treats the
// input strictly as data, never as SQL code.
// Even if an attacker submits: admin' --
// The database looks for a user literally named: admin' --
// which does not exist, so the attack fails.

Types of SQL Injection

Classic (In-band) SQLi

The most common and straightforward type. The attacker uses the same communication channel to launch the attack and gather results. This includes UNION-based injection, where the attacker appends a UNION SELECT statement to extract data from other tables, and Error-based injection, where database error messages reveal information about the database structure. In-band SQLi is easy to exploit because the attacker receives immediate feedback.

Blind SQL Injection

Used when the application does not display database errors or query results directly. The attacker infers information by observing the application's behavior. Boolean-based blind SQLi sends queries that result in a true or false response, allowing the attacker to deduce data one bit at a time. Time-based blind SQLi uses database functions like SLEEP() or WAITFOR DELAY to cause a time delay, confirming whether a condition is true based on the response time.

Out-of-band SQLi

A less common technique used when the attacker cannot use the same channel for attack and data retrieval, or when server responses are too unstable for reliable inference. The attacker causes the database to make a connection to an external server they control, typically using DNS or HTTP requests. For example, using SQL Server's xp_dirtree or Oracle's UTL_HTTP to send data to an attacker-controlled domain. This technique requires specific database features to be enabled.

Impact

A successful SQL Injection attack can have devastating consequences for an organization. The severity depends on the database privileges, the data stored, and the attacker's objectives.

Data theft and exfiltration

Attackers can read sensitive data from the database, including user credentials, personal information, financial records, and proprietary business data. Entire tables can be dumped using UNION-based techniques or extracted character by character through blind injection.

Authentication bypass

Injection into login queries allows attackers to bypass authentication entirely, gaining access to administrative accounts or impersonating any user without knowing their password.

Data manipulation and destruction

Attackers can use INSERT, UPDATE, or DELETE statements to modify or destroy data. This can corrupt business records, alter financial transactions, or wipe entire databases.

Remote code execution

In some database configurations, SQL Injection can escalate to operating system command execution. Functions like xp_cmdshell in SQL Server or LOAD_FILE() and INTO OUTFILE in MySQL can be exploited to read/write files on the server or execute system commands, potentially leading to full server compromise.

Prevention Checklist

Use parameterized queries / prepared statements

This is the primary defense against SQL Injection. Parameterized queries ensure that user input is always treated as data, never as SQL code. Every major database library and framework supports them. There is no legitimate reason to use string concatenation for building SQL queries with user input.

Use ORM frameworks

Object-Relational Mapping frameworks like Sequelize, Prisma, SQLAlchemy, or Entity Framework abstract away raw SQL queries and use parameterized queries internally. While not immune to all injection attacks (especially when raw query methods are used), they significantly reduce the attack surface.

Validate and sanitize input

Implement strict input validation as a defense-in-depth measure. Use allowlists to restrict input to expected formats (e.g., numeric IDs should only contain digits). While input validation alone is not sufficient to prevent SQLi, it adds an additional layer of protection.

Principle of least privilege for database accounts

Configure database accounts used by the application with the minimum permissions necessary. The application should not connect to the database using an admin account. Restrict access to only the tables and operations the application needs, limiting the damage an attacker can do even if injection is successful.

Use a Web Application Firewall (WAF)

Deploy a WAF as an additional layer of defense. WAFs can detect and block common SQL Injection patterns in HTTP requests. While they should not be relied upon as the sole defense, they provide valuable protection against automated attacks and known injection patterns.

Regular security testing

Integrate automated SQL Injection testing into your CI/CD pipeline using tools like SQLMap, OWASP ZAP, or Burp Suite. Conduct regular penetration tests and code reviews with a focus on database interaction code. Static analysis tools (SAST) can also detect potential injection vulnerabilities during development.

Real-World Examples

2008

Heartland Payment Systems

One of the largest data breaches in history at the time. Attackers used SQL Injection to install malware on Heartland's payment processing systems, ultimately compromising approximately 130 million credit and debit card numbers. The breach cost Heartland over $140 million in compensatory payments to card companies.

2011

Sony Pictures

The hacktivist group LulzSec exploited SQL Injection vulnerabilities in Sony's websites, accessing databases containing unencrypted personal information of over one million users. The attackers publicly released the stolen data, including email addresses, passwords, and home addresses, exposing Sony's inadequate security practices.

2015

TalkTalk

The UK telecommunications company TalkTalk suffered a SQL Injection attack that compromised the personal data of approximately 157,000 customers, including bank account details and sort codes. The breach resulted in a record fine of 400,000 pounds from the UK Information Commissioner's Office and cost the company an estimated 77 million pounds in total.

2017

Equifax

The Equifax data breach exposed the personal information of 147 million people, including Social Security numbers, birth dates, and addresses. While the primary attack vector was an unpatched Apache Struts vulnerability, SQL Injection techniques were part of the broader attack chain used to extract data from Equifax's databases. The breach resulted in a $700 million settlement.

Ready to Test Your Knowledge?

Put what you have learned into practice. Try identifying and fixing SQL Injection vulnerabilities in our interactive coding challenges, or explore more security guides to deepen your understanding.