sábado, 22 de febrero de 2014

[Parte 1] Seguridad en PHP

PHP



Como todos sabemos PHP es uno de los lenguajes scripting mas usados en la web, ya que se encuentra en varias paginas famosas como facebook, taringa, etc. Y una de las cosas por la cual nos debemos preocupar mas es por la seguridad.


Register globals


Esta directiva lo que hace es tomar todo tipo de variables, entonces si tu estas ejecutando por ejemplo un script, en una pagina donde solo autorizas que si es verdad que el usuario ha iniciado sesión, la variable autenticado sea igual a true, seria algo como lo siguiente (el archivo siguiente le pondremos un nombre falso que sera autenticar.php):


<?php
if(usuario_autenticado(){
$autenticado = true;
});
if($autenticado){
include 'datos_sensitivos.php';
}
?>

¿Que pasa con esto? pues fácil, tenemos una función usuario_autenticado() que valida un inicio de sesión luego de eso nos da una variable $autenticado que no ha sido inicializada y no la deja a true, después hacemos una comprobación de que si $autenticado es true o verdadero entonces mostremos al usuario un archivo que se llama datos_sensitivos.php. Con esto nosotros podemos meter en la url algo como lo siguiente:


http://ejemplo.com/autenticar.php?autenticado=1

De esta manera el script nos mostrara su archivo datos_sensitivos, esto ¿por que? pues gracias a que le programador no inicializo la variable $autenticado = false; de manera que nosotros la inicializamos a true o 1, y estamos autenticados y lo otro es que pues tenemos register_globals activado de manera que podemos poner nuestra variable por medio de $_GET.

Nota importante:  Desactivar register_globals si es que la tienes activada, esta paso a ser desactivada desde la versión 4.2.0 de php, obsoleta en php 5.3.0 y eliminada en php 5.4.0.


Mi versión esta en PHP/6.0.0-dev por lo que no tengo que molestarme por esta directiva ya que pues ha sido eliminada.


Reporte de errores

El reporte de errores nos ayuda para ver que es lo que estamos haciendo mal al programar algo, pero si lo usamos cuando nuestra web ya esta al publico y no en desarrollo esto puede ser fatal ya que le estamos dando entrada a un atacante para leer información que el puede querer recibir. Tal como el error de la linea de un archivo, la base de datos, la ruta del directorio, permisos, alguna salida de un poco de código, etc.

Es por eso que es importante que cuando estemos mostrando la información al publico debemos debemos de poner error_reporting(0) de manera que no reporte errores o display_errors = Off en php.ini


Si se hace esto de desactivar display_errors es recomendable activar log_errors = On e indicar la ruta de la directiva error_log.







Otra manera seria que si por ejemplo tu estas usando un servidor compartido y por lo cual no te deja editar el archivo de configuración php.ini puedes utilizar init_set para establecer una directiva de configuración, por ejemplo:

<?php
ini_set('error_reporting', E_ALL | E_STRICT);
ini_set('display_errors','Off');
ini_set('log_errors','On');
ini_set('error_log','/usr/local/apache/log/error_log');
?>

De esta forma los errores deben quedar guardados en el archivo error_log que esta  guardado en /usr/local/apache/log/ para que no se muestren al publico y nosotros podamos verificarlos cuando tengamos tiempo. Si quiere saber mas acerca de las constantes predefinidas tales como E_ALL o E_STRICT puede consultar la siguiente documentacion: http://php.net/manual/es/errorfunc.constants.php.



Por ultimo podemos crear nuestro propio gestor de errores para que nos arroje un error personalizado:


mixed set_error_handler ( callable $error_handler [, int $error_types = E_ALL | E_STRICT ] )

Como puedes ver solo establecemos set_error_handler('unafuncion',eltipodeerror); con eso podemos establecer una función personalizada para que nos arroje los errores.

 Este seria un ejemplo de mi función:

function gestor_errores($number,$string,$file,$line,$context){
$error .= "=====================\nPHP ERROR\n=====================";
$error .= "Numero: [$number]\n";
$error .= "Cadena: [$string]\n";
$error .= "Archivo: [$file]\n";
$error .= "Linea: [$line]\n";
$error .= "Contexto:\n" . print_r($context,TRUE) . "\n\n";
error_log($error,3,'/usr/local/apache/logs/error_log');
}

Como puedes ver la sintaxis de la variable $error_handler es la siguiente: $number contiene el nivel de error ocasionado, $string contiene el mensaje de error como cadena, $file en que archivo esta el error, $line el numero de linea donde esta el error, $context contiene una matriz con cada variable que existe en el ámbito donde el error fue provocado.

Al final contiene la función error_log que indica que el mensaje de error que va a ser enviado sera la variable $error, con 3 indicamos que el mensaje sera enviado al fichero destino, y luego el fichero destino que es '/usr/local/apache/logs/error_log'.

Por ultimo establecemos la función que acabamos de crear:

<?php
set_error_handler('gestor_errores',E_WARNING);
?>






Tambien es posible usar excepciones para gestionar los errores, para mas informacion: http://mx1.php.net/exceptions


Principios

Debemos aprender algunos principios para poder entender como hacer nuestras aplicaciones mas seguras y tomarlos como ejemplos nos ayudara.

Defensa en profundidad




Esta es una estrategia donde se usan distintas técnicas para limitar los daños, es como tener un respaldo para un ataque, me refiero a que si por ejemplo, un usuario secuestra la sesión de un administrador, ¿que es lo primero que se le ocurre? si, cambiar la contraseña, en ese caso podríamos poner un respaldo de que vuelva a iniciar sesión cada vez que se realice esta acción critica. Esto puede ser útil contra ataques día cero (zero day exploits), ya que podemos tener elementos de seguridad tales como:

* firewalls * Seguridad fisica (mantener servidores bajo llave)
* antivirus * Restricciones de acceso basadas en ip, hora y localización
* DMZs   * Seguridad por oscuridad
* VPNs

Los cuales pueden ser útil de respaldos, ya que si el atacante atraviesa una parte de nuestro sistema hay otro que le impedirá la acción que desea realizar.

Privilegio mínimo




Cada modulo (como un usuario,  un proceso o un programa) debe ser capaz de solo acceder a información y recursos que son necesarios para su legitimo propósito.
Esto quiere decir que, por ejemplo, no le vas a dar todos los privilegios a un moderador de un foro, ya que solo esta moderando un foro, es decir solo le vas a dar los privilegios necesarios, que en este caso son moderar su foro. ¿Por que pasa esto? Ya que si le das privilegios de mas puede ocurrir cosas que no desees como que te baje de la administración a ti y se quede con el foro, es un ejemplo, exagerado pero es para entenderlo mejor.

Lo simple es bello



No se si han escuchado del principio de parsimonia también se aplica en la informática como el principio KISS << Keep It Simple, Stupid! >> ¡Mantenlo simple, estúpido! , y esto se aplica también en este apartado de seguridad ya que si nosotros lo mantenemos simple entonces de esta manera engendraremos menos errores a la hora de la programación, un ejemplo seria el siguiente:

<?php
$buscar = (isset($_GET['buscar']) ? $_GET['buscar'] : '');
?>

Como ves esto es un poco complicado para un programador que apenas se va iniciando, ademas de que no se ve que esa entrada por $_GET podría estar contaminada, podemos hacerlo de forma mas bella, manteniendolo simple :), esto lo haríamos de la siguiente forma:


<?php
$buscar = '';
if(isset($_GET['buscar'])){
$buscar = $_GET['buscar'];
}
?>

Eso seria lo mismo que lo de arriba pero se ve mas entendible ¿no?, la sintaxis como ves y la sangría nos hace ver que el problema puede estar en la entrada $_GET['buscar'] que ni siquiera esta siendo validada.

Minimizar la exposición




Esto se refiere a que debemos eliminar datos que no sean necesarios mostrar en el servidor, por ejemplo, uno de los mas comunes que me he encontrado es que muchos ponen sus robots.txt y se encuentra un montón de información que no quiere que se muestre a los navegadores y un atacante es una de las cosas por las que va primero, otra cosa es que los datos de las bases de datos estén fuera como un archivo .sql o .db o un archivo .inc o .txt que son archivos que pueden contener información que no queremos que sea vista, todo esto veremos mas adelante como ocultarlo. Google es una de las empresas mas famosas y bueno es el mejor buscador y también el mejor "mirador" ya que puede ver toda nuestra web muchas veces entonces nosotros debemos aveces ocultarnos de los buscadores aunque debería ser al revés hay cosas que no queremos que vea. Entonces en esta forma vamos a minimizar la exposición solo de los datos que no queramos que vea el publico (internet).



Practicas

Al igual que los principios debemos aprender algunas practicas para hacer nuestras aplicaciones mas seguras.

Riesgos de balance y usabilidad




Aquí tenemos dos cosas a tomar en cuenta el balance y la usabilidad y estos dos no van en contra, sino al contrario, con esto me refiero a que para poder que el usuario se sienta cómodo navegando por la pagina tenemos que tener un balance en la usabilidad que le damos, con esto quiero decir por ejemplo, darle privilegios mínimos cuando se requiere, seria como cuando tenemos una pagina de administración donde solamente el staff (personal autorizado) tiene derecho a entrar, y para lograr esto tiene que haber un balance.

Otro ejemplo para que lo entiendan mejor, por ejemplo, facebook no va poner una parte donde te pregunte cada vez que te llega un mensaje si lo quieres ver el mensaje o no lo quieres ver, pero si te va poner que repitas tu contraseña cuando la quieres cambiar, esto es por que requiere un riesgo necesario para dar un balance y la usabilidad que hay en cada uno, seria muy molesto autorizar cada vez que quisieras ver un mensaje en el chat.

Seguimiento de datos




Esto es una de las cosas que puede parecer mas laboriosa pero también la que mas te ayudara, me refiero a que todos los datos de entrada pueden tener un seguimiento para saber que llegan y se muestran tal y como queremos que funcione, muchos de los datos viajan por las cabeceras http que podemos manejar en nuestro código usando los arrays superglobales $_GET, $_POST, $_COOKIE , tambien cualquier INPUT (entrada) o petición a la base de datos mysqli_query() , no solo eso sino también peticiones a sistemas remotos (file_get_contents(),include(),etc).

Filtrar la entrada




Con esto se refiere a cualquier cosa que pueda ingresar desde fuentes remotas, un ejemplo seria: un cliente enviando un campo de entrada, ¿mas fácil? pues eso es tan simple por lo mismo se llama campo de entrada, pero como dijimos en el seguimiento de datos estos pueden viajar por cabeceras, una cosa que no mencione fue que también pueden estar incluidas las sesiones ya que como las sesiones viajan por el servidor y son devueltas a nosotros ¿se puede considerar como una fuente remota? yo pienso que si, así que mientras podamos filtrar o validar la entrada nosotros ponemos las reglas en nuestras aplicaciones :)

Por eso es mejor crear una lista blanca para filtrar datos que una lista negra de cosas que no queremos que estén en nuestra entrada, con esto me refiero a que imagines, que tu digas, NO QUIERO tal cosa y tal cosa en mi aplicación seria mas difícil saber que no quieres, simplemente por que esta lista no terminaría, ya que agregarías nuevas cosas mas adelante, pero, si puedes poner una lista de lo que SI QUIERES de esa manera solo esa lista esta disponible entonces aunque sea una cosa nueva mientras no este en lo que quieres no pasara desapercibido.

Un ejemplo seria un nombre de usuario donde solo aceptas caracteres alfanuméricos:

<?php
$clean = array();
if(ctype_alnum($_POST['usuario'])){
$clean['usuario'] = $_POST['usuario'];
}
?>

Una cosa interesante aquí es que estoy usando una función en lugar de expresiones regulares, esto lo hago por que es mas segura ya que es menos propensa a contener errores que las expresiones regulares, esto no significa que no podamos poner expresiones regulares seguras ya que no es así.

Escapar las salidas

No se si algún día habrán escuchado de magic_quotes, ahora esta obsoleta en php 5.3.0 y eliminada en php 5.4.0, esto lo que hacia era limpiar la entrada cuando agregaran comillas, aunque aveces no era necesario es por eso que también producía errores y fue eliminada ya que muchos usuarios piensan que esto filtraba todo, esto hace lo mismo que la función addslashes():





Otro ejemplo seria el anterior del usuario donde se filtraba la entrada pero no la salida, aunque es seguro ya que la entrada es segura no queda de mas también escapar la salida...

<?php
$html = array();
$html['usuario'] = htmlentities($clean['usuario'],ENT_QUOTES,'UTF-8');
echo "Bienvenido de nuevo, {$html['usuario']}";
?>


Por ultimo veamos el de base de datos, donde se puede inyectar código SQL si no escapamos los datos correctamente:

<?php
$mysql = array();
$mysql['usuario'] = mysqli_real_escape_string($clean['usuario']);
$sql = "SELECT * from perfil WHERE usuario={$mysql['usuario']}";
$resultado = mysqli_query($sql);
?>


Una manera de identificar salidas es buscando en nuestro codigo:


echo
print
printf
<?=

Hasta aquí va el primer capitulo espero que les haya gustado, preguntas y sugerencias para esta parte y no para la próxima la pueden hacer abajo.

Referencias:
* Essential PHP Security
* Documentación oficial de PHP
* Wikipedia
* UNAM

No hay comentarios:

Publicar un comentario en la entrada