Seguridad en PHP básica: escribiendo aplicaciones web seguras

A continuación vamos a explicar algunos conceptos básicos de brechas de seguridad más comunes y como solucionarlos mediante PHP permitiendo escribir aplicaciones web un poco más seguras.

Protección contra la inyección de código SQL

Es muy común trabajar contra una base de datos (MySQL, PostgreSQL, Oracle, etc.) y estas consultas dependen de parámetros llegados desde GET o POST, por lo que permitimos al usuario de cada web que en cierta manera modifique las consultas SQL de nuestra web.

Esto podría provocar que un usuario que construya una consulta SQL malintencionada pudiera obtener resultados indeseados, como poder hacer un login en una zona restringida de nuestra web sin conocer ningún usuario / contraseña.

Supongamos la siguiente situación: tenemos un nombre de usuario pruebas y una contraseña clave_pruebas guardados en base de datos. Para realizar la identificador obtenemos los parámetros por POST mediante las variables $_POST['usuario'] y $_POST['clave']. Tras ello realizamos la siguiente consulta SQL:

mysql_query('select id from usuariosWeb where usuario = '".$_POST['usuario']."' and password = '".$_POST['clave']."'');

Si existe un usuario con ese nombre y usuario, es que la identificación es correcta y por tanto debemos hacer el login. Pero sin embargo, si construyésemos el siguiente parámetro

$_POST['clave'] ="m' or 1='1"

podemos observar que nos devuelve un valor válido para identificarse conociendo sólo el nombre de usuario.

Para solucionarlo, tenemos diversos métodos pero lo más cómodo es substituir las comillas simples y dobles de todos los parámetros parámetros POST mediante el siguiente código:

foreach($_POST as $key => $value) {
	$_POST[$key] = str_replace("'","",$value);
	$_POST[$key] = str_replace('"','',$value);
}

Guardar contraseñas, números de tarjeta de crédito, dirección de correo, etc. de forma segura con MD5 ó SHA1

Para guardar las contraseñas de los usuarios en las bases de datos debemos guardarlas de manera que no puedan ser visibles a simple vista ya que si alguna persona malintencionada consiguiera acceder a la base de datos, además de conseguir el control de nuestra web, además tendrá acceso a información sensible de los usuarios que nos visitan.

Para ello podemos dificultarlo mediante los resúmenes hash MD5 o SHA-1, que aunque se tratan de algoritmos crackeados, por lo menos no mostraremos como texto plano esta información.

Ejemplo:

$a = "clave_pruebas";

echo "clave original: ".$a." ";

$b = md5($a);

echo "Hash MD5 de la clave original: ".$b." ";

$c = sha1($a);

echo "Hash SHA-1 de la clave original: ".$c." ";

Como podemos ver, con ello conseguimos ocultar mínimamente la contraseña de cada usuario. Cuando un usuario se identifique con su contraseña, realizaremos el cálculo del hash de su contraseña y compararemos contra la base de datos, no el texto plano de la contraseña, si no del hash.

Uso de variables de sesión y no de cookies

Normalmente, alguna zona de nuestra web necesite acceso mediante usuario y contraseña. Para que el usuario no tenga que introducirlas repetidamente podemos guardarla mediante cookies pero es una práctica muy insegura ya que si lo hiciéramos, esta información es transmitida mediante la red en texto plano y podría ser leída por cualquier persona que realice un sniffing de red o que lea las cookies guardadas en nuestro ordenador mediante un virus o troyano.

Por ello lo mejor es hacer uso de las variables de sesión. Estas variables se guardan en el servidor por lo que en cualquier caso, la información transmitida sólo es sensible la primera vez que se envía y no las sucesivas veces (por tanto esto sería evitable haciendo uso del protocolo HTTPS en vez de HTTP).

Como ejemplo del usuario de variables de sesión, haremos uso de dos archivos. En el primero creamos una variable de sesión:

session_start();
$_SESSION['clave'] = "clave_pruebas";

y en el segundo mostramos el valor en pantalla.

session_start();
echo $_SESSION['clave'];

Suplantación de sesiones (Session hijacking o Man in the middle)

Del uso de variables de sesión se deriva otra posible brecha de seguridad que es una persona malintencionada intente suplantar el cliente de cara al servidor. Cuando iniciamos una sesión, creamos una cookie con un identificador único para esa sesión. Si una persona malintencionada interceptase ese identificador único y mandase esa cookie al servidor, podría suplantar nuestra identidad y tener acceso a nuestras zonas restringidas de la web.

Lo solución ideal sería el uso del protocolo HTTPS que encriptaría toda nuestra conexión, pero si esto no es posible, podemos crear una serie de comprobaciones en las que intentemos asegurarnos de la real identidad de cada usuario.

Para ello podemos guardar una serie de variables de sesión de los datos que el usuario nos proporciona en cada visita, como son la dirección IP o la información de su navegador, etc. Si estos cambian significaría que es un intento de session hijacking y por tanto es un usuario que realiza un ataque. En el siguiente ejemplo tenemos dos archivos, en el que en el primer archivo realizamos un login satisfactorio y guardamos los datos iniciales de dirección IP y tipo del navegador con el que lo hemos hecho:

session_start();
$_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];

En pantallas posteriores, comprobaremos si en cada pantalla los datos actuales de dirección IP y datos del navegador se corresponden con los que se realizó el login como usuario en la web:

session_start();
if($_SESSION['REMOTE_ADDR'] != $_SERVER['REMOTE_ADDR'] || $_SESSION['HTTP_USER_AGENT'] != $_SERVER['HTTP_USER_AGENT']) {
	exit();
}

Parámetros GET/POST explotables

Para finalizar, si hacemos uso de parámetros GET y POST deberemos comprobar siempre que se correspondan con el usuario actual o si tiene permisos.

Por ejemplo, si tenemos un archivo que edita productos llamado editarProducto.php y recibe el parámetro GET idproducto, que se corresponde con el identificador único del producto a editar y llamamos al archivo de la siguiente manera:

http://www.miweb.com/editarProducto.php?idproducto=15

deberemos comprobar en cualquier caso que en primer lugar seamos un usuario registrado y logueado y después que el producto de identificador único 15 corresponde a uno de los productos del usuario logueado.

Comentarios

Comentario de Miguel - 05 de Marzo de 2012 - 21:44
Muy interesante tu post, me gustaria saber mas de seguridad o tecnicas para mejorar la seguridad de la navegacion cuando hay un registro de por medio. Saludos y exitos.
Comentario de Imaginanet - Carlos - 06 de Marzo de 2012 - 06:58
Hola Miguel, se pueden hacer muchas más cosas al respecto a la hora de programar, pero con las aquí indicadas cubres el 95% de las brechas de seguridad. Puedes leer algo más sobre artículos de seguridad en nuestra sección de "Servidores y Seguridad" en la URL http://www.imaginanet.com/blog.html?cat=31 Saludos
Comentario de reinaldo - 07 de Abril de 2012 - 23:21
Haber probemos, jeje... <script> window.document.innerHTML='';</script>
Comentario de Julio Ruiz - 13 de Enero de 2013 - 05:04
Muchas gracias por tu aporte... soy nuevo en esto... y me ha sido muy util... suerte con tus proyectos
Comentario de andresbetancourt - 02 de Febrero de 2014 - 16:55
gracias por tu ayuda, una corrección: el codigo deberia ser asi: foreach($_POST as $clave => $valor) { $_POST[$clave] = str_replace("'","",$valor); //se reemplazan las comillas simples por vacio $_POST[$clave] = str_replace('"','',$_POST[$clave]);//se reemplazan las comillas dobles por vacio }
Comentario de Felix - 18 de Mayo de 2014 - 23:10
Hola ¿? De mucha ayuda tus comentarios, da las bases para comensar a revisar el tema en el web. Saludos Felix
Comentario de Leo - 06 de Agosto de 2014 - 00:02
Excelente articulo ! muy claro y rico en contenido. Sigue asi ! PD: lo que si podrias ir replanteando cambiar la captcha porque tuve que hacer +5 intentos para comentar...
Comentario de Omar - 11 de Septiembre de 2015 - 17:35
Hola, muy buen post, sólo una observación: encriptar con SHA o MD5 ya no es considerado muy seguro, en su lugar recomendaría utilizar otros métodos, por ejemplo bcrypt. Saludos.
Ha habido un error en el envío
Comentario enviado. Será revisado por la moderación antes de ser publicado.

Deja tu comentario

Tu nombre:
Tu email:
Tu comentario: