SQL injection (SQLi) occurs when untrusted input is interpreted as part of a SQL command rather than strictly as data.
## Main classes by how results are obtained
1. **In-band SQL injection**
The attacker injects SQL and receives results through the same application channel.
- **Error-based:** Deliberately triggers database errors that reveal schema, query, or data details.
- **UNION-based:** Appends a compatible `UNION SELECT` to make the application return data from other tables.
2. **Blind / inferential SQL injection**
The application does not display database output directly, so information is inferred one bit or condition at a time.
- **Boolean-based:** Responses differ depending on whether an injected condition is true or false.
- **Time-based:** Conditional database delays reveal whether a condition is true. This may work even when pages look identical.
3. **Out-of-band SQL injection**
The database is induced to communicate through another channel, such as DNS or an outbound network request. This depends on database features, privileges, and network access.
## Other useful classifications
- **Stacked-query injection:** An input terminates the intended statement and adds another statement. Driver and database support varies.
- **Second-order injection:** Malicious input is stored safely at first, but later incorporated unsafely into another SQL statement.
- **Stored-procedure or dynamic-SQL injection:** Procedures are not inherently safe; constructing SQL strings inside them remains vulnerable.
- **Injection by context:** Input may enter string, numeric, identifier, sorting, table-name, or query-structure positions. Parameterization handles data values, but identifiers and keywords usually require strict allowlists.
## Simple conceptual example
Unsafe:
```pseudo
sql = "SELECT * FROM users WHERE email = '" + userInput + "'"
db.execute(sql)
```
Safe:
```pseudo
sql = "SELECT * FROM users WHERE email = ?"
db.execute(sql, [userInput])
```
## What developers should do
- Use **parameterized queries / prepared statements** everywhere.
- Never build SQL by concatenating, interpolating, or formatting user-controlled values.
- For identifiers or structural options such as column names and sort direction, use a fixed **allowlist** and map external choices to hard-coded SQL fragments.
- Avoid dynamic SQL; if unavoidable, parameterize values inside it.
- Give application database accounts **least privilege**; separate read and write roles where practical.
- Return generic errors to users and keep detailed database errors in protected logs.
- Validate input as a secondary control, not as a substitute for parameterization.
- Restrict database outbound network access to reduce out-of-band risk.
- Test both immediate and second-order paths with code review, SAST/DAST, and authorized security testing.
- Do not rely on escaping, a WAF, or stored procedures alone as the primary defense.