x

x

Seguridad en PHP

En este capítulo vamos a explicar diferentes medidas de seguridad que podemos adoptar para aportar seguridad a nuestras aplicaciones PHP.

Seguridad en aplicaciones web

A medida que vayamos adquiriendo conocimiento y seguridad en la construcción de páginas web, ya sea con PHP+MySQL o con cualquier otro lenguaje de programación, podremos ir creando cada vez estructuras más complejas, en función de cuáles sean nuestras necesidades.

No obstante, existe una arquitectura común para todas las aplicaciones web, distribuida en tres niveles por sus diferentes funcionalidades.

Para empezar, tendremos que tener en cuenta el sistema de interfaz de usuario, es decir, la parte que gestiona la interacción entre nuestros usuarios y nuestro sistema, y que en una aplicación web típica es el navegador de Internet.

El siguiente nivel es donde reside la lógica de la aplicación. En esta parte están implicados los servidores web y los servidores de aplicaciones, que utilizarán diferentes tecnologías para crear conocimiento o procesar la información con un fin concreto.

En el tercer nivel se encuentra el almacén de datos, es decir, el repositorio de toda la información relativa a nuestra página web. Ejemplos de estos repositorios pueden ser un árbol LDAP, una base de datos relacional, un almacén con datos en XML o un simple fichero de datos.

Una vez conocidos los tres niveles, es obvio concluir que existe una relación funcional entre todos ellos. Por ejemplo, nuestros usuarios, mediante el envío de información de su navegador de Internet preferido, podrán interactuar con el servidor web donde tengamos alojada nuestra aplicación, que a su vez, tendrá una relación con nuestro almacén de datos, para extraer información o para escribir información del mismo.

Por lo tanto, cuando tengamos que tomar decisiones en lo relativo a la seguridad en nuestras aplicaciones, tendremos que pensar en soluciones para cada uno de estos niveles.

En este capítulo, apuntaremos algunas de las principales soluciones que podemos implementar mediante el lenguaje de programación PHP para aportar seguridad a nuestras aplicaciones.

Seguridad en PHP

Para comenzar a hablar de temas relativos a seguridad en el lenguaje de programación PHP, comenzaremos por referirnos a su intérprete. Al poder instalarlo como módulo de un servidor como Apache o bien como binario CGI, tendrá permisos para acceder a archivos, ejecutar comandos u otro tipo de operaciones que puedan ser realizadas en el servidor.

Éste será el primer problema de seguridad al que nos enfrentaremos.

En los siguientes apartados, explicaremos algunos consejos generales de seguridad, diferentes combinaciones de opciones de configuración y las situaciones en que pueden ser útiles, describiendo diferentes consideraciones relacionadas con la programación de acuerdo a diferentes niveles de seguridad.

Manejo de errores

PHP nos va a ofrecer la posibilidad de poder concretar la claridad con la que queremos que los errores nos sean ofrecidos por el intérprete. Esta tarea la podremos realizar, en primer lugar, conociendo los diferentes tipos de eventos de error que pueden ser producidos, y a continuación, pudiendo modificar estos tipos de eventos según nuestras necesidades. Por defecto, PHP ofrecerá informes sobre todos los tipos de errores que se puedan producir, excepto de los avisos.

Para poder consultar estos tipos de errores, podemos acudir a la siguiente tabla, donde veremos una serie de constantes definidas que podremos utilizar para variar este nivel de informes:

Valor Nombre Significado
1 E_ERROR Nos informará de los errores graves en tiempo de ejecución
2 E_WARNING Nos informará de los errores que no sean graves en tipo de ejecución
4 E_PARSE Nos informará de los informes de análisis
8 E_NOTICE Nos informará de los avisos, que serán notificaciones de que algo que hemos escrito es un error
16 E_CORE_ERROR Nos informará de los fallos que se produzcan al iniciar el motor de PHP
32 E_CORE_WARNING Nos informará de los fallos que no sean graves al arrancar el motor de PHP
64 E_COMPILE_ERROR Nos informará de los errores que se hayan producido en la tarea de compilación
128 E_COMPILE_WARNING Nos informará de los errores que no sean graves que se hayan producido en la tarea de compilación
256 E_USER_ERROR Nos informará de los errores que haya provocado el usuario
512 E_USER_WARNING Nos informará de las advertencias que pueda provocar el usuario
1024 E_USER_NOTICE Nos informará de los avisos que pueda provocar el usuario
2047 E_ALL Nos informará de todos los errores y advertencias que se puedan producir
2048 E_STRICT Nos informará del uso de un comportamiento obsoleto y que no es recomendado. No se incluirá en E_ALL pero resultará útiles para poder modificiar el factor del código.

En la anterior tabla, cada constante es el resultado de un tipo de error en concreto que podremos destacar o ignorar si lo estimamos oportuno. Si, por ejemplo, solo utilizamos el nivel de error E_ERROR, solo informaremos de los errores graves que se puedan producir.

Podremos realizar combinaciones de estas constantes para poder personalizar nuestros distintos niveles de error.

Por ejemplo, el nivel de error que se muestra como predeterminado, mediante el cual se nos informará de todos los errores que no sean avisos, se podrá especificar de la siguiente manera:

E ALL & ~E NOTICE

Como podemos observar en el anterior ejemplo, hemos utilizado dos operadores aritméticos. Por un lado el símbolo “&” será el equivalente al operador AND y el símbolo “~” será equivalente al operador NOT. Por lo tanto, esta expresión también podría ser escrita de la siguiente forma:

E ALL AND NOT E NOTICE

El nivel de error E_ALL es el equivalente a la combinación del resto de los errores, menos el error E_STRICT y podría ser sustituido por los otros niveles utilizando el operador OR, que puede ser representado por el símbolo “|”, de la siguiente forma:

E_ERROR | E_WARNING | E_PARSE | E_NOTICE | E_CORE ERROR | E_CORE_WARNING | 
E_COMPILE_ERROR | E_COMPILE_WARNING | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE

Igualmente, el nivel de informes de error predeterminado que hemos comentado anteriormente también podría ser especificado por todos los errores menos los avisos combinados con OR:

E_ERROR | E_WARNING | E_PARSE | E_CORE ERROR | E_CORE_WAR-NING | E_COMPILE_ERROR | 
E_COMPILE_WARNING | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE

Para poder modificar estos niveles de error podemos acudir al fichero de configuración PHP.INI o mediante una secuencia de comandos.

En lo que respecta al archivo PHP.INI, podremos modificar estas configuraciones en las siguientes cuatro líneas destacadas:

error_reporting = E_ALL & E~NOTICE
display_errors = On
log_errors = Off
track_errors = Off

Estos parámetros globales nos servirán para los siguientes propósitos:

■ Informarnos de todos los errores menos los avisos.

■ Mostrarnos los mensajes de error como HTML en resultado estándar.

■ No registrar los mensajes de error en el disco.

■ No realizar el seguimiento de errores y almacenar el error en la variable.

Cuando estemos en una fase de depuración del código, una buena práctica será subir el nivel de error_reporting.

En la fase de código de producción, si queremos suministrar mensajes de errores propios, otra buena práctica podría ser desactivar display_errors y activar log_errors, mientras seguimos manteniendo el nivel de error_reporting en un valor elevado. Con estas prácticas, podremos hacer referencia a errores detallados en los registros por si apareciera algún problema.

Si activamos track_errors, podremos revisar los errores de nuestro propio código, y no tener que esperar a que PHP nos suministre su funcionalidad de manera predeterminada.

No será necesario conservar el comportamiento del procesamiento de errores de forma predeterminada por PHP, ni tenemos por qué utilizar los mismos parámetros en todos los archivos, ya que podremos modificar este nivel de errores mediante la función error_reporting().

Podremos pasarle como parámetros a esta función las constantes de niveles de informes de errores o una combinación de las mismas, y estaríamos realizando la misma configuración como si lo hiciéramos directamente en el archivo PHP.INI. La función devolverá el nivel de informe de error que había anteriormente.

Podríamos utilizar esta función, por ejemplo, de la siguiente forma:

//desactivamos el informe de errores
$nivel_anterior = error_reporting(0);
//insertamos a partir de aquí el código que va generar las
advertencias
//volvemos a activar el informe de errores
error_reporting($nivel_anterior);

Con este ejemplo, estaremos desactivando los informes de error, permitiéndonos ejecutar el código que nos está generando advertencias que no deseamos que en principio sean vistas.

No obstante, nunca es aconsejable dejar desactivados los informes de error de manera permanente, ya que nos complicará la localización y la corrección de los errores en el código.

Podremos mostrar nuestros propios errores mediante la función trigger_error(). A esta función tendremos que pasarle como argumentos un mensaje de error, y de manera opcional, podremos asignarle un tipo de error a elegir entre E_USER_ERROR, E_USER_WARNING o USER_NOTICE, siendo el predeterminado E_USER_NOTICE si no se especifica ninguno.

La forma de utilizarla sería de la siguiente forma:

trigger_error(“Este es el mensaje de error que queremos mostrar”,E_USER_ERROR)”;

Nombres de ficheros

PHP está sujeto a la seguridad integrada en la mayoría de sistemas de servidores con respecto a los permisos de archivos y directorios. Esto permite controlar qué archivos en el sistema de archivos se pueden leer. Se debe tener cuidado con los archivos que son legibles, para garantizar que son seguros para la lectura por todos los usuarios que tienen acceso al sistema de archivos.

Desde que PHP fue diseñado para permitir el acceso a nivel de usuarios para el sistema de archivos, es perfectamente posible escribir un script PHP que le permita leer archivos del sistema como /etc/passwd, modificar sus conexiones de red, enviar trabajos de impresión masiva, etc. Esto tiene algunas implicaciones obvias, es necesario asegurarse que los archivos que se van a leer o escribir son los apropiados.

Veamos un ejemplo sencillo donde podemos observar rápidamente lo vulnerable que puede ser un sistema si no prestamos especial atención o no somos conscientes de las consecuencias que pueden acarrear nuestras decisiones a la hora de desarrollar nuestro código.

Comenzamos por desarrollar un script donde un usuario indica que quiere borrar un archivo en su directorio home. Mediante el siguiente código suponemos que previamente hemos introducido en un formulario el nombre del usuario y del archivo que queremos borrar. El siguiente código se encargaría de borrarlo:

<?php
// eliminar un archivo del directorio personal del usuario
$usuario = $_POST[‘nombre_usuario_enviado’];
$fichero = $_POST[‘nombre_fichero_enviado’];
$directoriohome = “/home/$usuario”;
unlink(“$directoriohome/$fichero”);
echo “El archivo ha sido eliminado!”;
?>

Como el nombre de usuario y el nombre del archivo son enviados desde un formulario, podríamos escribir un nombre de archivo y un nombre de usuario que pertenecen a otro usuario, o también podríamos eliminar el archivo a pesar que se supone que no estaría permitido hacerlo.

Para evitarnos todos estos problemas, deberíamos siempre implementar una solución de autenticación en la operación. Las consecuencias de un mal desarrollo como el anterior podemos observarlas rápidamente si las variables enviadas son “../etc/” y “passwd”. El código entonces se ejecutaría efectivamente como:

<?php
// eliminar un archivo del directorio personal del usuario
$usuario = $_POST[‘nombre_usuario_enviado’]; // “../etc”
$fichero = $_POST[‘nombre_fichero_enviado’]; // “passwd”
$directoriohome = “/home/$usuario”; // “/home/../etc”
unlink(“$directoriohome/$fichero”); // “/home/../etc/passwd”
echo “El archivo ha sido eliminado!”;
?>

Como premio acabamos de eliminar el fichero de passwords de nuestro sistema, que reside en la ruta “/etc/passwd”.

Principalmente, serán dos las medidas a las que deberemos prestar especial atención:

■ Establecer permisos limitados al usuario web de PHP.

■ Revisar todas las variables que se envían.

Además, en función del sistema operativo donde resida nuestro servidor, tendremos una gran variedad de archivos a los que tendremos que vigilar:

■ Entradas de dispositivos (/dev/ o COM1).

■ Archivos de configuración (archivos /etc/ y archivos .ini).

■ Las conocidas carpetas de almacenamiento (/home/, Mis documentos), etc.

Por esta razón, por lo general es más fácil crear una política en donde se prohíba todo excepto lo que expresamente se permite.

Tendremos que tener especial cuidado también con el uso de las funciones “include” y “require”. Veamos el siguiente código:

include(“/usr/local/lib/bienvenida/$username”);

Este código pretende mostrar un mensaje de bienvenida personalizado para el usuario. Aparentemente no es peligroso, pero ¿qué ocurriría si el usuario introduce como nombre la cadena “../../../../etc/passwd”? Se mostraría el fichero de passwords del sistema.

La directiva más útil en relación con la seguridad en PHP es open_basedir. Esta directiva indica a PHP a qué ficheros puede acceder y a cuáles no. El valor de esta directiva es una lista de prefijos de ficheros separados por una coma en Unix y por punto y coma en Windows.

Las restricciones configuradas aquí, afectan a los scripts de PHP y a los ficheros de datos. La opción recomendada es activar esta opción incluso en aquellos servidores con un único sitio web, y debe de apuntar un nivel por encima del directorio raíz del servidor web.

Una posible configuración sería:

open basedir = /var/www/

Es importante recordar la diferencia entre establecer las restricciones a un prefijo frente a establecer las restricciones a un directorio, por ejemplo:

open_basedir=/var/www
// permitiría acceso tanto a ficheros de /var/www como de /
var/www2
open_basedir=/var/www/
// permitiría acceso a los ficheros que estén únicamente en /
var/www/

Encriptación de textos

Uno de los aspectos fundamentales para aumentar la seguridad en nuestro código en PHP, es dotar de seguridad y encriptación a las contraseñas que usemos en nuestras aplicaciones. Por ejemplo, en el proceso de validación de usuario y contraseña.

La forma habitual de trabajo es recoger la contraseña del usuario mediante un formulario, y almacenarla ya encriptada en la base de datos, de tal forma que no sea legible ni para el propio usuario.

Seguramente, cuando alguna vez hayamos olvidado nuestra contraseña de acceso a alguna de nuestras páginas web favoritas, habremos podido comprobar que la solución que nos ofrece la propia página web es enviarnos a nuestro correo electrónico una nueva contraseña, en lugar de enviarnos la que originalmente escribimos. La razón de la creación de esta nueva contraseña es que la original fue almacenada encriptada y los algoritmos utilizados en la encriptación son de una sola vía, es decir, pueden aplicarse para encriptar, pero no para desencriptar.

Esta propiedad la podremos encontrar en las siguientes tres funciones para encriptar con php: crypt(), md5() y sha1().

Crypt()

La función crypt() devolverá el hash de un string utilizando el algoritmo basado en DES estándar de Unix o algoritmos alternativos que puedan estar disponibles en el sistema.

Su sintaxis es la siguiente:

string crypt ( string $str [, string $salt ] )

Algunos sistemas operativos soportan más de un tipo de hash. De hecho, a veces el algoritmo estándar DES es sustituido por un algoritmo basado en MD5. El tipo de hash se dispara mediante el argumento salt. Antes de 5.3, PHP determinaba los algoritmos disponibles en el momento de la instalación, basado en el crypt() del sistema. Si no se proporciona salt, PHP intentará auto-generar ya sea un salt estándar de dos caracteres (DES) o uno de doce caracteres (MD5), dependiendo de la disponibilidad del crypt() de MD5.

En sistemas donde la función crypt() soporta múltiples tipos de hash, podremos utilizar las siguientes constantes en 0 o 1, en función de si el tipo dado está disponible:

CRYPT_STD_DES – Hash estándar basado en DES con un salt de dos caracteres del alfabeto “./0-9A-Za-z”. Utilizar caracteres no válidos en el salt causará que crypt() falle.

CRYPT_EXT_DES – Hash extendido basado en DES. El salt es un string de nueve caracteres que consiste en un guion bajo seguido de 4 bytes del conteo de iteraciones y 4 bytes del salt. Estos están codificados como caracteres imprimibles, 6 bits por carácter, por lo menos, el carácter significativo primero.

Los valores del 0 al 63 son codificados como “./0-9A-Za-z”. Utilizar caracteres no válidos en el salt causará que crypt() falle.

CRYPT_MD5 – Hash MD5 con un salt de doce caracteres comenzando con $1$.

CRYPT_BLOWFISH – Hash Blowfish con un salt como sigue: “$2a$”, un parámetro de costo de dos dígitos, “$” y veintidós dígitos en base64 del alfabeto “./0-9A-Za-z”. Utilizar caracteres fuera de este rango en el salt causará que crypt() devuelva un string de longitud cero.

CRYPT_SHA256 – Hash SHA-256 con un salt de dieciséis caracteres prefijado con $5$. Si el strnig del salt inicia con ‘rounds=<N>$’, el valor numérico de N se utiliza para indicar cuántas veces el bucle del hash se debe ejecutar, muy similar al parámetro de costo en Blowfish.

CRYPT_SHA512 – Hash SHA-512 con un salt de dieciséis caracteres prefijado con $6$. Si el string del salt inicia con ‘rounds=<N>$’, el valor numérico de N se utiliza para indicar cuántas veces el bucle del hash se debe ejecutar, muy similar al parámetro de costo en Blowfish.

Veamos un ejemplo de sus posibles usos:

<?php
$password = 'ladonnaemobile';
if(CRYPT_STD_DES == 1) {
echo 'Standard DES: ' . crypt($password, 'dl')."\n";
echo "<br>";
}
if (CRYPT_EXT_DES == 1){
echo 'Standard DES: ' . crypt($password,
'_H9..escr')."\n";
echo "<br>";
}
if (CRYPT_MD5 == 1) {
echo 'MD5: ' . crypt($password, '$1
$escribeloquequieras$') . "\n";
echo "<br>";
}
if (CRYPT_BLOWFISH == 1) {
echo 'Blowfish: ' . crypt($password, '$2a$07
$escribeloquequieras$') . "\n";
echo "<br>";
}
if (CRYPT_SHA256 == 1) {
echo 'SHA-256: ' . crypt($password, '$5
$rounds=5000$escribeloquequieras$') . "\n";
echo "<br>";
}
if (CRYPT_SHA512 == 1) {
echo 'SHA-512: ' . crypt($password, '$6
$rounds=5000$escribeloquequieras$') . "\n";
echo "<br>";
}

Sha1

Calcula el hash sha1 de un string. Utiliza el algoritmo Secure Hash Algorithm 1 (SHA1).

SHA-0 y SHA-1 producen una salida resumen de 160 bits (20 bytes) de un mensaje que puede tener un tamaño máximo de 264 bits, y se basa en principios similares a los usados por el profesor Ronald L. Rivest del MIT en el diseño de los algoritmos de resumen de mensaje MD4 y MD5.

Su sintaxis es la siguiente:

string sha1 ( string $str [, bool $raw_output = false ] )

Donde “str” será la cadena a encriptar. Si se establece el “raw_output” opcional en TRUE, entonces el resumen sha1 será devuelto en formato binario sin tratar con una longitud de 20, de otra manera, el valor retornado será un número hexadecimal de 40 caracteres.

Veamos un sencillo ejemplo de su uso:

<?php
function cryptconsha1($string)
{ // Creamos un salt
$salt = sha1($string."%*4!#$;.k~’(_@");
$string = sha1("$salt$string$salt");
return $string;
}
$password = "ladonnaemobile";
echo "<br>";
echo 'SHA1: '.cryptconsha1($password);

MD5

Calcula el hash md5 de un string, utilizando el algoritmo de “resumen de mensajes” o MD5 Message-Digest Algorithm.

Su sintaxis es la siguiente:

string md5 ( string $str [, bool $raw_output = false ] )

Donde “str” será la cadena a encriptar. Si se establece el “raw_output” opcional en TRUE, entonces el resumen md5 será devuelto en formato binario sin tratar con una longitud de 16.

Ésta es una de las funciones más utilizadas para encriptar contraseñas, ya que siempre genera el mismo resultado para la misma cadena y también es de una sola vía, es decir, una vez codificada una cadena no es posible volver a descodificarla.

Veamos un sencillo ejemplo de su uso:

<?php
function cryptconMD5($string)
{
// Creamos un salt
$salt = md5($string . "%*4!#$;.k~’(_@");
$string = md5("$salt$string$salt");
return $string;
}
$password = "ladonnaemobile";
echo "<br>";
echo 'MD5: '.cryptconMD5($password);

Inyección SQL

Como veremos a partir de la próxima unidad, será muy común trabajar contra una base de datos (MySQL, PostgreSQL, Oracle, etc.), a través de las cuales realizaremos unas consultas para obtener datos que estén almacenados en las mismas. Estas consultas dependerán de parámetros que recibiremos por los métodos GET o POST.

Al utilizar estas consultas con formularios, una de las desventajas es que permitimos al usuario de cada web que en cierta manera modifique las consultas SQL de nuestra web.

Un ataque de inyección SQL es una vulnerabilidad existente en el proceso de validación de entradas a una base de datos en una aplicación. Esta aplicación puede ser, por ejemplo, un formulario de entrada de datos o cualquier otra que necesite acceder a una base de datos para poder realizar su función. Mediante un ataque de inyección SQL se podrán ejecutar sentencias SQL no deseadas, que pueden ser desde borrado de tablas hasta cambios de permisos o la obtención de contraseñas u otros datos sensibles de estas tablas.

Mediante esta técnica, un usuario malintencionado podría construir una consulta SQL para obtener resultados no deseados. El caso más habitual suele ser el de suplantación de identidad en un formulario para realizar las tareas de login para permitir el acceso a una zona restringida, sin que sea necesario conocer ningún usuario y/o contraseña.

Supongamos la siguiente situación: tenemos un nombre de usuario “admin” y una contraseña “abcde” guardados en una base de datos. Para realizar la identificación obtenemos los parámetros por POST mediante las variables $_POST[‘usuario’] y $_POST[‘clave’]. Esta introducción de datos por parte del usuario, podremos guardarla en unas variables, por ejemplo, de la siguiente forma:

$usua = $_POST['usuario'];
$clav = $_POST['clave'];

Tras ello realizamos la siguiente consulta SQL que almacenaremos en otra variable para posteriormente ser ejecutada.

$sql="SELECT * 
FROM usuarios 
WHERE user = '$usua' 
AND password='$clav'";

Con la utilización de la anterior consulta, estaremos intentando comprobar si un usuario con ese password existe en nuestra tabla de USUARIOS. Es decir, con el usuario anteriormente citado, la consulta sería como la siguiente:

$sql="SELECT * 
FROM usuarios 
WHERE user = 'admin' 
AND password='abcde'";

Si existe un usuario con ese nombre y usuario, es que la identificación es correcta y por tanto, permitiremos que el usuario acceda a la zona restringida para usuarios.

Un usuario experto, conocedor de cómo trabaja una inyección de código SQL, le bastaría con cambiar el valor de las variables $usua y $clav por lo siguiente:

$usua='' or '1'='1';
$clav='' or '1'='1';

Una vez sustituidas las variables en la consulta que va a ser ejecutada, quedaría de la siguiente forma:

$sql="SELECT * 
FROM usuarios 
WHERE user = '' or '1'='1' 
AND password= '' or '1'='1'";

Como podemos observar, vemos que una de las condiciones se convierte en una pregunta “uno es igual que uno”, por lo tanto esta, consulta devolverá valor trae, y por tanto, un acceso no deseado a la sección restringida de nuestra web.

Actualmente, existen hasta tres técnicas distintas de evitar un ataque por inyección de código SQL:

Prepared Statements. Los prepared statements son sentencias pre-compiladas, en las cuales tendremos que indicar los parámetros que van a introducir los usuarios. Así podremos especificarle a la base de datos, por un lado, el código que vamos a ejecutar, y por otra parte, le diferenciaremos las variables que vamos a utilizar. De esta forma, el motor de bases de datos podrá distinguir los datos de entrada y evitar la inyección de instrucciones SQL.

Otra de la ventaja que aportar es la optimización de tiempo de ejecución si la misma sentencia vamos a utilizarla en más de una ocasión, ya que el motor de la base de datos, por cada instrucción SQL, tiene que analizar, compilar y optimizar la forma en la que se ejecutará. Por lo tanto, si la preparamos una sola vez y la ejecutamos en varias ocasiones, con los mismos o diferentes argumentos, el tiempo de ejecución será inferior.

Podemos encontrar más información en el enlace oficial del manual de PHP:

http://www.php.net/manual/es/pdo.prepared-statements.php

Stored Procedures. Los stored procedures son más conocidos que los prepared statements entre los desarrolladores gracias a su utilización extensiva en la programación con bases de datos. Se escriben procedimientos en el lenguaje del DBMS (SQL) y desde la zona donde hayamos escrito el código se invocarán estos procedimientos con las variables ingresadas por el usuario como los parámetros. De esta manera, el DBMS podrá distinguir correctamente las variables del código, evitando de nuevo la inyección.

Podemos encontrar más información en el enlace oficial del manual de PHP:

http://www.php.net/manual/es/pdo.prepared-statements.php.

Escapar todo dato ingresado por el usuario. Esta es la manera más utilizada por los programadores de prevenir la inyección de SQL, pero también es la menos recomendada. La idea es que cada vez que el usuario introduzca los datos que utilizaremos en una sentencia, escapemos los caracteres especiales (como comillas simples o dobles, barras invertidas “\”, o caracteres de comentario “–” o “#”, etc) para que el dato sea un solo string y el motor de BD no lo confunda con código a ejecutar.

Un ejemplo de función que permite escapar los caracteres especiales, cuando utilizamos php y mysql, es la función mysql_real_escape_string().

No obstante, esta técnica es frágil y en principio será mejor utilizarla solo cuando ya contamos con un código inseguro y reescribirlo utilizando prepared statements requeriría un costo inaceptable. Si empezamos a desarrollar nuestro código desde el principio, siempre será mejor utilizar prepared statements o stored procedures.

Por ejemplo, en nuestro formulario de login anterior, tendríamos que hacer lo siguiente:

$usua=mysql_real_escape_string($_POST['usuario']);
$clav=mysql_real_escape_string($_POST['clave']);

Podemos encontrar más información en el enlace oficial del manual de PHP: http://php.net/manual/es/function.mysql-real-escape-string.php

Advertencia

Esta extensión fue declarada obsoleta en PHP 5.5.0 y eliminada en PHP

7.0.0. En su lugar debería utilzarse las extensiones MySQLi o PDO_MySQL.

Véase también la guía MySQL: elegir una API y sus P+F relacionadas para más información. Alternatives to this function include:

mysqli_real_escape_string()
PDO::quote()