Blog

Web Security articles

SQL Injection: How It Works and How to Prevent It

How SQL injection attacks work, why they still succeed, and how to prevent them with parameterised queries, least privilege and input validation.

3 min read Web Security

SQL injection has been understood for more than two decades, yet it still appears in breach reports every year. It lets an attacker read, change or delete data in your database by typing carefully crafted text into an ordinary form field or web address. The good news is that it is almost entirely preventable with a few well-established habits.

How SQL injection works

Most web applications build database queries that include something the user typed. The danger comes when that input is glued directly into the SQL text. Consider a login check written like this:

$sql = "SELECT * FROM users WHERE email = '" . $_POST['email'] . "'
        AND password_hash = '" . $hash . "'";

A normal user types anna@example.com and the query works as intended. An attacker instead types:

' OR '1'='1' -- 

The query now reads WHERE email = '' OR '1'='1' -- ' AND .... The condition is always true, and the double dash turns the rest of the line, including the password check, into a comment. The application logs the attacker in as the first user in the table, often an administrator.

The same flaw in a search box, a product filter or an ID in the address bar can let an attacker list every table in the database, extract customer records, or in some configurations modify data and run commands on the server.

Why it still happens

  • Older code written before safer database libraries were common, still running unchanged.
  • Quick fixes and reports where a developer builds a query by hand "just this once".
  • Dynamic sorting and filtering, where column names come from the user and cannot be passed as ordinary parameters.
  • Plugins and extensions in content management systems, written to varying standards.

The main defence: parameterised queries

The reliable fix is to never mix data into the SQL text. Instead, send the query with placeholders and pass the values separately. The database treats the values strictly as data, so there is nothing an attacker can type that changes the structure of the query.

// PHP with PDO
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$_POST['email']]);
$user = $stmt->fetch();
# Python with a DB-API driver
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))

Every mainstream language and database driver supports this, and modern frameworks and ORMs such as Laravel's Eloquent, Django's ORM and Entity Framework use parameters automatically. The risk returns only when developers bypass them with raw query strings built from input.

When parameters are not enough

Placeholders work for values, not for identifiers such as table names, column names or sort direction. If a user can choose the sort column, check it against a fixed list instead of inserting it directly:

$allowed = ['name', 'created_at', 'price'];
$column  = in_array($_GET['sort'], $allowed, true) ? $_GET['sort'] : 'name';
$dir     = ($_GET['dir'] ?? '') === 'desc' ? 'DESC' : 'ASC';
$sql     = "SELECT * FROM products ORDER BY $column $dir";

This allow-list approach means only values you wrote yourself ever reach the SQL text.

Layers that limit the damage

Parameterised queries prevent the attack; these measures reduce the harm if a mistake slips through somewhere:

  • Least privilege. The application's database account should have only the permissions it needs. A reporting page does not need to drop tables, and no web application should connect as the database administrator.
  • Input validation. Check that an ID is a number, an email looks like an email and a date is a date. Validation is not a substitute for parameters, but it rejects a lot of junk early.
  • Generic error messages. Detailed database errors shown to visitors help attackers map your schema. Log the details; show the user a plain message.
  • A web application firewall can block many common injection patterns, buying time while code is fixed.
  • Encryption and hashing. Passwords should be stored with a slow hashing algorithm such as bcrypt or Argon2, so a leaked table does not give away the passwords themselves.

Finding injection flaws in existing code

Search the codebase for queries built with string concatenation or interpolation, especially where request data is involved. Automated scanners and dependency checkers help, and a periodic penetration test by someone outside the development team catches what familiarity hides. Pay particular attention to old admin pages, export features and anything written in a hurry.

Key takeaways

  • SQL injection happens when user input becomes part of the SQL text.
  • Parameterised queries prevent it by keeping data and code separate.
  • Use allow-lists for anything that cannot be a parameter, such as column names.
  • Limit database permissions and hide error details so a single mistake cannot become a full breach.

Need help with this?

Netifi helps businesses around the world with Web Security. Tell us what you are working on.