07 Seguridad PHP
07 Seguridad PHP
2/51
Seguridad PHP
Seguridad PHP
I. Introducción
Mediante el presente módulo, aplicaremos los conocimientos de OWASP en su proyecto Top Ten para
uno de los lenguajes de desarrollo web más empleados debido a su carácter libre, como es PHP.
II. Objetivos
El diseño seguro es solo una parte de la solución global desarrollada. Durante todo el ciclo de
desarrollo, cuando se está escribiendo el código, es importante tener en cuenta cualquier uso ilegítimo
de la aplicación. Muchas veces, la atención principal se centra en hacer que la aplicación funcione
como se pretende y, si bien esto es necesario, entregar una aplicación que funcione correctamente no
hace nada para ayudar a que la aplicación sea segura.
El hecho de que usted se encuentre cursando este módulo formativo, es una evidencia de que se
preocupa por la seguridad y, aunque parezca trivial, este es el paso más importante. Existen
numerosos recursos disponibles en la web, blogs, libros impresos e incluso recursos que se encuentran
en la biblioteca del Consorcio de Seguridad PHP en [Link]
3/51
Seguridad PHP
Un enfoque de lista blanca es mucho mejor que un enfoque de lista negra. Esto significa que usted
debe tener en cuenta todos los datos no válidos a menos que pueda demostrarse válido (en lugar de
considerar todos los datos válidos a menos que pueda demostrarse no válido). Piense siempre en
establecer una política de denegar a permitir como consejo general.
Riesgo de seguridad
¿Por qué es un riesgo de seguridad? De forma general, ya que cada aplicación es única y concebida
de una forma particular, se expresa un ejemplo común obtenido del propio manual de PHP:
<?php
if (authenticated_user()) {
$authorized = true; }
if ($authorized) {
include '/datos/privados/sensibles/[Link]'; }
?>
Si alguien tuviera acceso al código fuente, enseguida comprobaría que con register_globals
habilitado, se podría solicitar la página con el parámetro ?authorized=1 en la cadena de consulta para
eludir el control de acceso previsto.
4/51
Seguridad PHP
Por supuesto, esta vulnerabilidad en particular es debido a un mal diseño de la aplicación por los
desarrolladores, no en especial de la directiva register_globals, pero esto indica que existe un aumento
innecesario del riesgo que plantea el uso de la propia directiva. Sin su uso, las variables globales
comunes (como $authorized en el ejemplo) no se ven afectados por los datos presentados por el
cliente.
Una buena práctica es para inicializar todas las variables y desarrollar con la directiva
error_reporting establecido en E_ALL, por lo que no se debe pasar por alto el uso de una variable no
inicializada durante el desarrollo.
Otro ejemplo que ilustra cómo register_globals puede ser problemático es el siguiente uso de
incluir con una ruta dinámica:
¿Cuántas veces hemos observado esto mismo en nuestros códigos? Con register_globals activada,
esta página se puede solicitar con path=http%3A%2F%[Link]%2F%3F en la cadena de
consulta con el fin de mostrar este ejemplo real para las siguientes acciones:
allow_url_fopen
5/51
Seguridad PHP
Variable $path
El inicializar la variable $path puede mitigar este riesgo en particular, pero también lo hace la
desactivación de register_globals. Mientras que el error de un desarrollador puede conducir a una
variable no inicializada, deshabilitar register_globals, que es un cambio de configuración global
afectando a todas las variables, es mucho menos probable que se pase por alto con lo cual
mitigaremos el riesgo existente.
Sin embargo, el uso de los arrays superglobales $_POST y $_GET es todavía muy conveniente, y
no merece la pena plantear un riesgo añadido para habilitar register_globals.
V. Filtrado de datos
Como se ha comentado anteriormente, el filtrado de datos es la piedra angular de la seguridad de las
aplicaciones web, y esto es independiente del lenguaje de programación o plataforma.
Implica desarrollar un mecanismo por el cual se determina la validez de los datos que entran y salen
de la aplicación, y un buen diseño de software puede ayudar a los desarrolladores a:
Existen numerosos criterios sobre la forma de garantizar que el filtrado de los datos no pueda ser
anulada, pero hay dos enfoques generales que parecen ser los más comunes y ambos ofrecen un nivel
suficiente de garantía.
Este método por lo general requiere que se pase una variable GET o POST junto con cada URL,
identificando la tarea a realizar. Esta variable GET se puede considerar como un reemplazo para el
nombre del script que se utilizaría en un diseño más minimalista y simplificado. Por ejemplo:
6/51
Seguridad PHP
[Link]
Ejemplo
<?php
switch ($_GET['task']) {
case 'print_form':
include '/inc/presentation/[Link]';
break;
case 'process_form':
$form_valid = false;
include '/inc/logic/[Link]';
if ($form_valid) {
include '/inc/presentation/[Link]';
} else {
include '/inc/presentation/[Link]';
break;
default:
7/51
Seguridad PHP
include '/inc/presentation/[Link]';
break; }
?>
Si este es el único script PHP público, entonces debe quedar claro que el diseño de esta aplicación
se asegura de que las medidas de seguridad globales tomadas en la parte superior no pueden ser
anuladas.
También permite a un desarrollador ver fácilmente el flujo de control para una tarea específica. Por
ejemplo, en lugar de estar buscando a través de una gran cantidad de código, es muy fácil ver que
[Link] solo se muestra a un usuario cuando $form_valid se cumple, y como se inicializa como falso
justo antes, se incluye [Link]
Por supuesto, el desarrollo lógico dentro de [Link] debe establecerlo en true, ya que de lo
contrario el formulario se mostrará de nuevo (presumiblemente con mensajes de error
correspondientes).
Además, si se utiliza un archivo de índice de directorio como [Link] (en lugar de [Link]),
pueden emplear las URL como [Link]
Ejemplo
8/51
Seguridad PHP
Este módulo se incluye en la parte superior (o muy cerca de la parte superior) de todos los scripts
PHP que son públicos (disponible a través de URL). Considere el siguiente script [Link]:
<?php
switch ($_POST['form']) {
case 'login':
$allowed = array();
$allowed[] = 'form';
$allowed[] = 'username';
$allowed[] = 'password';
$sent = array_keys($_POST);
if ($allowed == $sent) {
include '/inc/logic/[Link]';
break; }
?>
Formulario HTML
En dicho ejemplo, se espera que cada formulario que se presente, debe ser un formulario que lo
identifica de forma única y, además, el fichero [Link] tiene un caso separado para manejar el
filtrado de datos para esa forma particular. Un ejemplo de un formulario HTML que cumple este
requisito es el siguiente:
9/51
Seguridad PHP
</form>
El array $allowed se utiliza para identificar exactamente qué variables se permiten de formulario, y
esta lista debe ser idéntica para que el formulario sea procesado. El control de flujo se determina en
otros lugares y es en [Link] donde el filtrado de datos real tiene lugar.
Además, una buena manera de asegurar que [Link] siempre se incluye en la parte superior de
cada script PHP es utilizar la directiva auto_prepend_file.
Correo electrónico
<?php
$clean = array();
$email_pattern = '/^[^@\s<&>]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';
if (preg_match($email_pattern, $_POST['email'])) {
$clean['email'] = $_POST['email']; }
?>
Color
El siguiente código filtra y asegura que $_POST[‘color´] solo puede tomar los valores red, green o
blue.
<?php
$clean = array();
switch ($_POST['color']) {
case 'red':
case 'green':
case 'blue':
10/51
Seguridad PHP
$clean['color'] = $_POST['color'];
break; }
?>
Entero
A continuación, nos aseguramos que $_POST[‘num’] solo puede recibir un valor entero:
<?php
$clean = array();
if ($_POST['num'] == strval(intval($_POST['num']))) {
$clean['num'] = $_POST['num']; }
?>
Valores reales
<?php
$clean = array();
if ($_POST['num'] == strval(floatval($_POST['num']))) {
$clean['num'] = $_POST['num']; }
?>
Esto demuestra una buena práctica ya que puede ayudar a los desarrolladores a identificar si los datos
están potencialmente contaminados o no. Jamás se debe hacer una validación de datos dejándolos en
$_POST o $_GET ya que los desarrolladores deben sospechar siempre de los datos dentro de estos
arrays superglobales.
Además, un uso más liberal de $clean puede permitir que usted considere todo lo demás para tener
datos maliciosos y esto se parece más a un enfoque de lista blanca, por lo que ofrece un mayor nivel de
seguridad.
11/51
Seguridad PHP
Si solo se guardan datos en $clean después de haber sido validados, el único riesgo de fracasar para
validar algo, es que usted puede hacer referencia a un elemento de un array que no existe nada más que
con datos potencialmente contaminados.
5.5. Sincronización
Una vez que un script desarrollado en PHP comienza a ser procesado, cualquier solicitud HTTP ha
tenido que ser recibida. Esto significa que el usuario no tiene otra oportunidad de enviar datos, y por lo
tanto no hay datos se puedan inyectar en la secuencia de comandos (incluso cuando register_globals está
habilitado). Esta es la razón por la que la inicialización de las variables es una buena práctica de
desarrollo seguro.
Error_reporting
Esta directiva establece el nivel de reporte de errores deseada. Se recomienda establecer E_ALL tanto
para el desarrollo como para la producción.
Display_errors
Esta directiva determina si los errores deben ser mostrados en la pantalla (incluido en la salida). Se
debe desarrollar con este conjunto en On, de modo que usted puede ser alertado y avisado de errores
durante el desarrollo, y usted debe establecer esta en Off para la producción, por lo que los errores se
ocultan a los usuarios y potenciales atacantes.
Log_errors
Esta directiva determina si los errores deben ser escritos en un fichero de registro. Si bien esto puede
plantear problemas de rendimiento, es deseable que los errores queden registrados. Si los errores de
registro presentan un grave problema de carga de E/S (Entrada/Salida), es probable que tenga
preocupaciones más grandes que el rendimiento de la aplicación. Debe establecer esta directiva en On
en la producción.
Error_log
Esta directiva indica la ubicación del fichero de registro para los errores que se almacenan. Asegúrese
de que el servidor web tiene privilegios de escritura para el archivo especificado.
Establecer error_reporting set para E_ALL ayudará a cumplir con la inicialización de todas las
variables, ya que cualquier referencia a una variable no definida genera una alerta.
12/51
Seguridad PHP
Cada una de estas directivas pueden configurarse con ini_set (), en caso de que usted no tenga
acceso al fichero [Link] o cualquier otro método de establecer estas directivas.
Una buena referencia de todas las funciones de manejo y presentación de informes de error se
encuentra en el propio manual de PHP:
[Link]
PHP 5 incluye manejo de excepciones. Es muy importante manejar y tratar las excepciones
cuando se deriva en una causa de error.
[Link]
Formulario de ejemplo
<select name="color">
<option value="red">red</option>
<option value="green">green</option>
<option value="blue">blue</option>
</select>
</form>
13/51
Seguridad PHP
</form>
Este nuevo formulario, puede ser ubicado en cualquier sitio, ni siquiera es necesario un servidor
web, ya que tan solo es necesario que lo interprete un navegador web. Además, incluso se puede
manipular en tránsito con un proxy tipo “ tamper data ” o desde el mismo origen. La propia URL
utilizada hace que la petición POST sea enviada al mismo lugar.
Esto hace que sea muy fácil de eliminar las restricciones del lado del cliente, si las restricciones de
formulario HTML o scripts del lado del cliente (como JavaScript, por ejemplo) destinados a realizar
algún tipo de filtrado de datos rudimentarios.
En este ejemplo en particular, $_POST ["color"] no es necesariamente de color rojo, verde o azul.
Con un procedimiento muy sencillo, cualquier usuario puede crear un formulario que se puede utilizar
para enviar cualquier tipo de datos a la dirección URL que procesa el formulario.
Host: [Link]
Content-Type: application/x-www-form-urlencoded
Content-Length: 9
color=red
14/51
Seguridad PHP
Por supuesto, y conforme vimos en el primer módulo, es posible mediante un programa como Telnet o
Netcat poder enviar cualquier otro tipo de dato a nuestra elección.
En resumidas cuentas, si no filtramos en nuestra aplicación los datos de entrada, nos exponemos a que
cualquier persona pueda enviar cualquier dato y tengamos un nivel de exposición elevado. Además,
conforme veremos en el último módulo, cada formulario debe contener un token único que haga que
solo pueda ser procesado si viene desde un origen controlado.
¿Cómo puede suceder esto? Si muestra el contenido que proviene de cualquier fuente externa sin
filtrar adecuadamente, se es vulnerable a XSS. Los datos externos, no se limitan exclusivamente a los
datos que viene desde el cliente. También significa que pueden encontrarse en un correo electrónico
que aparece en un cliente, un anuncio publicitario, un blog sindicado y similares. Cualquier
información que no se encuentra definida en el código fuente, viene de una fuente externa, y esto
generalmente significa que la mayoría de los datos son datos externos.
Ejemplo
<form>
15/51
Seguridad PHP
<input type="submit">
</form>
<?php if (isset($_GET['message'])) {
fclose($fp);
readfile('./[Link]');
?>
Este foro añade la etiqueta de HTML <br /> a lo que el usuario introduce, lo añade en un archivo y
a continuación muestra el contenido actual del archivo.
El siguiente usuario que visite este foro con JavaScript activado, va a ser redirigido a
[Link] y las cookies asociadas con el sitio actual se van a incluir junto con la cadena de
consulta de la URL.
Por supuesto, un atacante real no estaría limitado como en el ejemplo anterior o la experiencia de
JavaScript, tal como hemos visto en los módulos anteriores de OWASP. Hay que pensar en mejores o
más "maliciosos" ejemplos.
¿Qué se puede hacer? XSS es realmente muy fácil de detener. Cuando las cosas se ponen difíciles
es cuando se quieren permitir que algunas etiquetas de HTML sean aceptadas. Pero incluso estas
situaciones no son muy difíciles de manejar. Las siguientes prácticas pueden mitigar el riesgo de
XSS:
16/51
Seguridad PHP
Como se mencionó anteriormente, el filtrado de datos es la práctica más importante que puede
adoptar. Al validar todos los datos externos a medida que entran y salen en la solicitud, permiten
mitigar la mayoría de los riesgos de sufrir un XSS.
Emplear PHP con su lógica de filtrado. Funciones como htmlentities (), strip_tags () y utf8_decode
() pueden ser útiles. Trate de evitar la reproducción de algo que una función de PHP ya lo hace. Es
posible la función integrada de PHP no sea mucho más rápida que la suya, pero también estará
mucho más probado y será menos propenso a contener errores que produzcan vulnerabilidades.
Supongamos que los datos no son válidos hasta que se puedan demostrar como válidos. Esto
implica la verificación de la longitud y también asegurar que solo los caracteres válidos son
permitidos. Por ejemplo, si el usuario está suministrando un apellido, podría empezar por permitir
solo los caracteres alfabéticos y espacios. Es necesario proceder con cautela, ya que de esa forma,
mientras que los nombres de O'Reilly y Berners-Lee se considerarán inválidos, esto se puede
solucionar fácilmente añadiendo dos personajes más a la lista blanca. Es mejor negar que los datos
sean válidos que aceptar datos maliciosos.
Como se mencionó anteriormente, una convención de nombres puede ayudar a los desarrolladores
a que distingan fácilmente entre datos filtrados y sin filtrar. Es importante para los desarrolladores
hacer las cosas de la forma más fácil y clara que sea posible. La falta de claridad produce
confusión, y esto supone tener vulnerabilidades desde el origen en nuestro código.
Una versión mucho más segura del foro sería el siguiente código:
<form>
17/51
Seguridad PHP
<?php if (isset($_GET['message'])) {
$message = htmlentities($_GET['message']);
fclose($fp); }
readfile('./[Link]');
?>
Con la simple adición de htmlentities() el foro es ahora mucho más seguro. No debe considerarse
completamente seguro, pero este es probablemente el paso más fácil que usted puede tomar para
proporcionar un nivel adecuado de protección.
Por supuesto, es muy recomendable que usted siga todas las mejores prácticas que se han discutido.
Ejercicio
Vector de ataque:
?url="onm<>ouseover="ale<>rt(0)
Los ataques CSRF son más peligrosos, menos populares (lo que significa menos recursos para los
desarrolladores) y algo más difíciles de defender que los ataques XSS.
18/51
Seguridad PHP
Muchos usuarios pueden no confiar, pero es común para las aplicaciones web para ofrecer a
los usuarios ciertos privilegios al iniciar sesión en la aplicación. Los usuarios con estos
elevados privilegios son víctimas potenciales (cómplices sin saberlo, de hecho).
Generalmente implican sitios web que dependen de la identidad de los usuarios. Es típico
que la identidad de un usuario sea una de las cosas más importantes en la aplicación web.
Incluso con mecanismos de gestión de sesiones seguras, los ataques CSRF todavía podrían
tener éxito. De hecho, es en estos tipos de entornos donde los ataques de CSRF son más
potentes.
Realizar solicitudes HTTP a elección del atacante.
Los ataques CSRF involucran al atacante a falsificar una solicitud HTTP de otro usuario (en
esencia, engañar a un usuario para enviar una petición HTTP en nombre del atacante) como
si hubiera sido una petición legítima del usuario víctima.
Debido a que los ataques de CSRF implican la falsificación de peticiones HTTP, es muy
importante tener un nivel básico de familiaridad con HTTP.
Un navegador web es un cliente HTTP y un servidor web es un servidor HTTP. Los clientes inician
una transacción mediante el envío de una solicitud, y el servidor completa la transacción mediante el
envío de una respuesta. Una petición HTTP típica es la siguiente tal como se veía en el primer módulo
del curso:
GET / HTTP/1.1
Host: [Link]
La primera línea se llama “ línea de petición ” y contiene el método de petición (GET), solicitud de
URL (se utiliza una URL relativa “/”), y la versión de HTTP (1.1). Las otras líneas son cabeceras
HTTP, y cada nombre de la cabecera es seguida por dos puntos, un espacio y el valor.
Usted podría estar familiarizado con el acceso a esta información en PHP. Por ejemplo, el siguiente
código se puede utilizar para reconstruir esta petición HTTP especialmente en una cadena:
<?php
$request .= "{$_SERVER['SERVER_PROTOCOL']}\r\n";
19/51
Seguridad PHP
?>
Respuesta típica
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 57
<html>
</html>
Ejemplo de solicitud
El navegador solicita este recurso como lo haría cualquier otro, y lo que sigue es un ejemplo de una
solicitud de este tipo:
Host: [Link]
El navegador solicita la dirección URL especificada en el atributo src de la etiqueta img como si el
usuario hubiera navegado manualmente allí. El navegador no tiene manera de indicar específicamente
que espera que sea una imagen.
20/51
Seguridad PHP
Consideración
Combine esto con lo que ha aprendido acerca de los formularios y considere una URL similar a la
siguiente:
[Link]
Un envío de un formulario que utiliza un método GET potencialmente puede ser indistinguible de
una solicitud de imagen ya que ambos podrían ser las solicitudes de la misma URL.
Si register_globals está habilitado, el método del formulario ni siquiera es importante (a menos que
el desarrollador siga utilizando $_POST y similares).
Otra característica que hace a CSRF tan poderoso es que las cookies que pertenecen a una URL se
incluyen en la solicitud de la URL. Un usuario que tiene una sesión establecida con
[Link] (se ha validado como usuario) puede potencialmente comprar 1.000 acciones de
SCOX visitando una página con una etiqueta img que especifica la dirección URL en el ejemplo
anterior.
Formulario de ejemplo
<form action="/[Link]">
</form>
21/51
Seguridad PHP
Si el usuario introduce SCOX para el símbolo, 1000 como la cantidad, y envía el formulario, la
solicitud que se envía por el navegador es similar a lo siguiente:
Host: [Link]
Cookie: PHPSESSID=1234
Cookie
Se ha incluido en la cabecera una cookie para ilustrar la aplicación utilizando una cookie para el
identificador de sesión. Si una etiqueta img hace referencia a la misma URL, la misma cookie será
enviada en la solicitud de dicha URL, y el servidor de procesamiento de la solicitud no será capaz de
distinguir esto de una orden real.
<img src=”[Link]
Veríamos que la petición es exactamente la misma que la que produce el formulario auténtico:
Host: [Link]
Cookie: PHPSESSID=1234
Hay algunas cosas que pueden hacerse para proteger sus aplicaciones contra CSRF:
Utilice POST en lugar de emplear el método GET. Especifique POST en el atributo método de
todos los formularios con acciones. Por supuesto, esto no es apropiado para todos los formularios,
pero es adecuado cuando un formulario está realizando una acción, como la compra de acciones o
para actualizar cualquier información.
22/51
Seguridad PHP
Utilice $_POST en lugar de depender de register_globals. Utilizando el método POST para envíos
de formularios es inútil si usted confía en register_globals y forman referencia variables como
símbolo $ y $cantidad. También es inútil si utiliza $_REQUEST.
No se concentre en la conveniencia.
Si bien parece deseable hacer que la experiencia del usuario sea lo más relajada posible y menos
complicada, el exceso de conveniencia puede tener consecuencias graves.
El mayor problema con CSRF es tener peticiones que se parecen a los envíos de formularios pero
no lo son. Si un usuario no ha solicitado a la página con el formulario, debe asumir que… ¿La
petición es legítima e intencionada?
<?php
$token = md5(time());
fwrite($fp, "$token\n");
fclose($fp);
?>
<form method="POST">
<input type="submit">
23/51
Seguridad PHP
</form>
<?php
$tokens = file('./[Link]');
if (in_array($_POST['token'], $tokens)) {
if (isset($_POST['message'])) {
$message = htmlentities($_POST['message']);
fclose($fp);
readfile('./[Link]');
?>
Ejercicio
Solución
Más importante aún, es trivial para un atacante para obtener un token válido. Con solo visitar
esta página, se genera un token válido y se incluye en la fuente. Con un token válido, el ataque es
tan simple como antes de añadir el requisito token.
Ejercicio
24/51
Seguridad PHP
<?php
session_start();
if (isset($_POST['message'])) {
$message = htmlentities($_POST['message']);
fclose($fp);
$_SESSION['token'] = $token;
?>
<form method="POST">
<input type="submit">
</form>
<?php
readfile('./[Link]');
25/51
Seguridad PHP
?>
<?php
$host = '[Link]';
$username = 'myuser';
$password = 'mypass';
?>
Esto podría ser un ejemplo típico de un fichero denominado [Link] que se incluye siempre y
cuando necesitemos conectarlo a una base de datos. Este enfoque es conveniente, ya que mantiene las
credenciales de acceso en un único archivo.
Los posibles problemas de seguridad surgen cuando el archivo está en algún lugar dentro de la raíz
de los documentos o accesible en rutas predecibles o no protegidas adecuadamente. Como es lógico,
puede dar lugar a situaciones donde las credenciales de acceso queden expuestas.
26/51
Seguridad PHP
Recuerde que todo dentro de la raíz de los documentos tiene una URL asociada a ella.
Al conjuntar todo esto con el hecho de que la mayoría de los servidores web servirán las
extensiones de archivos .inc como texto sin ningún tipo de formato, se corre el riesgo de exponer las
credenciales de acceso en texto claro. Un problema mayor es que el código fuente de estos módulos se
puede exponer, pero las credenciales de acceso son particularmente sensibles y jamás deben revelarse.
Por supuesto, una solución muy sencilla es colocar todos los módulos fuera del documento raíz, en
un directorio “oculto” y sin nombres predeterminados, ya que nuestra aplicación conocerá
perfectamente cómo se denomina y dónde se encuentra y esto es una buena práctica.
Tanto las funciones de PHP include() y require() pueden aceptar una ruta del sistema de ficheros,
por lo que no hay ninguna necesidad de hacerlo accesible a través de módulos de URL. Es tener un
riesgo innecesario.
Si no se tuviera ninguna opción para poder ubicar los módulos como deseemos y deben de
encontrarse obligatoriamente dentro de la raíz de documentos, puede especificarse algo como lo
siguiente en su archivo [Link] (suponiendo Apache) o en un fichero .htaccess:
<Files ~ "\.inc$">
Order allow,deny
</Files>
27/51
Seguridad PHP
No es una buena práctica tener sus módulos de procesado por el motor de PHP. Esto incluye el
cambio de nombre de sus módulos con una extensión .php, así como el uso de AddType para tratar a
los archivos .inc como ficheros de PHP.
La ejecución de código fuera del contexto previsto puede ser muy peligroso, porque es inesperado y
puede conducir a resultados desconocidos. Sin embargo, si sus módulos constan de solo asignaciones
de variables (como en el ejemplo anterior) se mitiga este riesgo en particular.
Una solución eficaz para proteger las credenciales de acceso de base de datos se describe en el libro
de PHP (O'Reilly) por David Sklar y Adam Trachtenberg. Se crea un fichero con las credenciales en
/ruta/secreta/a/fichero, que solo el usuario root (o cualquier usuario que lo necesite) puede leer, pero
no cualquier usuario:
Por tanto, no será necesario escribir en el código el nombre de usuario y contraseña en cualquiera
de las secuencias de comandos, el servidor web tampoco podrá leer el fichero de credenciales y no
podrán ser leídas por otros usuarios. Solo tenga cuidado de no exponer estas variables con algo como
phpinfo() o print_r($ _ SERVER).
28/51
Seguridad PHP
Conforme hemos visto en los módulos de OWASP, realizar un ataque de inyección SQL es
extremadamente simple y es muy fácil poder defenderse del mismo, pero muchísimas aplicaciones web
siguen siendo vulnerables a dichos ataques.
<?php
$sql = "INSERT
reg_password,
reg_email)
VALUES ('{$_POST['reg_username']}',
'$reg_password',
'{$_POST['reg_email']}')";
?>
Esta consulta se construye con $_POST, que debe de ser inmediatamente sospechosa
Supongamos que esta consulta es la creación de una nueva cuenta. El usuario proporcionará un
nombre de usuario deseado y una dirección de correo electrónico. La solicitud de registro generará
una contraseña temporal y enviará un mensaje de correo electrónico al usuario para verificar la
dirección de correo electrónico suministrada.
Esto ciertamente no será jamás un nombre de usuario válido, pero sin usar el filtrado de datos, la
aplicación no lo puede decidir.
<?php
29/51
Seguridad PHP
$sql = "INSERT
reg_password,
reg_email)
'1234',
'shiflett@[Link]')";
?>
En lugar de la acción pretendida de crear de una única cuenta (good_guy) con una dirección de
correo electrónico válida, la solicitud ha inyectado el código necesario en SQL para crear dos
cuentas y el usuario suministrado, con todos los detalles de la cuenta bad_guy.
Si bien este ejemplo en particular podría no parecer tan nocivo, conforme se ha abordado en los
módulos referentes a OWASP, debe quedar claro que el ejemplo sería una de las “mejores” cosas
que podrían suceder una vez que un atacante pueda realizar modificaciones en las sentencias SQL.
Por ejemplo, en función de la base de datos que se emplee, es posible que se permita enviar
varias consultas al servidor de base de datos en una sola llamada. Así, un usuario potencialmente
puede terminar la consulta existente con un punto y coma y pueda emplear a continuación una
consulta completamente libre y no limitada de la elección del atacante.
MySQL hasta hace muy poco tiempo, no permitía varias consultas por lo que este riesgo en
particular se mitiga. Las nuevas versiones de MySQL permiten varias consultas, pero la extensión
de PHP correspondiente (ext/mysqli) requiere el uso de una función separada si desea enviar varias
consultas empleando mysqli_multi_query() en lugar de mysqli_query().
30/51
Seguridad PHP
Por supuesto, es mucho más seguro permitir realizar una única consulta ya que limita lo que un
atacante pueda potencialmente realizar contra nuestra base de datos.
IX. Sesiones
Es por ello que es clave para el atacante obtener el identificador de sesión, ya que esto es necesario
para cualquier ataque de suplantación. Hay tres métodos comunes utilizados para obtener un
identificador de sesión válido:
Predicción
31/51
Seguridad PHP
Captura
Intentar capturar un identificador de sesión válido es el tipo más común de ataque de sesiones y
hay numerosas técnicas para poder hacerlo. Debido a que los identificadores de sesión se
transmiten normalmente en las cookies o como variables GET, los diferentes enfoques se centran
en atacar estos métodos para poder conseguirlas. Aunque ambos pueden ser atacados, es mucho
más seguro guardar la sesión mediante cookies que por un método GET.
Fijación
Es el método más simple de obtener un identificador de sesión válido. Aunque no es muy difícil
de proteger la aplicación, si su mecanismo de sesión consiste en nada más que session_start() es
vulnerable.
Para poder demostrar la fijación de una sesión, supongamos el siguiente código [Link]:
<?php
session_start();
if (!isset($_SESSION['visits'])) {
$_SESSION['visits'] = 1;
} else {
$_SESSION['visits']++;
echo $_SESSION['visits'];
?>
Cuando se visita la página por primera vez, se obtendrá 1 en la salida. Cada visita posterior,
aumentará la sesión para mostrar cuántas veces ha sido visitada.
32/51
Seguridad PHP
Para poder realizar el ejercicio, es necesario que no exista ningún identificador de sesión previo.
Simplemente para atacar esta aplicación sería necesario visitarla con un parámetro como ?
PHPSESSID=1234 en la URL.
A continuación, con otro navegador completamente diferente o incluso otro equipo, si se visita con
la misma URL ?PHPSESSID=1234 se demostrará que no se ve la salida en 1, sino que se continuará
con la sesión que se había iniciado con anterioridad en el otro navegador o por otro usuario.
¿Por qué puede ser esto un problema? En la inmensa mayoría de ataques de fijación de sesión, tan
solo es necesario emplear un enlace o una redirección a nivel de protocolo para enviar un usuario a un
sitio remoto con el identificador de sesión incluido en la URL. El usuario probablemente no se dará
cuenta, puesto que el sitio se comportará exactamente de la misma forma. Debido a que el atacante ha
elegido el identificador de sesión y como se ha comprobado, podrá ser empleado para lanzar ataques
de suplantación con secuestro de la sesión de usuario.
Un ataque tan simple como el presentado es muy fácil de prevenir. Si no hay una sesión activa
asociada con un identificador de sesión que el usuario esté presentando a la aplicación, entonces será
necesario regenerarlo solo para poder estar seguros:
<?php
session_start();
if (!isset($_SESSION['initiated'])) {
session_regenerate_id();
$_SESSION['initiated'] = true;
?>
33/51
Seguridad PHP
El problema con una defensa tan simple y minimalista como la presentada es que un atacante puede
inicializar una sesión para un identificador de sesión válido en particular y, a continuación, utilizar ese
identificador para lanzar el ataque.
Para protegerse contra este tipo de ataque, primero hay que considerar que un secuestro de la
sesión solo será útil cuando el usuario ha iniciado sesión o ha obtenido de otro modo un mayor nivel
de privilegio. Así que, si modificamos la forma de regenerar el identificador de sesión cuando se
produzca algún cambio en el nivel de privilegios (por ejemplo, después de verificar un nombre de
usuario y contraseña), habremos eliminado prácticamente el riesgo de un ataque de fijación de sesión
con éxito.
Al igual que ocurre con el ataque anterior de fijación de sesión, si el mecanismo de sesión solo se
compone de la función session_start(), que es vulnerable, el método para capturar la sesión conforme
vimos en los módulos de OWASP no es tan simple.
Más que centrarse en cómo mantener el identificador de sesión para ser capturado, el objetivo será
complicar la suplantación, ya que con cada capa que podamos añadir, aumentaremos la seguridad de la
aplicación. Para ello, vamos a examinar las medidas necesarias para secuestrar con éxito una sesión. En
cada escenario, vamos a suponer que el identificador de sesión se ha visto comprometida.
Con el mecanismo de la sesión más simple, un identificador de sesión válido es todo lo que se necesita
para secuestrar con éxito una sesión. Para mejorar esto, tenemos que comprobar si en la petición HTTP
que podemos utilizar para la identificación adicional hay algún campo adicional.
No es prudente confiar a nivel de protocolo TCP/IP, como la dirección IP, ya que estos son los
protocolos de nivel inferior que no están destinados para dar cabida a las actividades que tienen
lugar en el nivel HTTP. Un único usuario puede potencialmente tener una dirección IP diferente
para cada solicitud, y múltiples usuarios pueden potencialmente tener la misma dirección IP.
34/51
Seguridad PHP
GET / HTTP/1.1
Host: [Link]
Cookie: PHPSESSID=1234
Conforme hemos visto anteriormente, tan solo es necesario el identificador del host para la petición
HTTP.
Es por ello que es poco prudente confiar en cualquier otra cosa. Sin embargo, la consistencia es
realmente todo lo que necesitamos, porque solo estamos interesados en complicar la suplantación sin
que afecte negativamente a los usuarios legítimos.
Vamos a suponer que la solicitud anterior es seguida por una solicitud con un user-agent diferente:
GET / HTTP/1.1
Host: [Link]
Cookie: PHPSESSID=1234
Aunque se presenta la misma cookie, si suponemos que se trata del mismo usuario, es muy poco
probable que el navegador modifique la cabecera user-agent entre las peticiones.
Comprobación adicional
<?php
session_start();
if (isset($_SESSION['HTTP_USER_AGENT'])) {
35/51
Seguridad PHP
if ($_SESSION['HTTP_USER_AGENT'] !=
md5($_SERVER['HTTP_USER_AGENT'])){
exit;
} } else {
$_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
?>
Con el anterior código, entonces, un atacante deberá no solo presentar un identificador de sesión
válido sino que tendrá que emplear la cabecera user-agent correcta asociada con la sesión. Esto
complica las cosas un poco y, por lo tanto, es un poco más seguro.
¿Podemos mejorar esto? Por supuesto. Para el atacante, sería muy fácil obtener la cabecera user-
agent correcta y secuestrar la sesión.
Por ejemplo, podríamos hacer que el usuario envíe el MD5 del user-agent en cada solicitud. Un
atacante podría ya no solo recrear los encabezados que las solicitudes de la víctima
contienen, también sería necesario suministrar esta información adicional.
Hay que tener en cuenta, que también sería trivial poder obtener el MD5 del user-agent para el
atacante, pero podemos complicar un poco más todo añadiendo un poco más de aleatoriedad a la
forma en que se construye el token:
<?php
$string = $_SERVER['HTTP_USER_AGENT'];
$fingerprint = md5($string);
?>
36/51
Seguridad PHP
Teniendo en cuenta que estamos pasando el identificador de sesión en una cookie y esto ya requiere
que el ataque comprometa la misma para obtenerla, debemos enviar esta huella digital como una
variable URL. Esto debe estar en todas las direcciones URL como si fuera el identificador de sesión,
ya que ambos deben ser necesarios para que una sesión para continuar de forma automática, sin
olvidar nunca todos los controles de paso adicionales.
Con el fin de asegurarse de que los usuarios legítimos no son tratados como atacantes, simplemente
solicite una contraseña si no se pasa el testigo. Si hay un error en su mecanismo o se sospecha de un
ataque de suplantación, lo que provocó solicitar una contraseña antes de continuar, es la forma menos
ofensiva para manejar la situación. De hecho, los usuarios pueden apreciar el toque adicional de
protección percibida por una consulta de este tipo.
Hay muchos métodos diferentes que puede utilizar para complicar la suplantación y proteger sus
aplicaciones de secuestro de sesión.
X. Alojamientos compartidos
Si alojamos nuestras aplicaciones web en un servidor compartido, como es lógico, la seguridad no va
a ser tan fuerte como cuando se hace en un host dedicado. Esta es una de las desventajas de tener un
precio muy bajo.
37/51
Seguridad PHP
Tan solo el usuario con el que se ejecuta el servidor HTTP será el único que podrá leer estos
archivos de sesión. Ningún otro usuario podrá.
$ ls /tmp
total 12
Por desgracia, es trivial escribir un script PHP para leer estos archivos y debido a que se ejecutará
como el usuario nobody (o cualquier usuario que use el servidor web) tendrá concecidos los privilegios
necesarios para poder hacerlo.
La directiva safe_mode puede ayudar a prevenir esto y solucionar otros problemas de seguridad
similares, pero ya que solo se aplica a PHP, no aborda la causa raíz del problema. Los atacantes pueden
simplemente utilizar otros lenguajes de desarrollo para poder tener acceso.
¿Cuál es la mejor solución? No use el mismo lugar para almacenar las sesiones como todos los demás.
Es preferible almacenarlos en una base de datos o incluso emplear la función session_set_save_handler()
para omitir el tratamiento de sesión predeterminado de PHP con sus propias funciones de PHP.
Como hemos dicho, tendrá que analizarse cada una de las funciones dentro del contexto empleado,
pues no son en realidad “maliciosas” sino que podrían ser empleadas para cometer acciones no previstas
a través de PHP.
38/51
Seguridad PHP
Ejecución en shell
Ejecución PHP
preg_replace (con modificador /e) - Realiza una búsqueda y sustitución de una expresión regular
require[_once] - es idéntico a include excepto que en caso de fallo producirá un error fatal de nivel
E_COMPILE_ERROR. En otras palabras, éste detiene el script mientras que include sólo emitirá una
advertencia (E_WARNING) lo cual permite continuar el script.
Ejecución de comandos
Ejecución de PHPs
39/51
Seguridad PHP
eval()
create_function()
include()
include_once()
require()
require_once()
$_GET['func_name']($_GET['argument']); $func = new
ReflectionFunction($_GET['func_name']); $func->invoke(); or $func-
>invokeArgs(array());
'ob_start' => 0,
'array_filter' => 1,
'array_map' => 0,
'array_reduce' => 1,
40/51
Seguridad PHP
'array_walk_recursive' => 1,
'array_walk' => 1,
'assert_options' => 1,
'uasort' => 1,
'uksort' => 1,
'usort' => 1,
'preg_replace_callback' => 1,
'spl_autoload_register' => 0,
'iterator_apply' => 1,
'call_user_func' => 0,
'call_user_func_array' => 0,
'register_shutdown_function' => 0,
'register_tick_function' => 0,
'set_error_handler' => 0,
'set_exception_handler' => 0,
'sqlite_create_function' => 2,
Divulgación de información
41/51
Seguridad PHP
phpinfo
posix_mkfifo
posix_getlogin
posix_ttyname
getenv
get_current_user
proc_get_status
get_cfg_var
disk_free_space
disk_total_space
diskfreespace
getcwd
getlastmo
42/51
Seguridad PHP
getmygid
getmyinode
getmypid
getmyuid
Otros
ini_set mail - tiene inyección CRLF en el 3er parámetro, abre la puerta al spam. header - en
sistemas antiguos la inyección CRLF podría ser usada para xss u otros propósitos, ahora todavía es un
problema si hacen un header("location: ...."); y no mueren();. El script sigue ejecutándose después de
una llamada a header(), y aún así imprimirá la salida normalmente. Esto es desagradable si usted está
tratando de proteger un área administrativa.proc_nice
proc_terminate
proc_close
pfsockopen
fsockopen
apache_child_terminate
posix_kill
posix_mkfifo
posix_setpgid
posix_setsid
posix_setuid
43/51
Seguridad PHP
Manejadores de ficheros
fopen
tmpfile
bzopen
gzopen
SplFileObject->__construct
chgrp
chmod
chown
copy
file_put_contents
lchgrp
lchown
link
mkdir
move_uploaded_file
rename
rmdir
symlink
tempnam
touch
unlink
44/51
Seguridad PHP
iptcembed
ftp_get
ftp_nb_get
file_exists
file_get_contents
file
fileatime
filectime
filegroup
fileinode
filemtime
fileowner
fileperms
filesize
filetype
glob
is_dir
is_executable
45/51
Seguridad PHP
is_file
is_link
is_readable
is_uploaded_file
is_writable
is_writeable
linkinfo
lstat
parse_ini_file
pathinfo
readfile
readlink
realpath
stat
gzfile
readgzfile
getimagesize
imagecreatefromgif
imagecreatefromjpeg
imagecreatefrompng
imagecreatefromwbmp
imagecreatefromxbm
imagecreatefromxpm
ftp_put
ftp_nb_put
exif_read_data
46/51
Seguridad PHP
read_exif_data
exif_thumbnail
exif_imagetype
hash_file
hash_hmac_file
hash_update_file
md5_file
sha1_file
highlight_file
show_source
php_strip_whitespace
get_meta_tags
XII. Resumen
Dentro de este módulo específico, hablamos de vulnerabilidades encontradas dentro del lenguaje PHP,
uno de los más populares y utilizados, dada su naturaleza, open source .
Aun enfocándonos y basándonos en OWASP “Top Ten”, en concreto midiendo y analizando las
diferentes vulnerabilidades encontradas y explotadas dentro de PHP, hemos podido aprender que, debido
a su origen, tiene ciertas funciones que pueden ser explotadas y conllevar a una vulnerabilidad o ataque.
Por ejemplo, si hablamos de shell podemos encontrar:
Exec.
Sustem.
Popen.
Estos, a su vez, si los ejecutamos como comandos logramos conseguir información dentro del propio
código si este no está correctamente asegurado.
Además, encontramos dentro de PHP una de las áreas más vulnerables que es el proceso de
formulario, donde podemos encontrar Cross Site Scripting (XSS) y Cross-Site Request Forgery (CSRF),
ambas incluidas dentro de la lista de vulnerabilidades de OWASP 2017.
Finalmente, también encontramos que una vulnerabilidad recurrente y persistente dentro de PHP es la
inyección de SQL, vulnerabilidad encontrada en numerosas aplicaciones web.
47/51
Seguridad PHP
XIII. Referencias
Revisado sin licencias que no permitan explotación comercial.
[Link]
[Link]
[Link]
[Link]
[Link]
Mann, E. (2017). Security Principles for PHP Applications: A Php [architect] Guide.
Louys, P. (2018). Professional PHP. Building Maintainable and Secure Applications. CreateSpace
Independent Publushing Platform.
Ahsman, H., Choo, K.R., y Prokhorenko, V. (2016): “Intent-Based Extensible Real-Time PHP
Supervision Framework”. IEEE Transactions on Information Forensics and Security. Volumen 11
(10).
48/51
Seguridad PHP
Ejercicios
Ejercicio propuesto
Puede ayudarse con el Proyecto de Fin de Máster con licencia GPLv3 disponible en [Link]
.[Link]/recursos/Proyectos/PFM/2011_12/PFM_DVWA.pdf
49/51
Seguridad PHP
Recursos
Bibliografía
Professional PHP. Building Maintainable and Secure Applications. : Louys, P. (2018).
Professional PHP. Building Maintainable and Secure Applications. CreateSpace Independent
Publushing Platform.
Security Principles for PHP Applications: A Php [architect] Guide. : Mann, E. (2017).
Security Principles for PHP Applications: A Php [architect] Guide.
Ataques de XSS en entornos Microsoft: Protección general.:
[Link]
Boletines de seguridad de Microsoft: TechCenter de Seguridad.:
[Link]
Intent-Based Extensible Real-Time PHP Supervision Framework. : Ahsman, H., Choo,
K.R., y Prokhorenko, V. (2016): “Intent-Based Extensible Real-Time PHP Supervision
Framework”. IEEE Transactions on Information Forensics and Security. Volumen 11 (10).
Mensajes de error seguros: NET Framework. : [Link]
es/library/994a1482(v=vs.100).aspx
Microsoft Developer Network. Cifrar información de configuración mediante una
configuración protegida.: [Link]
Microsoft Developer Network. Cómo: Mostrar mensajes de error seguros.:
[Link]
Microsoft Developer Network. [Link]
(Propiedad).: [Link]
es/library/[Link](v=vs.100).aspx
Microsoft Developer Network. [Link] (Espacio de nombres).:
[Link]
Microsoft Developer Network. Tutorial: Cifrar la información de configuración
mediante la configuración protegida.: [Link]
es/library/dtkwfdky(v=vs.100).aspx
Microsoft Developer Network: MSDN.: [Link]
Microsoft Docs. Información general sobre los ataques mediante secuencias de
comandos.: [Link]
Microsoft LINQ: Language-Integrated Query.: [Link]
es/library/[Link]
Microsoft Patterns & Practices: Patterns & Practices. : [Link]
Microsoft Safety & Security Center: Security Center.:
[Link]
Microsoft TechCenter de Seguridad.: [Link]
es/security/[Link]
50/51
Seguridad PHP
OWASP “Top Ten” for .Net Developers: Troy Hunt. Microsoft MVP Licencia Creative
Commons Attibution 3.0 unported license.: [Link]
[Link]
Glosario.
Denegación de servicio (DoS): Hacer deliberadamente que una aplicación esté menos
disponible de lo que debería. El típico ejemplo es la sobrecarga de una aplicación web para que
no pueda servir más recursos al resto de usuarios.
Suplantar (spoof): Emplear los datos legítimos de otro usuario de forma no autorizada.
WAF (Web Application Firewall): Medida adicional de seguridad que se implementa entre
el cliente y el servidor de aplicaciones. En cada petición que se recibe, realiza una inspección
profunda de los datos que contiene para proteger a la aplicación y/o servidores de cualquier
tráfico malicioso que hubiera tenido lugar y poder aislar y bloquearlo en tiempo real.
51/51