Está en la página 1de 17

Construyendo aplicaciones

seguras con PHP


Ben.edmunds@gmail.com y Judas Borbn
Este libro est a la venta en
http://leanpub.com/buildingsecurephpapps-es

Esta versin se public en 2014-06-15

This is a Leanpub book. Leanpub empowers authors and


publishers with the Lean Publishing process. Lean
Publishing is the act of publishing an in-progress ebook
using lightweight tools and many iterations to get reader
feedback, pivot until you have the right book and build
traction once you do.

2014 Ben.edmunds@gmail.com y Judas Borbn


ndice general

Captulo 1 - Nunca confen en los usuarios. Filtren


TODAS las entradas de datos. . . . . . . . . . . . 1
Inyeccin de SQL . . . . . . . . . . . . . . . . . . . 3
Asignacin Masiva . . . . . . . . . . . . . . . . . . 7
Typecasting . . . . . . . . . . . . . . . . . . . . . . 10
Sanitizando informacin de salida . . . . . . . . . . 12
Captulo 1 - Nunca
confen en los
usuarios. Filtren
TODAS las entradas de
datos.
Comencemos con una historia. Mike es un administrador de
sistemas para una escuela privada en Oklahoma. Su principal
responsabilidad es mantener funcionando la red de compu-
tadoras. Hace poco comenz a desarrollar una aplicacin web
interna con el objetivo de automatizar diferentes tareas de la
escuela. Mike no tiene formacin profesional en desarrollo y
comenz a programar alrededor de un ao atrs, pero siente
que es bastante bueno en lo que hace. Sabe lo bsico de
PHP y ha creado un sistema para manejo de clientes (CRM)
bastante estable. Todava queda mucha funcionalidad por
agregar, pero lo esencial est terminado. Incluso el director
de la escuela lo felicit por agilizar operaciones y ahorrarle
dinero a la escuela.
Todo iba bien hasta que lleg un nuevo estudiante. El nombre
del estudiante es Little Bobby Tables . Un da, John de
la oficina administrativa le inform a Mike que el sistema
estaba cado. Despus de realizar una inspeccin, Mike se
http://xkcd.com/327/
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 2

dio cuenta que la tabla que contena la informacin de los


estudiantes ya no estaba. Al parecer, el nombre completo de
Little Bobby Tables era Robert); DROP TABLE students;.
No hay ningn respaldo de la base de datos; ha estado en la
lista de pendientes de Mike por un tiempo, pero nunca lo ha
hecho. Mike est en grandes dificultades.
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 3

Inyeccin de SQL

El mundo real
Aunque es poco probable que el nombre real de una persona
contenga cdigo SQL, este tipo de vulnerabilidad por inyec-
cin se da en el mundo real todo el tiempo:

En el 2012, se expuso la informacin de cerca de 6 mi-


llones de usuarios de LinkedIn debido a una inyeccin
de SQL no revelada
En el 2012, 450,000 contraseas de usuarios de Yahoo
quedaron expuestas
En el 2012, 400,000 contraseas de usuarios de Nvidia
fueron comprometidas
En el 2012, 150,000 contraseas de usuarios de Adobe
fueron comprometidas
En el 2013, se expusieron alrededor de 1.5 millones de
contraseas de usuarios de eHarmony

Cmo funciona una inyeccin de SQL?


Si se utilizan datos directamente desde un campo de texto, es
posible que un usuario haya ingresado informacin inespera-
da y provoque que se modifiquen las consultas SQL.
Si nuestro cdigo luce as:

Los detalles precisos de estas vulnerabilidades no fueron revelados, es por ello que no
podemos estar seguros que ocurrieron debido a inyeccin de SQL. Aunque en la mayora de
los casos es debido a una inyeccin.
La extensin mysql_* y sus mtodos estn deprecados oficialmente. Por favor no los
usen.
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 4

1 mysql_query('UPDATE users
2 SET first_name="' . $_POST['first_name'] . '"
3 WHERE id=1001');

Esperaramos que la consulta SQL luciera as:

UPDATE users set first_name="Liz" WHERE id=1001;

Pero si un usuario escribe su primer nombre de la siguiente


manera:

Liz", last_name="Lemon"; --

El SQL generado se convierte en:

UPDATE users
SET first_name="Liz", last_name="Lemon"; --"
WHERE id=1001;

Ahora todos los usuarios se llaman Liz Lemon, y eso no es


nada bueno.

Cmo Protegerse
El requisito principal para evitar la inyeccin de SQL es la
sanitizacin (tambin conocida como escape). Podemos es-
capar cada entrada de datos individualmente o usar un mejor
mtodo llamado vinculacin de parmetros. La vinculacin
de parmetros es, en definitiva, la medida que recomiendo,
ya que ofrece mayor seguridad. El cdigo quedara de las
siguiente manera usando la clase PDO de PHP:
http://us1.php.net/manual/es/intro.pdo.php
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 5

1 $db = new PDO(...);


2 $query = $db->prepare('UPDATE users
3 SET first_name = :first_name
4 WHERE id = :id');
5
6 $query->execute([
7 ':id' => 1001,
8 ':first_name' => $_POST['first_name']
9 ]);

Vincular los parmetros hace que cada valor sea filtrado


apropiadamente. Sin embargo, hay que tener en cuenta que
la vinculacin de parmetros protege las consultas SQL pero
no protege de los datos que ya han ingresado a la base
de datos. Hay que recordar que cualquier valor puede ser
malicioso. Es necesario sanitizar la informacin que vaya a
tomarse de la base de datos para ser mostrada al usuario. Esto
se puede hacer al momento de almacenar la informacin o
al momento de mostrarla; lo importante es no saltarse este
paso. Abordaremos ms al respecto en la seccin llamada
Sanitizando informacin de salida.
Nuestro cdigo ahora es ms extenso, pero es seguro. Ya
no habr que preocuparse por otro Little Bobby Tables. La
vinculacin de parmetros es genial, cierto?

Buenas prcticas y otras soluciones


Los procedimientos almacenados son otra forma de protec-
cin contra una inyeccin de SQL. Un procedimiento almace-
nado es una funcin que se encuentra en la base de datos. Usar
procedimientos almacenados disminuye el riesgo de una in-
yeccin de SQL, ya que los datos no se mandan directamente a
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 6

la consulta SQL. En general, los procedimientos almacenados


no son bien vistos. Algunas de las razones principales son:

1. Son difciles de probar


2. Trasladan la lgica hacia otro sistema fuera de la apli-
cacin
3. Son difciles de incluir en un sistema de control de
versiones por encontrarse en la base de datos y no en el
cdigo
4. Su uso puede limitar el nmero de personas capaces de
modificar la lgica, de ser necesario

JavaScript NO es una solucin para validacin de datos. Pue-


de ser alterada o burlada fcilmente por un usuario malicioso
que ni siquiera cuente con mucho conocimiento. Repitan
despus de mi: NUNCA voy a confiar en la validacin con
JavaScript; NUNCA JAMS voy a confiar en la validacin con
JavaScript. Es posible usarla para proporcionar informacin
inmediata y mejorar la experiencia de usuario, pero por el
amor de su deidad favorita, revisen los datos de entrada para
asegurarse que son vlidos.
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 7

Asignacin Masiva
La asignacin masiva puede ser una herramienta muy til
para acelerar el desarrollo, pero tambin puede causar mucho
dao si se usa incorrectamente.
Supongamos que tenemos un modelo User el cual necesita-
mos actualizar. Podramos actualizar cada campo de forma
individual, o podramos enviar todos los cambios desde un
formulario y actualizar todos a la vez.
El formulario podra lucir del siguiente modo:

1 <form action="...">
2 <input name="first_name" />
3 <input name="last_name" />
4 <input name="email" />
5 </form>

Luego tendramos el cdigo PHP que se encarga de procesar y


guardar la informacin. Usando el framework Laravel lucira
as:

1 $user = User::find(1);
2 $user->update(Input::all());

Rpido y fcil, cierto? Pero, qu tal si un usuario malicioso


modifica el formulario para otorgarse permisos de adminis-
trador?
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 8

1 <form action="...">
2 <input type="text" name="first_name" />
3 <input type="text" name="last_name" />
4 <input type="text" name="email" />
5 <input type="hidden" name="permissions" value="{\
6 'admin':'true'}" />
7 </form>

El mismo cdigo ha cambiado los permisos del usuario de


forma errnea.
Puede parecer un problema muy tonto, pero muchos desa-
rrolladores y sitios han sido vctimas de l. El ms reciente
y conocido exploit de esta vulnerabilidad ocurri cuando un
usuario descubri que el framework Ruby on Rails era sus-
ceptible. Cuando Egor Homakov le report al equipo de Rails
que las nuevas instalaciones eran inseguras, su reporte del bug
fue rechazado. El equipo pens que se trataba de un problema
menor, el cual los nuevos desarrolladores sabran cmo evitar.
Para atraer la atencin hacia el problema, Homakov hac-
ke la cuenta de GitHub de Rails (GitHub est construido
con Rails) para otorgarse permisos de administrador en sus
repositorios. No hace falta mencionar que esto prob lo que
Homakov trat de demostrar desde un principio y ahora Rails
(y GitHub) estn protegidos contra este ataque.
Cmo podemos proteger nuestra aplicacin frente a esto? Los
detalles de implementacin dependen del framework o cdigo
en el que nos estemos basando, pero existen algunas opciones:

Desactivar por completo la asignacin masiva


Poner en una lista blanca los campos que pueden ser
includos en una asignacin masiva
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 9

Poner en una lista negra los campos que no deben ser


includos en una asignacin masiva

Dependiendo de la implementacin, algunos se pueden utili-


zar simultneamente.
En Laravel agregamos una propiedad llamada $fillable a los
modelos para establecer los campos que pueden ser asignados
de forma masiva:

1 class User extends Eloquent {


2
3 protected $table = 'users';
4
5 protected $fillable = ['first_name', 'last_name'\
6 , 'email'];

Esto evita que la columna permissions se incluya en la


asignacin masiva. Otra forma de solucionar esto en Laravel
es utilizando una lista negra mediante la propiedad $guarded:

1 class User extends Eloquent {


2
3 protected $table = 'users';
4
5 protected $guarded = ['permissions'];

La decisin sobre usar una lista negra/lista blanca depende de


cul sea ms fcil de implementar en nuestra aplicacin.
Si no usan Laravel, el framework que utilicen probablemente
cuente con un mtodo similar. Si utilizan un framework
propio les recomiendo implementar esta funcionalidad.
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 10

Typecasting
Un paso adicional que deberan de considerar, no slo por
cuestiones de seguridad sino tambin de integridad de datos,
es la conversin a formatos conocidos. Ya que PHP es un
lenguaje dinmico, sus valores pueden ser de cualquier tipo:
cadena de texto, entero, flotante, etc. Al convertir un valor
podemos verificar que la informacin coincide con lo que
esperamos. En el ejemplo anterior, si el identificador proviene
de una variable tiene sentido hacer la conversin si sabemos
que siempre debe tratarse de un valor entero:

1 $id = (int) 1001;


2
3 $db = new PDO(...);
4 $query = $db->prepare('UPDATE users
5 SET first_name = :first_name
6 WHERE id = :id');
7
8 $query->execute([
9 ':id' => $id, //sabemos que es un entero
10 ':first_name' => $_POST['first_name']
11 ]);

En este caso no hay mucha diferencia puesto que el identi-


ficador lo hemos definido nosotros, as que estamos seguros
que se trata de un valor entero. Pero si el identificador viniera
desde otra fuente, tendramos la seguridad de tener el tipo de
dato correcto.
PHP soporta diferentes tipos para conversin:
http://stackoverflow.com/questions/7394711/what-is-dynamic-typing
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 11

1 $var = (array) $var;


2 $var = (binary) $var;
3 $var = (bool) $var;
4 $var = (boolean) $var;
5 $var = (double) $var;
6 $var = (float) $var;
7 $var = (int) $var;
8 $var = (integer) $var;
9 $var = (object) $var;
10 $var = (real) $var;
11 $var = (string) $var;

Esto es til no slo para tratar con la base de datos, sino


tambin con la aplicacin. El hecho de que PHP sea un
lenguaje dinmico no significa que no podamos asegurar el
tipo de datos.
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 12

Sanitizando informacin de
salida
Desplegando informacin en el
navegador
No hay que tomar precauciones nicamente al guardar in-
formacin, hay que sanitizar / escapar cualquier informacin
generada por el usuario que vaya a mostrarse en el navegador.
Podemos modificar y escapar informacin previo a guardarla
en la base de datos o al recuperar la informacin para mostrar-
la. Normalmente depende de cmo se utilice la informacin.
Por ejemplo, si el usuario va a poder editar la informacin
posteriormente, tiene sentido guardarla tal y como es y sani-
tizarla al momento del despliegue.
Cules beneficios de seguridad se obtienen al escapar in-
formacin generada por el usuario? Supongamos que un
usuario guarda el siguiente fragmento de cdigo en nuestra
aplicacin, la cual lo guarda para mostrarlo despus.

<script>alert('I am not sanitized!');</script>

Si no sanitizamos este cdigo antes de imprimirlo en el


navegador, el JavaScript malicioso se ejecutar normalmente,
tal y como si lo hubiramos escrito nosotros mismos. En este
caso es un inofensivo alert(), pero un hacker no ser tan
benevolente.
Otro lugar comn para este tipo de exploit es en la informa-
cin XIFF de una imagen. Si un usuario carga una imagen
y nuestra aplicacin muestra la informacin XIFF, habr
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 13

que sanitizarla tambin. En cualquier lugar donde vayamos


a mostrar informacin proveniente del exterior, hay que
sanitizar.
Si usamos una librera para templates o un framework que
los maneje, es posible que la sanitizacin se haga de manera
automtica o exista un mtodo encargado de ello. Hay que
asegurarnos de revisar la documentcin de la librera /
framework para averiguar cmo implementar la sanitizacin.
Para quienes lo hagan por si mismos, PHP provee una se-
rie de funciones que sern grandes aliados a la hora de
mostrar informacin en el navegador: htmlentities() y
htmlspecialchars(). Ambas escapan y manipulan informa-
cin para hacerla ms segura antes de desplegarla.
htmlspecialchars() sea quiz la opcin para el 90% de los
casos. Se encarga de encontrar caracteres especiales (p. ej. <,
>, &) y tranformarlos a su equivalente en HTML.

htmlentities() es como htmlspecialchars() con esteroi-


des. Se encarga de codificar cualquier caracter para convertir-
lo en su equivalente en HTML, en caso de que exista. Quiz no
sea lo que necesitemos en muchos casos. Hay que asegurarse
de entender cmo trabajan estas funciones para evaluar cul
es la mejor para el tipo de informacin que deseamos enviar
al navegador.

http://us1.php.net/manual/es/function.htmlentities.php
http://us1.php.net/manual/es/function.htmlspecialchars.php
Captulo 1 - Nunca confen en los usuarios. Filtren TODAS las entradas
de datos. 14

Imprimiendo en la lnea de comandos


No hay que olvidar sanitizar cualquier cosa proveniente de
un script de lnea de comandos. Las funciones encargadas de
esto son: escapeshellcmd() y escapeshellarg().
escapeshellcmd() se usa para escapar comandos; previene la
ejecucin abritraria de cualquier comando que provenga del
exterior. escapeshellarg() se usa para filtrar parmetros con
el fin de asegurarse de que sean vlidos. No hay que dejar que
la aplicacin acepte cualquier comando como entrada.

http://us1.php.net/manual/es/function.escapeshellcmd.php
http://us1.php.net/manual/es/function.escapeshellarg.php

También podría gustarte