php

Las sentencias dinámicas son sentencias SQL que se crean como cadenas de texto (strings) y en las que se insertan/concatenan valores obtenidos de alguna fuente (normalmente proveniente del usuario), lo que puede hacer que sean vulnerables a inyección SQL si no se sanean las entradas, como por ejemplo:

$id_usuario = $_POST["id_usuario"];

mysql_query("SELECT * FROM usuarios WHERE id_usuario = $id_usuario");

Eso es un ejemplo de una vulnerabilidad grave en la seguridad de una aplicación (web o no) porque si el usuario introdujese un valor como

1; DROP TABLE usuarios;

nos encontraríamos con que la sentencia ejecutada sería:

SELECT * FROM usuarios WHERE id = 1; DROP TABLE usuarios;

Y se eliminaría la tabla Usuarios con todos los datos contenidos en ella.

¿Cómo puedo evitar que la inyección SQL ocurra en PHP?

NO USES SENTENCIAS DINÁMICAS NI FUNCIONES mysql_*

Las funciones mysql_* (mysql_connect, mysql_query, etc.) son inseguras por naturaleza y su uso no sólo no está recomendado, sino que se consideran obsoletas y se han eliminado completamente a partir de PHP7.

Incluso los métodos nativos que existen en PHP para sanear las entradas de usuario (como mysql_real_escape_string) pueden presentar (raros) problemas y fallar en algunos casos como cuando se usan codificación de caracteres diferentes a UTF-8 junto a versiones no actualizadas de MySQL (en las páginas de PHP para estas funciones se avisa de este riesgo).

Usa sentencias preparadas y consultas parametrizadas

Aunque se podrían sanear las entradas usando métodos como mysqli_real_escape_string, es más recomendable la utilización de sentencias preparadas o parametrizadas. Las sentencias preparadas te permitirán ejecutar la misma sentencia con gran eficiencia.

En PHP, tienes dos alternativas principales: PDO y MySQLi. Hay varias diferencias entre ambas, pero la principal es que PDO se puede usar con diferentes tipos de base de datos (dependiendo del driver utilizado) mientras que MySQLi es exclusivamente para bases de datos MySQL. Es por ello que recomendaría PDO sobre MySQLi.

PDO

Los marcadores de posición (que indican dónde se sustituirá una cadena por su valor), se pueden definir bien usando un signo de interrogación (?) o bien usando un nombre (generalmente empezando con :). Personalmente prefiero usar un nombre, porque eso me ayuda a encontrar posibles errores en caso de tener múltiples variables.

Aquí dejo un ejemplo:

// la variable $pdo contendrá el objeto con la conexión PDO 
$pdo = new PDO('mysql:host=localhost;dbname=basedatos', "usuario_db", "password_db"); 
$id_usuario = $_POST["id_usuario"]; 
$query = $pdo->prepare("SELECT * FROM usuarios WHERE id = :id_usuario"); 
$query=$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 
$query->bindParam(":id_usuario", $id_usuario, PDO::PARAM_INT); 
$query->execute();

En este caso, :idusuario se sustituirá por el valor de $_POST[“id_usuario”] de forma segura, y cuando hace el bind se indica que la variable es de tipo entero (PDO::PARAM_INT).

Nota: si la variable es una cadena de texto se usará PDO::PARAM_STR y no hace falta poner las comillas en la sentencia SQL; al especificarle a PHP que es una cadena, las añadirá automáticamente al hacer el bind.

En caso de que existan varias variables a incluir en la sentencia SQL, se debe incluir un único parámetro para cada uno de los valores que se usan en la sentencia. Del ejemplo anterior, el :id_usuario puede usarse una única vez en la consulta que se esta preparando. Si fuera necesario usar el “id_usuario” de nuevo en la consulta, se debe crear otro parámetro con el valor de $usuario_id.

$pdo = new PDO('mysql:host=localhost;dbname=basedatos', "usuario_db", "password_db"); 
$id_usuario = $_POST["id_usuario"]; 
$query = $pdo->prepare("UPDATE usuarios SET id_usaurio = :id_usuario WHERE id_usuario = :usuario_id"); 
$query=$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 
$query->bindParam(":id_usuario", $id_usuario, PDO::PARAM_INT); 
$query->bindParam(":usuario_id", $id_usuario, PDO::PARAM_INT); 
$query->execute();

MySQLi

Este método tiene dos interfaces: una procedural y otra orientada a objetos. La interfaz procedural es muy parecida a mysql_*, y por ello la gente que migra desde mysql_* puede sentirse atraída por la facilidad que mysqli_* ofrece. Aunque, de nuevo personalmente, optaría por la versión POO.

Nota: aunque las funciones mysqli_* suelen ser parecidas a las mysql_*, en algunos casos pueden tener diferentes parámetros de entrada o diferentes salidas, lo que puede llevar a algo de confusión al principio.

El ejemplo de la pregunta quedaría así con MySQLi en su interfaz orientada a objetos:

// en $mysqli tendremos la conexión MySQLi 
$mysqli = new mysqli("localhost", "usuario_db", "password_db", "basedatos"); 
$id_usuario = $_POST["id_usuario"]; 
$query = $mysqli->prepare("SELECT * FROM usuarios WHERE id_usuario = ?"); 
$query->bind_param("i", $id_usuario ); $query->execute();

Como se puede ver, es bastante parecido a PDO (cambia un poco cómo se especifica el tipo de valor, i para enteros y s para cadenas, pero la idea es similar).

En la versión procedural de MySQLi, el código equivalente sería:

// en $conn tendríamos la conexión a la base de datos con MySQLi 
$conn = mysqli_connect("localhost", "usuario_db", "password_db", "basedatos"); 
$id_usuario = $_POST["id_usuario"]; 
$query = mysqli_prepare("SELECT * FROM usuarios WHERE id_usuario = ?"); 
mysqli_stmt_bind_param($query, "i", $id_usuario); 
mysqli_stmt_execute($query);

Fuente y bibliografía para más información en español: