Current situation: PHP2Shell. Two critical PHP vulnerabilities (CVSS 9.2) allow malicious code without login. Patches since 21 October 2026.What to do now →

Preventing SQL injection in PHP

How developers reliably rule out SQL injection in PHP applications with prepared statements, whitelists and minimal database permissions.

Last updated 5 min readGuides

In short: You reliably prevent SQL injection in PHP by never building values into SQL strings and always passing them through prepared statements with placeholders. For column and table names, which cannot be passed as placeholders, a fixed whitelist applies. In addition, a database user with minimal permissions limits the damage if a vulnerability does remain.

Why SQL injection must not drop out of view after PHP2Shell

In October 2026 a lot revolves around PHP2Shell, two critical vulnerabilities in the PHP interpreter, fixed since 21 October 2026 in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. In those days many teams updated servers and restarted processes. That is right and urgent.

An up-to-date PHP state, however, says nothing about your own code. SQL injection has been among the most common weaknesses in web applications for years and is listed in the OWASP Top 10 under the Injection category. Anyone who is planning security checks anyway should look at the database access of their own application at the same time.

The good news: SQL injection is one of the few weaknesses that can be almost completely ruled out with a simple, consistently applied rule.

How does SQL injection arise in PHP?

SQL injection arises when an application treats user input as part of an SQL command instead of as pure data. Typical is assembling a query from text fragments and variables. If a visitor can determine the content of such a variable, they may be able to change the meaning of the command.

The consequences range from reading other customers' data, through changing prices or permissions, to deleting entire tables. Depending on the database and permissions, a vulnerability can also enable access beyond the database. If personal data is affected, a reporting duty under the GDPR may arise.

Input is not only form fields. URL parameters, cookies, HTTP headers, uploaded files, responses from third-party interfaces and data already stored in your own database are also regarded as untrusted.

Prepared statements with PDO and mysqli

Prepared statements send the SQL command and the values to the database separately. The values are never interpreted as part of the command. With PDO, a safe query looks like this:

<?php
$pdo = new PDO($dsn, $benutzer, $passwort, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES   => false,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

$stmt = $pdo->prepare('SELECT id, name, email FROM kunden WHERE id = :id AND status = :status');
$stmt->execute([
    'id'     => (int) $_GET['id'],
    'status' => 'aktiv',
]);
$kunde = $stmt->fetch();

The option PDO::ATTR_EMULATE_PREPARES => false ensures that the database uses real prepared statements, as far as the driver supports it. With mysqli the same principle applies:

<?php
$stmt = $mysqli->prepare('SELECT id, name FROM produkte WHERE kategorie = ? AND preis <= ?');
$stmt->bind_param('sd', $kategorie, $maxPreis);
$stmt->execute();
$ergebnis = $stmt->get_result();

Lists for IN (...) can also be handled safely by creating a separate placeholder for each value and passing the values separately.

<?php
$ids = array_map('intval', $ausgewaehlteIds);
$platzhalter = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT id, name FROM produkte WHERE id IN ($platzhalter)");
$stmt->execute($ids);

Special cases: column names, sorting and raw SQL

Placeholders work only for values, not for table names, column names or sort directions. This is exactly where many vulnerabilities arise in practice, for example with sortable tables or filters. The solution is a fixed list of permitted values:

<?php
$erlaubteSpalten = ['name' => 'name', 'preis' => 'preis', 'datum' => 'erstellt_am'];
$spalte   = $erlaubteSpalten[$_GET['sort'] ?? ''] ?? 'name';
$richtung = (($_GET['dir'] ?? '') === 'desc') ? 'DESC' : 'ASC';

$stmt = $pdo->query("SELECT id, name, preis FROM produkte ORDER BY $spalte $richtung");

Frameworks and CMS bring their own tools. In WordPress, $wpdb->prepare() takes on this task; in Laravel and Symfony, the query builders and ORMs do. As soon as you use raw SQL there, for example through functions with “raw” in the name, the rules of this section apply again. Notes on Laravel are at Securing Laravel applications.

Tip

Search the code specifically for SQL strings into which variables are inserted. A simple start is a search for query( and ->raw in combination with $_GET, $_POST or $_REQUEST.

Tracking down SQL injection in existing code

With new software, the rule can be kept from the start. It is harder with applications that have grown over years. There it is worth searching systematically for places where SQL strings are assembled with variables:

# SQL commands into which variables are inserted
grep -rnE "(SELECT|INSERT|UPDATE|DELETE)[^;]*\\\$[a-zA-Z_]" src/ --include="*.php" | head -50
# Direct use of request data in query functions
grep -rnE "(query|exec|raw)\s*\(.*\\\$_(GET|POST|REQUEST|COOKIE)" src/ --include="*.php"

Not every hit is a vulnerability, for example when the variable contains only a fixed table name. Every finding should nevertheless be checked consciously once and, where necessary, converted to prepared statements. Static code analysis tools help to detect such patterns permanently in the build pipeline, so that new mistakes never go live in the first place.

Limiting the damage: permissions, error messages, review

Secure code is the first line of defence. Further layers limit the damage if a vulnerability is overlooked after all:

  • Minimal permissions: The website's database user usually needs only SELECT, INSERT, UPDATE and DELETE on its own database, no permissions to create users or to access other databases.
  • No error messages to the outside: display_errors = Off in php.ini. Database errors belong in the log, not in the browser.
  • Separation per application: Every website gets its own database user.
  • Keep dependencies current: Vulnerabilities also lurk in libraries, as the guide composer audit shows.
  • Have it reviewed: Code review and automated analysis find many places; a penetration test shows what is actually reachable from outside.
-- Example: database user with only the necessary permissions
CREATE USER 'shop_app'@'localhost' IDENTIFIED BY '<starkes Passwort>';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'localhost';

A related weakness concerns the output of data in the browser. How to secure it is explained in the guide Preventing cross-site scripting in PHP.

How does PHP SOS help?

  • We systematically check your application's database access for unsafe queries.
  • We prioritise findings by reachability and affected data and show concrete fixes.
  • We bring database permissions and error handling to a secure level.
  • We test again after the fix and document the result.

An independent review is described in the service Security audit and penetration test.

Frequently asked questions

Is it enough to treat input with addslashes or real_escape_string?

No, that is error-prone. Escaping depends on the character set and the context and is easily forgotten in one place. Prepared statements separate command and data by design and are therefore the recommended route.

Does an ORM protect against SQL injection automatically?

Largely, as long as you use the ORM's query functions. As soon as you write raw SQL or insert values into sort orders and column names, the same rules apply as without an ORM.

Does a web application firewall help against SQL injection?

A WAF can catch known patterns and buy time. But it does not replace secure code, because rules based on request data can be bypassed and sometimes hit legitimate input.

Does the PHP update against PHP2Shell also close SQL injection vulnerabilities?

No. PHP2Shell affects the PHP interpreter. SQL injection is a flaw in application code and remains regardless of which PHP version is running.

Sources

Immediate contact

Every hour counts. Write to us now.

Briefly describe your system, PHP version and observation. We will get back to you, clarify the situation and work remotely until the incident is closed and documented.

Immediate help