Ocultar

Olvidaste tu contraseña?

Olvidaste tu contraseña? Por favor inserta tu correo y recibiras un link para crear una nueva contraseña.

Error message here!

In a entrar de nuevo

Olvidaste tu contraseña?

Registrate rápido con face, twitte, google, github
o usa tu correo electronico

Registrate!!

Close

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

+3 votos
Dic 22 2016 en Programación general por alvaro (9 puntos)
Dic 22 2016

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 todas las entradas, como por ejemplo:

$id_usuario = $_POST["id"];

mysql_query("SELECT * FROM usuarios WHERE id = $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?

1 Respuesta

+5 votos
Dic 22 2016 por adriano (50 puntos)
Mar 27 2017
 
Mejor respuesta

NO USES SENTENCIAS DINÁMICAS NI FUNCIONES mysql_*

Las funciones mysql* (mysqlconnect, 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 mysqlrealescape_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 mysqlirealescape_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 para el código de la pregunta:

// la variable $pdo contendrá el objeto con la conexión PDO
$pdo = new PDO('mysql:host=mihost;dbname=basedatos', "usuario", "contraseña");

$idusuario = $POST["id"];

$sentencia = $pdo->prepare("SELECT * FROM usuarios WHERE id = :idusuario");
$sentencia->bindParam(":idusuario", $idusuario, PDO::PARAMINT);
$sentencia->execute();
En este caso, :idusuario se sustituirá por el valor de $POST["id"] de forma segura, y cuando hace el bind se indica que la variable es de tipo entero (PDO::PARAMINT).

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.
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("mihost", "usuario", "contraseña", "basedatos");

$idusuario = $POST["id"];

$sentencia = $mysqli->prepare("SELECT * FROM usuarios WHERE id = ?");
$sentencia->bindparam("i", $idusuario );
$sentencia->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("mihost", "usuario", "contraseña", "basedatos");

$idusuario = $POST["id"];

$sentencia = mysqliprepare("SELECT * FROM usuarios WHERE id = ?");
mysqli
stmtbindparam($sentencia, "i", $idusuario);
mysqli
stmt_execute($sentencia);

...