Consideraciones de seguridad

Las aplicaciones web suelen enfrentarse a todo tipo de problemas de seguridad y es muy difícil que todo salga bien. Flask intenta resolver algunas de estas cosas por ti, pero hay un par más de las que tienes que ocuparte tú mismo.

Secuencia de comandos en sitios cruzados (XSS)

Secuencia de comandos en sitios cruzados es el concepto de inyectar HTML arbitrario (y con él JavaScript) en el contexto de un sitio web. Para remediarlo, los desarrolladores tienen que escapar adecuadamente el texto para que no pueda incluir etiquetas HTML arbitrarias. Para más información al respecto, consulte el artículo de Wikipedia sobre Cross-Site Scripting.

Flask configura Jinja2 para que escape automáticamente todos los valores a menos que se le indique explícitamente lo contrario. Esto debería descartar todos los problemas de XSS causados en las plantillas, pero todavía hay otros lugares donde hay que tener cuidado:

  • generar HTML sin la ayuda de Jinja2

  • llamar a Markup sobre los datos enviados por los usuarios

  • enviando el HTML de los archivos subidos, nunca lo hagas, utiliza la cabecera Content-Disposition: attachment para evitar ese problema.

  • enviando archivos de texto desde los archivos subidos. Algunos navegadores utilizan la adivinación del tipo de contenido basándose en los primeros bytes, por lo que los usuarios podrían engañar a un navegador para que ejecute HTML.

Otra cosa que es muy importante son los atributos no citados. Mientras que Jinja2 puede protegerte de problemas de XSS escapando del HTML, hay una cosa de la que no puede protegerte: XSS por inyección de atributos. Para contrarrestar este posible vector de ataque, asegúrate de citar siempre tus atributos con comillas dobles o simples cuando uses expresiones Jinja en ellos:

<input value="{{ value }}">

¿Por qué es necesario? Porque si no lo hicieras, un atacante podría inyectar fácilmente manejadores JavaScript personalizados. Por ejemplo, un atacante podría inyectar esta pieza de HTML+JavaScript:

onmouseover=alert(document.cookie)

Cuando el usuario se moviera con el ratón sobre la entrada, la cookie se presentaría al usuario en una ventana de alerta. Pero en lugar de mostrar la cookie al usuario, un buen atacante podría también ejecutar cualquier otro código JavaScript. En combinación con inyecciones de CSS, el atacante podría incluso hacer que el elemento llenara toda la página, de modo que el usuario sólo tuviera que tener el ratón en cualquier lugar de la página para desencadenar el ataque.

Hay una clase de problemas de XSS contra los que el escape de Jinja no protege. El atributo a de la etiqueta href puede contener un URI javascript:, que el navegador ejecutará al hacer clic si no se asegura correctamente.

<a href="{{ value }}">click here</a>
<a href="javascript:alert('unsafe');">click here</a>

Para evitarlo, deberá establecer la cabecera de respuesta Política de seguridad de contenidos (CSP).

La falsificación de solicitudes en sitios cruzados (CSRF)

Otro gran problema es el CSRF. Este es un tema muy complejo y no lo describiré aquí en detalle, sólo mencionaré lo que es y cómo prevenirlo teóricamente.

Si tu información de autenticación se almacena en cookies, tienes una gestión de estado implícita. El estado de «estar conectado» es controlado por una cookie, y esa cookie es enviada con cada petición a una página. Desgraciadamente, esto incluye las peticiones realizadas por sitios de terceros. Si no tienes esto en cuenta, algunas personas pueden ser capaces de engañar a los usuarios de tu aplicación con ingeniería social para que hagan cosas estúpidas sin que lo sepan.

Digamos que tienes una URL específica a la que, cuando envías peticiones POST borrará el perfil de un usuario (digamos http://example.com/user/delete). Si ahora un atacante crea una página que envíe una petición de post a esa página con algo de JavaScript sólo tiene que engañar a algunos usuarios para que carguen esa página y sus perfiles acabarán siendo eliminados.

Imagínate que tuvieras un Facebook con millones de usuarios concurrentes y que alguien enviara enlaces a imágenes de gatitos. Cuando los usuarios fueran a esa página, sus perfiles se borrarían mientras están viendo imágenes de gatitos peludos.

¿Cómo se puede evitar eso? Básicamente, para cada solicitud que modifique el contenido en el servidor tendría que utilizar un token de un solo uso y almacenarlo en la cookie y también transmitirlo con los datos del formulario. Después de recibir los datos en el servidor de nuevo, usted tendría que comparar los dos tokens y asegurarse de que son iguales.

¿Por qué Flask no lo hace por ti? El lugar ideal para que esto ocurra es el marco de validación de formularios, que no existe en Flask.

Seguridad JSON

En Flask 0.10 e inferiores, jsonify() no serializaba las matrices de nivel superior a JSON. Esto se debía a una vulnerabilidad de seguridad en ECMAScript 4.

ECMAScript 5 cerró esta vulnerabilidad, por lo que sólo los navegadores extremadamente antiguos siguen siendo vulnerables. Todos estos navegadores tienen otras vulnerabilidades más serias, así que este comportamiento fue cambiado y jsonify() ahora soporta serializar arrays.

Encabezados de seguridad

Los navegadores reconocen varias cabeceras de respuesta para controlar la seguridad. Le recomendamos que revise cada una de las cabeceras que aparecen a continuación para utilizarlas en su aplicación. La extensión Flask-Talisman puede utilizarse para gestionar HTTPS y las cabeceras de seguridad por ti.

Seguridad de transporte estricta HTTP (HSTS)

Indica al navegador que convierta todas las peticiones HTTP a HTTPS, lo que evita los ataques de tipo man-in-the-middle (MITM).

response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'

Política de seguridad de contenidos (CSP)

Indica al navegador desde dónde puede cargar varios tipos de recursos. Esta cabecera debería utilizarse siempre que sea posible, pero requiere cierto trabajo para definir la política correcta para su sitio. Una política muy estricta sería:

response.headers['Content-Security-Policy'] = "default-src 'self'"

X-Content-Type-Options

Obliga al navegador a respetar el tipo de contenido de la respuesta en lugar de intentar detectarlo, lo que puede ser abusado para generar un ataque de cross-site scripting (XSS).

response.headers['X-Content-Type-Options'] = 'nosniff'

X-Frame-Options

Evita que sitios externos incrusten su sitio en un iframe. Esto evita una clase de ataques en los que los clics en el marco externo pueden traducirse de forma invisible en clics en los elementos de su página. Esto también se conoce como «clickjacking».

response.headers['X-Frame-Options'] = 'SAMEORIGIN'

Fijación de la clave pública HTTP (HPKP)

Esto le dice al navegador que se autentique con el servidor usando sólo la clave del certificado específico para evitar ataques MITM.

Advertencia

Tenga cuidado al activar esto, ya que es muy difícil deshacerlo si configura o actualiza su clave incorrectamente.

Copiar/pegar en el terminal

Los caracteres ocultos, como el carácter de retroceso (\b, ^H), pueden hacer que el texto se muestre de forma diferente en HTML a como se interpreta si se pega en un terminal.

Por ejemplo, import y\bose\bm\bt\b se muestra como importar yosemite en HTML, pero los espacios traseros se aplican cuando se pega en un terminal, y se convierte en importar os.

Si espera que los usuarios copien y peguen código no fiable de su sitio, como el de los comentarios publicados por los usuarios en un blog técnico, considere la posibilidad de aplicar un filtrado adicional, como la sustitución de todos los caracteres \b.

body = body.replace("\b", "")

La mayoría de los terminales modernos advierten y eliminan los caracteres ocultos al pegar, por lo que esto no es estrictamente necesario. También es posible elaborar comandos peligrosos de otras maneras que no son posibles de filtrar. Dependiendo del caso de uso de su sitio, puede ser bueno mostrar una advertencia sobre la copia de código en general.