Recently I did security and pen-testing assessment of a web application written entirely in PHP, and some of the biggest issues I’ve come along were in-code vulnerabilities to 💉 SQL injections.
To fix this, I recommended using prepared statements and parameterized queries which are basically SQL statements that are sent to and parsed by the database server separately from any parameters. This way it is impossible for an attacker to inject malicious SQL.
You basically have two options to achieve this:
Connect to database with PDO in PHP
I’d recommend using PDO (PHP Data Objects) to run parameterized SQL queries as not only does this protect against SQL injection, but it also speeds up queries.
And by using PDO rather than mysql_
, mysqli_
, and pgsql_
functions, you make your application a little more abstracted from the database, in the rare occurrence that you have to switch database providers.
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
$stmt->execute([ 'name' => $name ]);
foreach ($stmt as $row) {
// Do something with $row
}
Using MySQLi
$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
$stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
// Do something with $row
}
If you’re connecting to a database other than MySQL, there is a driver-specific second option that you can refer to (for example, pg_prepare()
and pg_execute()
for PostgreSQL). PDO is the universal option.
PDO with Prepared Statements
When using PDO
to access a MySQL database prepared statements are not used by default. To fix this you have to disable the emulation of prepared statements. An example of creating a connection using PDO is:
$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
In the above example the error mode isn’t strictly necessary, but it is advised to add it. This way the script will not stop with a Fatal Error
when something goes wrong. And it gives the developer the chance to catch
any error(s) which are throw
n as PDOException
s.
What is mandatory, however, is the first setAttribute()
line, which tells PDO to disable emulated prepared statements and use real prepared statements. This makes sure the statement and the values aren’t parsed by PHP before sending it to the MySQL server (giving a possible attacker no chance to inject malicious SQL).
Although you can set the charset
in the options of the constructor, it’s important to note that ‘older’ versions of PHP (before 5.3.6) silently ignored the charset parameter in the DSN.
BONUS: SQL Injection Cheat Sheet
The SQL Injection Cheat Sheet published by Ferruh Mavituna is a great resource that will allow you to get the basic idea of a potential attack and almost every section includes brief information about itself.