The sneaky vulnerability that bypasses prepared statements.
We all know that Prepared Statements (Parameterization) stop SQL injection in `WHERE` clauses (e.g. `WHERE username = ?`). However, you cannot parameterize table names, column names, or the ORDER BY direction (ASC/DESC) in most SQL dialects. If you allow users to sort a data table by passing a column name in the URL (`?sort=price`), and you concatenate that directly into the `ORDER BY` clause, you are vulnerable to SQL Injection, even if you use an ORM.
Because `ORDER BY` cannot be parameterized, the only defense is to strictly whitelist the allowed column names in your application code before injecting them into the query.
# VULNERABLE: Direct concatenation of column name
sort_col = request.args.get('sort') # E.g., 'price'
# DANGER: Attackers can inject '?sort=(CASE WHEN ...)'
query = f"SELECT * FROM products ORDER BY {sort_col}"
# SECURE: Strict Whitelisting
sort_col = request.args.get('sort')
ALLOWED_COLUMNS = ['price', 'name', 'date_added']
if sort_col not in ALLOWED_COLUMNS:
sort_col = 'date_added' # Safe fallback
query = f"SELECT * FROM products ORDER BY {sort_col}"
A data-table in an admin panel shows a list of users. Clicking a column header changes the URL to `/users?sort=email`. An attacker changes the URL to `/users?sort=extractvalue(1,concat(0x7e,(SELECT version())))`. Because the backend concatenates this into the `ORDER BY` clause, MySQL attempts to extract XML using the database version number. It crashes and returns an error: `XPATH syntax error: '~8.0.21'`. The attacker has successfully extracted the database version directly into the error log.
Why can't you just use a Prepared Statement (e.g. `ORDER BY ?`) to fix this vulnerability?