If you’re a coder, you might not keep up with the latest vulnerabilities and data breaches. One of the most common exploits is SQL injection. PHP developers often build SQL queries using dynamic strings, and then send those strings to the database server. Without scrubbing malicious code from a SQL statement, you could be introducing a severe vulnerability to your code and your client sites.

SQL Server developers are taught from the beginning to never use dynamic SQL queries, but it’s common in PHP. Developers working with SQL Server use stored procedures. Prepared statements in PHP are the equivalent of stored procedures. PHP developers introduced the PHP Data Objects (PDO) class to PHP in 2005, so every PHP developer should move to prepared statements for the security of applications. 

SQL Injection in PHP Explained Using a SQL Query Concatenation String

SQL injection affects any coding language, but this content is focused on PHP specifically. PHP developers build dynamic queries using string concatenation functions. To illustrate, here is an example of building a SQL query using string concatenation:

$table_name = "customers";
$id = “cust1”;
$sql_query = "SELECT *  FROM " . $table_name . " WHERE id=’" . $id . "’;”;

The above is a simple query where a customer with an ID of “custid” is retrieved from the customers table. As you can see, the concatenation can be used to dynamically build a query. Developers usually work with this type of query building to take input from users to determine the data returned from the database. Even if it doesn’t take user input, the dynamically created variables built in a query string should be protected from SQL injection possibilities, especially if you change the code in the future. This example hard codes variables, but SQL injection is a large risk when you take any input to form variable values.

Let’s take a look at the same statement but the $id variable is retrieved from a server POST request:

$table_name = "customers";
$id = $_POST[“id”];
$sql_query = "SELECT *  FROM " . $table_name . " WHERE id=’" . $id . "’;”;

Notice that the $id is now retrieved from a POST request, but the id variable could be anything. Let’s say that a hacker sends a POST request that looks like this:

POST /form-submit HTTP/1.1
Host: mysite.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Content-Type: application/x-www-form-urlencoded
Content-Length: 28 
Connection: keep-alive
Accept: */*

id=%27%20or%201%3D1%20--

Note that the server request body is encoded, but it translates to:

id=' or 1=1 --

The above is a server header sent in every form request. You don’t see it from the user interface, but the backend sends this server request for every form submitted on your site. Now let’s plug in the malicious code sent to our PHP file named form-submit.php that retrieves the server request and prints the output:

$table_name = "customers";
$id = $_POST[“id”];
$sql_query = "SELECT *  FROM " . $table_name . " WHERE id=’" . $id . "’;”;
echo $sql_query;

The text in a user’s browser would then show:

SELECT * FROM customers WHERE id=’' or 1=1 --’

The result is that all customers stored in the customer table would be returned to the attacker, because the logic says to return a row if 1=1, which of course is always true. If you wonder what could happen with vulnerable code, check out my write-up on a recent SQL injection vulnerability in CVE-2026-0683 found in the WordPress plugin SupportCandy.

Using Prepared Statements in PHP

The PDO class is available to PHP developers to limit risk of SQL injection. It’s especially useful for WordPress plugin developers because most plugins take user input for site customizations. First, we need to build the database connection string and send it to the PDO object. Here is a simple PDO connection example:

$host = '127.0.0.1';
$db   = 'database_name';
$user = 'username';
$pass = 'password';
$dsn = "mysql:host=$host;dbname=$db;";

$pdo = new PDO($dsn, $user, $pass);

The above code is self-explanatory. You need a database name, location (in this case localhost), and a username and password. The $dsn variable holds the connection string, and then the connection is sent to the PDO object when it’s instantiated. With this code, we can now call a method in the PDO class named prepare.

Using the same example from the previous section, now let’s rewrite the SQL query for a prepared statement:

$table_name = "customers";
$id = $_POST[“id”];
$sql = “SELECT * FROM “ . $table_name . “ WHERE id= :id;”;
$stmt = $pdo->prepare($sql);

$stmt->execute([
    ':id' => $id
]);

$results = $stmt->fetchAll();

The result is that any malicious code would not be translated into SQL syntax but return an error, or it would treat the text as actual query content. The tick marks and hyphens used as malicious SQL injection code would not be used as the statement but instead be used to query the customer ID, which would then return no results.

The $results variable contains table rows after running the query. Malicious SQL statements would return no rows, but a legitimate query would return multiple rows. To complete the process, here is an example of iterating over all the returned records:

foreach ($results as $row) {
    echo $row['cust_name'] . "<br>";
}

The above code assumes that the customer name is stored in the cust_name field, so your code may be different. The prepared statement handles all the work and retrieves variable values as literal values. PDO does not protect against the numerous other types of attacks like Persistent XSS where an attacker stores malicious JavaScript or HTML in your database, so you need to consider adding protection like the sanitize_text_field  function for removing malicious scripts.

Beyond PHP Secure Coding to Prevent SQL Injection

Always code with security in mind, but site owners can increase their SQLi protection using CloudFlare. CloudFlare is a web application firewall (WAF) that protects from SQL injection and Cross-Site Scripting (XSS) attacks. It detects malicious form input and blocks it from reaching your site.

If you run WordPress, you can add Wordfence to the site as a plugin. This plugin also protects against SQL injection and other known attacks. Coding with security in mind and integrating CloudFlare will greatly reduce your risk of a data breach from SQLi. Adding Wordfence in the mix on a WordPress site reduces your risk even more.

If you’d like to keep up with my security musings, sign up to my newsletter to get more about the latest data breaches and CVEs in the news.

Join Our Newsletter
get weekly access to the latest hacks, tricks, and updates

Categorized in: