El autor seleccionó Girls Who Code para recibir una donación como parte del programa Write for DOnations.
Introducción
Cuando visitas un sitio web, se utilizan varios recursos para cargarlo y representarlo. Por ejemplo, cuando vas a https://www.digitalocean.com
, tu navegador descarga el HTML y CSS directamente desde digitalocean.com
. Sin embargo, las imágenes y otros activos se descargan desde assets.digitalocean.com
, y los scripts de análisis se cargan desde sus respectivos dominios.
Algunos sitios web utilizan una multitud de diferentes servicios, estilos y scripts para cargar y representar su contenido, y tu navegador ejecutará todo ello. Un navegador no sabe si el código es malicioso, por lo que es responsabilidad del desarrollador proteger a los usuarios. Dado que puede haber muchos recursos en un sitio web, tener una función en el navegador que solo permita recursos aprobados es una buena manera de asegurar que los usuarios no se vean comprometidos. Para eso sirven las Políticas de Seguridad de Contenido (CSP, por sus siglas en inglés).
Usando un encabezado CSP, un desarrollador puede permitir explícitamente que ciertos recursos se ejecuten mientras se evita que se ejecuten todos los demás. Dado que la mayoría de los sitios pueden tener más de 100 recursos, y cada uno debe ser aprobado para la categoría específica de recurso que es, implementar un CSP puede ser una tarea tediosa. Sin embargo, un sitio web con un CSP será más seguro ya que garantiza que solo se permitan ejecutar los recursos aprobados.
En este tutorial, implementarás un CSP en una aplicación básica de Django. Personalizarás el CSP para permitir que ciertos dominios y recursos en línea se ejecuten. Opcionalmente, también puedes usar Sentry para registrar violaciones.
Prerrequisitos
Para completar este tutorial, necesitarás:
- A working Django project (version 3 or greater is preferred), either on your local machine or a DigitalOcean Droplet. If you don’t have one, you can create one with the tutorial, How to Install Django and Set Up a Development Environment on Ubuntu 20.04.
- A web browser like Firefox or Chrome and an understanding of browser network tools. For more on using browser network tools, check out the product documentation for the Network Monitor in Firefox or the DevTools Network Tab in Chrome. For more general guidance on browser developer tools, see the guide: What are Browser Developer Tools?
- Conocimientos de Python 3 y Django, que puedes adquirir de la serie de tutoriales, Cómo Codificar en Python y Desarrollo en Django.
- Una cuenta en Sentry para rastrear violaciones del CSP (opcional).
Paso 1: Crear una Vista de Demostración
En este paso, modificarás cómo tu aplicación maneja las vistas para que puedas agregar soporte CSP.
Como requisito previo, has instalado Django y configurado un proyecto de muestra. La vista predeterminada en Django es demasiado simple para demostrar todas las capacidades del middleware CSP, así que crearás una página HTML simple para este tutorial.
Navega a la carpeta del proyecto que creaste en los prerrequisitos:
Mientras estés dentro del directorio django-apps
, crea tu entorno virtual. Lo llamaremos genéricamente env
, pero deberías usar un nombre que tenga sentido para ti y tu proyecto.
Ahora, activa el entorno virtual con el siguiente comando:
Dentro del entorno virtual, crea un archivo views.py
en la carpeta de tu proyecto utilizando nano
, o tu editor de texto favorito:
Ahora, agregarás una vista básica que renderizará una plantilla index.html
que crearás a continuación. Agrega lo siguiente a views.py
:
Guarda y cierra el archivo cuando hayas terminado.
Crea una plantilla index.html
en un nuevo directorio templates
:
Agrega lo siguiente a index.html
:
La vista que creamos renderizará esta página HTML simple. Mostrará el texto Hola, Sammy! junto con una imagen de Sammy el Tiburón.
Guarda y cierra el archivo cuando hayas terminado.
Para acceder a esta vista, necesitarás actualizar urls.py
:
Importa el archivo views.py
y agrega una nueva ruta añadiendo las líneas resaltadas:
La nueva vista que acabas de crear ahora será visible cuando visites /
(cuando la aplicación esté en ejecución).
Guarda y cierra el archivo.
Finalmente, necesitarás actualizar INSTALLED_APPS
para incluir testsite
en settings.py
:
Aquí, agregas testsite
a la lista de aplicaciones en settings.py
para que Django pueda hacer algunas suposiciones sobre la estructura de tu proyecto. En este caso, asumirá que la carpeta templates
contiene plantillas de Django que puedes usar para renderizar vistas.
Desde el directorio raíz del proyecto (testsite
), inicia el servidor de desarrollo de Django con el siguiente comando, reemplazando tu-dirección-ip-del-servidor
con la dirección IP de tu propio servidor.
Abre un navegador y visita tu-dirección-ip-del-servidor:8000
. La página debería lucir similar a esta:
En este punto, la página muestra una imagen de perfil de Sammy el tiburón. Debajo de la imagen está el texto ¡Hola, Sammy! en script azul.
Para detener el servidor de desarrollo de Django, presiona CONTROL-C
.
En este paso, creaste una vista básica que actúa como la página de inicio de tu proyecto de Django. A continuación, agregarás soporte para CSP a tu aplicación.
Paso 2 — Instalando Middleware CSP
En este paso, instalarás e implementarás un middleware CSP para que puedas agregar encabezados CSP y trabajar con características CSP en tus vistas. Los middlewares agregan funcionalidad adicional a cualquier solicitud o respuesta que maneje Django. En este caso, el Middelware Django-CSP agrega soporte CSP a las respuestas de Django.
Primero, instalarás el Middleware de CSP de Mozilla en tu proyecto Django usando pip
, el gestor de paquetes de Python. Utiliza el siguiente comando para instalar el paquete necesario desde PyPi, el Índice de Paquetes de Python. Para ejecutar el comando, puedes detener el servidor de desarrollo de Django usando CONTROL-C
o abrir una nueva pestaña en tu terminal:
A continuación, añade el middleware a la configuración del proyecto Django. Abre settings.py
:
Con django-csp
instalado, ahora puedes agregar el middleware en settings.py
. Esto añadirá encabezados CSP a tus respuestas.
Añade la siguiente línea al arreglo de configuración MIDDLEWARE
:
Guarda y cierra el archivo cuando hayas terminado. Tu proyecto Django ahora admite CSPs. En el siguiente paso, comenzarás a agregar encabezados CSP.
Paso 3 — Implementación de un Encabezado CSP
Ahora que tu proyecto admite CSPs, está listo para ser asegurado. Para lograrlo, configurarás el proyecto para añadir encabezados CSP a tus respuestas. Un encabezado CSP es lo que le indica al navegador cómo comportarse cuando encuentra un tipo de contenido particular. Entonces, si el encabezado dice solo permitir imágenes de un dominio particular, entonces el navegador solo permitirá imágenes de ese dominio.
Usando nano o tu editor de texto favorito, abre settings.py
:
Define las siguientes variables en cualquier parte del archivo:
Estas reglas son el punto de partida para tu CSP. Estas líneas indican qué fuentes están permitidas para imágenes, hojas de estilo y scripts, respectivamente. En este momento, todas contienen la cadena 'self'
, lo que significa que solo se permiten recursos de tu propio dominio.
Guarda y cierra el archivo cuando hayas terminado.
Ejecuta tu proyecto Django con el siguiente comando:
Cuando visites tu-ip-del-servidor:8000
, verás que el sitio está roto:
Como era de esperar, la imagen no aparece y el texto aparece con el estilo predeterminado (negrita negra). Esto significa que el encabezado CSP está siendo aplicado, y nuestra página ahora es más segura. Debido a que la vista que creaste anteriormente está haciendo referencia a hojas de estilo e imágenes de dominios que no son el tuyo, el navegador los bloquea.
Ahora tu proyecto tiene un CSP funcional que le indica al navegador que bloquee los recursos que no son de tu dominio. A continuación, modificarás el CSP para permitir recursos específicos, lo que solucionará la falta de imagen y estilos en la página de inicio.
Paso 4 — Modificar el CSP para Permitir Recursos Externos
Ahora que tienes una CSP básica, la modificarás en función de lo que estés utilizando en tu sitio. Como ejemplo, un sitio web que utiliza fuentes de Adobe y videos incrustados de YouTube necesitará permitir estos recursos. Sin embargo, si tu sitio web solo muestra imágenes desde tu propio dominio, puedes dejar la configuración de imágenes en sus valores predeterminados restrictivos.
El primer paso es encontrar cada recurso que necesitas aprobar. Puedes utilizar las herramientas de desarrollo de tu navegador para hacer esto. Abre el Monitor de red en Inspeccionar elemento, actualiza la página y observa los recursos bloqueados:
El registro de red muestra que dos recursos están siendo bloqueados por la CSP: una hoja de estilos de fonts.googleapis.com y una imagen de html.sammy-codes.com. Para permitir estos recursos en la cabecera CSP, deberás modificar las variables en settings.py.
Para permitir recursos de dominios externos, agrega el dominio a la parte de la CSP que coincide con el tipo de archivo. Entonces, para permitir una imagen de html.sammy-codes.com, agregarás html.sammy-codes.com a CSP_STYLE_SRC.
Abre settings.py y agrega lo siguiente a la variable CSP_STYLE_SRC:
Ahora, en lugar de permitir solo imágenes desde tu dominio, el sitio también permite imágenes desde html.sammy-codes.com.
La vista del índice utiliza Google Fonts. Google suministra a tu sitio las fuentes (desde https://fonts.gstatic.com
) y una hoja de estilos para aplicarlas (desde https://fonts.googleapis.com
). Para permitir que las fuentes se carguen, agrega lo siguiente a tu CSP:
Similar a permitir imágenes desde html.sammy-codes.com
, también permitirás hojas de estilos desde fonts.googleapis.com
y fuentes desde fonts.gstatic.com
. Para contexto, la hoja de estilos cargada desde fonts.googleapis.com
se utiliza para aplicar las fuentes. Las fuentes en sí se cargan desde fonts.gstatic.com
.
Guarda y cierra el archivo.
Advertencia: Similar a self
, hay otras palabras clave como unsafe-inline
, unsafe-eval
o unsafe-hashes
que se pueden usar en un CSP. Se recomienda encarecidamente evitar el uso de estas reglas en tu CSP. Aunque facilitarán la implementación, pueden ser utilizadas para eludir el CSP y volverlo inútil.
Para obtener más información, consulta la documentación del producto de Mozilla sobre “Script en línea no seguro”.
Ahora, las fuentes de Google podrán cargar estilos y fuentes en tu sitio, y html.sammy-codes.com
podrá cargar imágenes. Sin embargo, al visitar una página en tu servidor, es posible que notes que solo se cargan imágenes ahora. Esto se debe a que los estilos en línea en el HTML que se utilizan para aplicar las fuentes no están permitidos. Resolverás eso en el próximo paso.
Paso 5 — Trabajar con Scripts y Estilos en Línea
En este punto, has modificado la CSP para permitir recursos externos. Pero los recursos en línea, como estilos y scripts en tu vista, todavía no están permitidos. En este paso, los harás funcionar para que puedas aplicar estilos de fuente.
Hay dos formas de permitir scripts y estilos en línea: nonces y hashes. Si descubres que modificas con frecuencia scripts y estilos en línea, usa nonces para evitar cambios frecuentes en tu CSP. Si rara vez actualizas scripts y estilos en línea, usar hashes es un enfoque razonable.
Usar nonce
para Permitir Scripts en Línea
Primero, utilizarás el enfoque de nonce. Un nonce es un token generado aleatoriamente que es único para cada solicitud. Si dos personas visitan tu sitio, cada una obtendrá un nonce
único que se incrusta en los scripts y estilos en línea que apruebes. Piensa en el nonce como una contraseña de un solo uso que aprueba ciertas partes de un sitio para ejecutarse durante una sesión única.
Para agregar soporte de nonce a tu proyecto, actualizarás tu CSP en settings.py
. Abre el archivo para editarlo:
Agrega script-src
en CSP_INCLUDE_NONCE_IN
en el archivo settings.py
.
Define CSP_INCLUDE_NONCE_IN
en cualquier parte del archivo y agrega 'script-src'
a él:
CSP_INCLUDE_NONCE_IN
indica a qué scripts en línea se te permite agregar atributos nonce
. CSP_INCLUDE_NONCE_IN
se maneja como un arreglo ya que múltiples fuentes de datos admiten nonces (por ejemplo, style-src
).
Guarda y cierra el archivo.
Ahora se permite generar nonces para scripts en línea cuando agregas el atributo nonce
a ellos en tu plantilla de vista. Para probar esto, usarás un fragmento de JavaScript simple.
Abre index.html
para editarlo:
Agrega el siguiente fragmento en la etiqueta <head>
del HTML:
Este fragmento imprime ¡Hola desde la consola!
en la consola del navegador. Sin embargo, como tu proyecto tiene una CSP que solo permite scripts en línea si tienen un nonce
, este script no se ejecutará y en su lugar producirá un error.
Puedes ver este error en la consola del navegador cuando actualices la página:
La imagen se carga porque permitiste recursos externos en el paso anterior. Como se esperaba, el estilo actualmente es el predeterminado porque aún no has permitido estilos en línea. También como se esperaba, el mensaje de consola no se imprimió y devolvió un error. Tendrás que darle un nonce
para aprobarlo.
Puedes hacerlo añadiendo nonce="{{request.csp_nonce}}"
a este script como atributo. Abre index.html
para editarlo y agrega la parte resaltada como se muestra aquí:
Guarda y cierra tu archivo cuando hayas terminado.
Si actualizas la página, el script se ejecutará ahora:
Cuando observes en Inspeccionar elemento, notarás que no hay ningún valor para el atributo:
El valor no aparece por razones de seguridad. El navegador ya ha procesado el valor. Está oculto para que ningún script con acceso al DOM pueda acceder a él y aplicarlo a otro script. Si en su lugar ves el código fuente de la página, esto es lo que recibió el navegador:
Observa que cada vez que actualizas la página, el valor de nonce
cambia. Esto se debe a que el middleware de CSP en nuestro proyecto genera un nuevo nonce
para cada solicitud.
Estos valores de nonce
se añaden a la cabecera CSP cuando el navegador recibe la respuesta:
Cada solicitud que el navegador hace a tu sitio tendrá un valor de nonce
único para ese script. Dado que el nonce
se proporciona en la cabecera CSP, eso significa que el servidor Django aprobó ese script específico para ejecutarse.
Has actualizado tu proyecto para trabajar con nonce, que se puede aplicar a múltiples recursos. Por ejemplo, también puedes aplicarlo a estilos, actualizando CSP_INCLUDE_NONCE_IN
para permitir style-src
. Pero hay un enfoque más simple para aprobar recursos en línea, y eso es lo que harás a continuación.
Usar Hashes para Permitir Estilos en Línea
Otro enfoque para permitir scripts y estilos en línea es con hashes. Un hash es un identificador único para un recurso en línea dado.
Como ejemplo, este es el estilo en línea en nuestra plantilla:
Actualmente, sin embargo, los estilos no están funcionando. Cuando visualizas el sitio en el navegador, las imágenes se cargan correctamente, pero las fuentes y los estilos no se aplican:
En la consola del navegador, encontrarás un error que indica que un estilo en línea viola la CSP. (Puede haber otros errores, pero busca el error sobre estilo en línea.)
El error se produce porque el estilo no está aprobado por nuestra CSP. Pero, nota que el error proporciona el hash necesario para aprobar el fragmento de estilo. Este hash es único para este fragmento de estilo específico. Ningún otro fragmento tendrá nunca el mismo hash. Cuando este hash se coloca dentro de la CSP, cada vez que se cargue este estilo específico, será aprobado. Pero, si alguna vez modificas estos estilos, necesitarás obtener el nuevo hash y reemplazar el antiguo con él en la CSP.
Ahora aplicarás el hash agregándolo a CSP_STYLE_SRC
en settings.py
, así:
Agregar el hash sha256-...
a la lista CSP_STYLE_SRC
permitirá al navegador cargar la hoja de estilos sin errores.
Guarda y cierra el archivo.
Ahora, recarga el sitio en el navegador y las fuentes y estilos deberían cargar correctamente:
Los estilos y scripts en línea ahora funcionan correctamente. En este paso, utilizaste dos enfoques diferentes, nonces y hashes, para permitir estilos y scripts en línea.
Pero, hay un problema importante que abordar. Las CSP son tediosas de mantener, especialmente para sitios web grandes. Es posible que necesites una forma de rastrear cuándo la CSP bloquea un recurso para poder determinar si es un recurso malicioso o simplemente una parte rota de tu sitio. En el próximo paso, usarás Sentry para registrar y hacer un seguimiento de todas las violaciones producidas por tu CSP.
Paso 6 — Reporte de Violaciones con Sentry (Opcional)
Dado lo estrictas que suelen ser las CSP, es bueno saber cuándo está bloqueando contenido, especialmente porque bloquear contenido probablemente signifique que alguna funcionalidad en tu sitio no funcionará. Herramientas como Sentry pueden informarte cuando la CSP está bloqueando solicitudes para los usuarios. En este paso, configurarás Sentry para registrar y reportar violaciones de CSP.
Como requisito previo, te has registrado para obtener una cuenta en Sentry. Ahora crearás un proyecto.
En la esquina superior izquierda del panel de control de Sentry, haz clic en la pestaña Proyectos:
En la esquina superior derecha, haz clic en el botón Crear Proyecto:
Verás una serie de logotipos con un título que dice Elige una plataforma. Elige Django:
Luego, en la parte inferior, nombra tu proyecto (para este ejemplo, usaremos sammys-tutorial
), y haz clic en el botón Crear Proyecto:
Sentry te proporcionará un fragmento de código para agregar a tu archivo settings.py
. Guarda este fragmento para agregarlo en un paso posterior.
En tu terminal, instala el SDK de Sentry:
Abre settings.py
de la siguiente manera:
Agrega lo siguiente al final del archivo, y asegúrate de reemplazar SENTRY_DSN
con el valor del panel de control:
Este código es proporcionado por Sentry para que pueda registrar cualquier error que ocurra en tu aplicación. Es la configuración predeterminada para Sentry e inicializa Sentry para registrar problemas en nuestro servidor. Técnicamente, no necesitas inicializar Sentry en tu servidor para violaciones de CSP, pero en el raro caso de que haya algún problema al renderizar nonces o hashes, estos errores se registrarán en Sentry.
Guarda y cierra el archivo.
A continuación, regrese al panel de control de su proyecto y haga clic en el ícono de engranaje para ingresar a Configuración:
Vaya a la pestaña Encabezados de seguridad:
Copie el report-uri
:
Agréguelo a su CSP de la siguiente manera:
Asegúrese de reemplazar your-report-uri
con el valor que copió del panel de control.
Guarde y cierre su archivo. Ahora, cuando la aplicación de la CSP cause una violación, Sentry la registrará en esta URI. Puede probar esto eliminando un dominio o hash de su CSP, o eliminando el nonce
del script que agregó anteriormente. Cargue la página en el navegador y verá el error en la página de Problemas de Sentry:
Si encuentra que está abrumado por la cantidad de registros, también puede definir
CSP_REPORT_PERCENTAGE
en settings.py
para enviar solo un porcentaje de los registros a Sentry.
Ahora, cada vez que haya una violación de la CSP, recibirá una notificación y podrá ver el error en Sentry.
Conclusión
En este artículo, aseguraste tu aplicación Django con una política de seguridad de contenido. Actualizaste tu política para permitir recursos externos y utilizaste nonces y hashes para permitir scripts y estilos en línea. También lo configuraste para enviar violaciones a Sentry. Como próximo paso, echa un vistazo a la documentación de CSP de Django para aprender más sobre cómo hacer cumplir tu CSP.