Seguridad de scripts PHP

Hablando de scripts PHP, dos aspectos de seguridad son importantes: la prevención de Cross-site scripting (CSS o XSS) y prevención de espionaje de datos o infiltración de códigos.

Para ello, es especialmente necesario verificar todas las entradas que proceden del exterior. Esto se refiere a los datos transferidos en la URL, los datos de formulario, las cookies y los archivos cargados.

La lista de aspectos de seguridad en este artículo no debe considerarse como un plan de seguridad completo, sino que sólo sirve para detectar riesgos fundamentales. Un plan de seguridad siempre cubre el software en su totalidad y cubre consistentemente todas las áreas del procesamiento de datos. La seguridad de los scripts es sólo uno de los componentes del plan general.

Insertar código a través de include()

Una vulnerabilidad común es usar la función include() con parámetros variables. En este caso, un atacante puede inyectar un código extranjero transmitiendo una URL como parámetro (por ejemplo, http://evil-site.tld/exploit.txt). Este código, que puede ser libremente determinado por el atacante, se ejecuta como si fuera parte del script. El atacante podría entonces influir en todo el script. En include(), es recomendable usar constantes en lugar de variables. Si el uso de variables es inevitable, filtre su contenido como corresponde. Esto se puede hacer, por ejemplo, con el siguiente código:

if (strpos($variable, '://') !== FALSE || strpos($variable, '../') !== FALSE)
die('Illegal string'); 

Este código comprueba si la secuencia de caracteres ://, como en http://, ftp:// o ../.../secret/passwords, está contenida en la cadena $variable, y puede terminar la ejecución.

Inyección SQL

Cuando un script utiliza consultas de base de datos, es relativamente fácil para un atacante inyectar un código SQL arbitrario si el script está programado incorrectamente. Esto le permite leer, modificar o incluso borrar datos a los que de otro modo no tendría acceso.

Digamos que utiliza el siguiente código en un script:

[...]
$sql = "SELECT * FROM direcciones WHERE name='".$_GET['name']."'";
$result = mysql_query($sql);
[...]        

Esta o una consulta similar puede mostrarse si desea filtrar la dirección de un nombre determinado de una tabla que contiene direcciones.
El atacante ahora puede acceder al URL http://domain.tld/skript.php?name=';DELETE FROM direcciones WHERE 1=1 OR name='.
Esto resulta en la consulta SQL SELECT * FROM direcciones WHERE name=''; DELETE FROM direcciones WHERE 1=1 OR name=''; . Primero, el servidor de base de datos selecciona todos los registros para los que el campo Nombre contiene una cadena vacía y, a continuación, todos los registros se eliminan de la tabla. Por supuesto, una vulnerabilidad de este tipo también se puede utilizar para otros fines. El borrado del contenido de la tabla sólo sirve de ejemplo en este caso.

Para evitar tal vulnerabilidad, las variables en las consultas SQL siempre deben ser transformadas usando la función mysql_real_escape_string(). Los caracteres especiales, como la comilla simple, se enmascaran para que se interpreten como parte de toda la cadena y no como caracteres para el final de la cadena. Si los valores se transmiten fuera de comillas (por ejemplo, valores numéricos), utilice la función intval() si es posible. Esto asegura que sólo se utilice un valor numérico.

Transferencia de parámetros en URL

Transferir parámetros en direcciones URL (por ejemplo http://domain.tld/script.php?id=1) es una forma común de pasar argumentos a scripts. Sin embargo, siempre debe ser consciente de que estos argumentos pueden ser establecidos a voluntad por el usuario. Esto significa que su contenido no debe considerarse digno de confianza. Lo mismo se aplica a los datos transmitidos a través de HTTP-post y cookies.

Esto es importante cuando se redirige de un script a otro y se transmiten parámetros en el URL o como cookie. El nuevo script no puede considerar estos parámetros como fiables y tiene que comprobarlos de nuevo. A menudo es más fácil usar las funciones de sesión de PHP. Los datos que se han comprobado y almacenado en la sesión ahora se pueden clasificar y utilizar como seguros. El usuario no tiene la posibilidad de cambiar el contenido de la sesión directamente.
El identificador de sesión se transmite como parámetro con el URL. Dado que se trata de una cadena generada aleatoriamente, la probabilidad de que un atacante pueda adivinar el identificador de una sesión ajena es extremadamente baja.

Variables globales

En versiones anteriores de PHP, los parámetros transmitidos desde los formularios o en el URL se indicaron en variables globales. Sin embargo, por razones de compatibilidad, las versiones actuales siguen con este comportamiento en la mayoría de los sistemas.
Por ejemplo, si se accede a al URL http://domain.tld/skript.php?variable=content, se establece en el script una variable con el nombre $variable que tiene el contenido contenido.

Esto puede convertirse en un problema si utiliza variables globales internamente sin inicializarlas correctamente antes.

Ejemplo:

<?php
if ($_GET['password'] == 'secreto') {
$admin = true;
}

[...]

if ($admin) {
// Acciones que solo puede realizar el administrador conociendo la contraseña
[...]
}
?>

Un atacante puede simplemente acceder al URL http://domain.tld/skript.php?admin=1 e inmediatamente tiene derechos de administrador porque la variable $admin devuelve true.

Con una correcta inicialización, esto puede evitarse:

<?php
$admin = false;

if ($_GET['password'] == 'secreto') {
$admin = true;
}

[...]

if ($admin) {
// Acciones que solo puede realizar el administrador conociendo la contraseña
[...]
}
?>

Ahora, el valor establecido previamente para $admin se sobrescribe primero y sólo se devuelve true si la contraseña se ha introducido.

En otros escenarios, la omisión de la inicialización también se puede utilizar para inyecciones SQL, por ejemplo. Por esta razón debería acostumbrarse a inicializar todas las variables, aunque la sintaxis de PHP no lo requiere.

Cross-Site-Scripting

Cross-Site-Scripting significa que un atacante inserta un código JavaScript en un sitio extranjero para obtener información a la que normalmente no tiene acceso. Se puede utilizar para acceder a otros ID de sesión o para filtrar nombres de usuario.

Para ello, un atacante crea un URL en la que establece parámetros que se muestran inmediatamente en el texto de la página. Si este texto ahora contiene un código de script, se ejecuta en el contexto de seguridad de la página, por ejemplo, puede leer las cookies que se han establecido en esta página. El peligro de este tipo de ataque es que es casi invisible para el usuario cuando ocurre en un marco oculto.

La solución es usar la función htmlentities() en el script para todas las variables a ser emitidas (especialmente aquellas que contienen entradas del usuario).
Esta función reemplaza los caracteres con significado especial en HTML, tales como <, >, >', ", con las entidades correspondientes. Esta sencilla medida le permite eliminar la mayoría de las posibilidades de ataque para el cross-site scripting.