La secuencia de comandos entre sitios (XSS), la capacidad de insertar secuencias de comandos maliciosas en una app web, ha sido una de las vulnerabilidades de seguridad web más grandes durante más de una década.
La Política de Seguridad del Contenido (CSP) es una capa adicional de seguridad que ayuda a mitigar los XSS. Para configurar una CSP, agrega el encabezado HTTP Content-Security-Policy
a una página web y establece valores que controlen qué recursos puede cargar el usuario-agente para esa página.
En esta página, se explica cómo usar un CSP basado en nonces o hashes para mitigar los XSS, en lugar de los CSP basados en listas de entidades permitidas de host que se usan comúnmente y que, a menudo, dejan la página expuesta a XSS porque se pueden evitar en la mayoría de las configuraciones.
Término clave: Un nonce es un número aleatorio que se usa solo una vez y que puedes usar para marcar una etiqueta <script>
como de confianza.
Término clave: Una función hash es una función matemática que convierte un valor de entrada en un valor numérico comprimido llamado hash. Puedes usar un hash (por ejemplo, SHA-256) para marcar una etiqueta <script>
intercalada como confiable.
A menudo, una Política de Seguridad del Contenido basada en nonces o hashes se denomina CSP estricta. Cuando una aplicación usa un CSP estricto, los atacantes que encuentran fallas de inserción de HTML, por lo general, no pueden usarlas para forzar al navegador a ejecutar secuencias de comandos maliciosas en un documento vulnerable. Esto se debe a que el CSP estricto solo permite secuencias de comandos con hash o secuencias de comandos con el valor nonce correcto generado en el servidor, de modo que los atacantes no pueden ejecutar la secuencia de comandos sin conocer el nonce correcto para una respuesta determinada.
¿Por qué deberías usar un CSP estricto?
Si tu sitio ya tiene un CSP que se parece a script-src www.googleapis.com
, es probable que no sea eficaz contra los ataques entre sitios. Este tipo de CSP se denomina CSP de lista de entidades permitidas. Requieren mucha personalización y los atacantes pueden evitarlas.
Los CSP estrictos basados en nonces o hashes criptográficos evitan estos problemas.
Estructura estricta de CSP
Una política de seguridad del contenido estricta básica usa uno de los siguientes encabezados de respuesta HTTP:
CSP estricta basada en nonce
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
CSP estricta basada en hash
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
Las siguientes propiedades hacen que un CSP como este sea "estricto" y, por lo tanto, seguro:
- Usa nonces
'nonce-{RANDOM}'
o hashes'sha256-{HASHED_INLINE_SCRIPT}'
para indicar qué etiquetas<script>
confía el desarrollador del sitio para ejecutar en el navegador del usuario. - Establece
'strict-dynamic'
para reducir el esfuerzo de implementar un CSP basado en nonce o hash, ya que permite automáticamente la ejecución de secuencias de comandos que crea una secuencia de comandos de confianza. Esto también desbloquea el uso de la mayoría de las bibliotecas y widgets de JavaScript de terceros. - No se basa en listas de entidades permitidas de URLs, por lo que no sufre de evasiones de CSP comunes.
- Bloquea las secuencias de comandos intercaladas que no son de confianza, como los controladores de eventos intercalados o los URIs
javascript:
. - Restringe
object-src
para inhabilitar complementos peligrosos, como Flash. - Restringe
base-uri
para bloquear la inserción de etiquetas<base>
. Esto evita que los atacantes cambien las ubicaciones de las secuencias de comandos cargadas desde URLs relativas.
Adopta una CSP estricta
Para adoptar un CSP estricto, debes hacer lo siguiente:
- Decide si tu aplicación debe establecer una CSP basada en nonce o hash.
- Copia el CSP de la sección Estructura de CSP estricta y configúralo como un encabezado de respuesta en tu aplicación.
- Refactoriza las plantillas de HTML y el código del cliente para quitar los patrones que sean incompatibles con la CSP.
- Implementa tu CSP.
Puedes usar la auditoría de prácticas recomendadas de Lighthouse (versión 7.3.0 y versiones posteriores con la marca --preset=experimental
) durante este proceso para verificar si tu sitio tiene un CSP y si es lo suficientemente estricto como para ser eficaz contra los XSS.
Paso 1: Decide si necesitas una CSP basada en nonce o hash
A continuación, se muestra cómo funcionan los dos tipos de CSP estrictos:
CSP basada en nonce
Con un CSP basado en nonce, generas un número aleatorio durante el tiempo de ejecución, lo incluyes en tu CSP y lo asocias con cada etiqueta de secuencia de comandos de tu página. Un atacante no puede incluir ni ejecutar una secuencia de comandos maliciosa en tu página, ya que tendría que adivinar el número aleatorio correcto para esa secuencia de comandos. Esto solo funciona si el número no se puede adivinar y se genera de forma nueva en el tiempo de ejecución para cada respuesta.
Usa un CSP basado en nonce para las páginas HTML renderizadas en el servidor. Para estas páginas, puedes crear un número aleatorio nuevo para cada respuesta.
CSP basada en hash
En el caso de un CSP basado en un hash, el hash de cada etiqueta de secuencia de comandos intercalada se agrega al CSP. Cada secuencia de comandos tiene un hash diferente. Un atacante no puede incluir ni ejecutar una secuencia de comandos maliciosa en tu página, ya que el hash de esa secuencia de comandos debería estar en tu CSP para que se ejecute.
Usa un CSP basado en hash para las páginas HTML que se entreguen de forma estática o las páginas que deban almacenarse en caché. Por ejemplo, puedes usar un CSP basado en hash para aplicaciones web de una sola página compiladas con frameworks como Angular, React o otros, que se entregan de forma estática sin renderización del servidor.
Paso 2: Establece un CSP estricto y prepara tus secuencias de comandos
Cuando configuras un CSP, tienes algunas opciones:
- Modo informativo (
Content-Security-Policy-Report-Only
) o modo de aplicación forzosa (Content-Security-Policy
). En el modo informativo, el CSP aún no bloquea los recursos, por lo que no se produce ningún error en tu sitio, pero puedes ver errores y obtener informes de todo lo que se habría bloqueado. De forma local, cuando configuras tu CSP, esto no importa, ya que ambos modos te muestran los errores en la consola del navegador. En cualquier caso, el modo de aplicación forzosa puede ayudarte a encontrar los recursos que bloquea tu CSP de borrador, ya que bloquear un recurso puede hacer que tu página se vea dañada. El modo solo informes se vuelve más útil más adelante en el proceso (consulta el Paso 5). - Encabezado o etiqueta
<meta>
HTML Para el desarrollo local, una etiqueta<meta>
puede ser más conveniente para ajustar tu CSP y ver rápidamente cómo afecta a tu sitio. Sin embargo, ten en cuenta lo siguiente:- Más adelante, cuando implementes tu CSP en producción, te recomendamos configurarlo como un encabezado HTTP.
- Si deseas configurar tu CSP en modo de solo informes, deberás configurarlo como un encabezado, ya que las metaetiquetas de CSP no admiten el modo de solo informes.
Establece el siguiente encabezado de respuesta HTTP Content-Security-Policy
en tu aplicación:
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
Genera un nonce para CSP
Un nonce es un número aleatorio que se usa solo una vez por cada carga de página. Un CSP basado en un nonce solo puede mitigar XSS si los atacantes no pueden adivinar el valor del nonce. Un nonce de CSP debe cumplir con los siguientes requisitos:
- Un valor aleatorio con seguridad criptográfica (idealmente, de más de 128 bits de longitud)
- Se genera una nueva para cada respuesta.
- Codificación en base64
Estos son algunos ejemplos de cómo agregar un nonce de CSP en frameworks del servidor:
- Django (python)
- Express (JavaScript):
const app = express();
app.get('/', function(request, response) {
// Generate a new random nonce value for every response.
const nonce = crypto.randomBytes(16).toString("base64");
// Set the strict nonce-based CSP response header
const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`;
response.set("Content-Security-Policy", csp);
// Every <script> tag in your application should set the `nonce` attribute to this value.
response.render(template, { nonce: nonce });
});
Agrega un atributo nonce
a los elementos <script>
Con un CSP basado en nonce, cada elemento <script>
debe tener un atributo nonce
que coincida con el valor de nonce aleatorio especificado en el encabezado del CSP. Todas las secuencias de comandos pueden tener el mismo nonce. El primer paso es agregar estos atributos a todas las secuencias de comandos para que el CSP los permita.
Establece el siguiente encabezado de respuesta HTTP Content-Security-Policy
en tu aplicación:
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
Para varias secuencias de comandos intercaladas, la sintaxis es la siguiente:
'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'
.
Carga secuencias de comandos de origen de forma dinámica
Puedes cargar secuencias de comandos de terceros de forma dinámica con una secuencia de comandos intercalada.
<script>
var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js'];
scripts.forEach(function(scriptUrl) {
var s = document.createElement('script');
s.src = scriptUrl;
s.async = false; // to preserve execution order
document.head.appendChild(s);
});
</script>
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
Consideraciones sobre la carga de secuencias de comandos
El ejemplo de secuencia de comandos intercalada agrega s.async = false
para garantizar que foo
se ejecute antes que bar
, incluso si bar
se carga primero. En este fragmento, s.async = false
no bloquea el analizador mientras se cargan las secuencias de comandos, ya que estas se agregan de forma dinámica. El analizador se detiene solo mientras se ejecutan las secuencias de comandos, como lo haría para las secuencias de comandos async
. Sin embargo, con este fragmento, ten en cuenta lo siguiente:
-
Es posible que una o ambas secuencias de comandos se ejecuten antes de que se termine de descargar el
documento. Si deseas que el documento esté listo cuando se ejecuten las secuencias de comandos, espera el evento
DOMContentLoaded
antes de adjuntarlas. Si esto causa un problema de rendimiento porque las secuencias de comandos no comienzan a descargarse con suficiente anticipación, usa etiquetas de carga previa antes en la página. -
defer = true
no hace nada. Si necesitas ese comportamiento, ejecuta la secuencia de comandos de forma manual cuando sea necesario.
Paso 3: Refactoriza las plantillas HTML y el código del cliente
Los controladores de eventos intercalados (como onclick="…"
, onerror="…"
) y los URIs de JavaScript (<a href="javascript:…">
) se pueden usar para ejecutar secuencias de comandos. Esto significa que un atacante que encuentre un error de XSS puede insertar este tipo de HTML y ejecutar JavaScript malicioso. Un CSP basado en un nonce o un hash prohíbe el uso de este tipo de marcado.
Si tu sitio usa alguno de estos patrones, deberás refactorizarlos en alternativas más seguras.
Si habilitaste CSP en el paso anterior, podrás ver las infracciones de CSP en la consola cada vez que CSP bloquee un patrón incompatible.
En la mayoría de los casos, la solución es sencilla:
Cómo refactorizar controladores de eventos intercalados
<span id="things">A thing.</span>
<script nonce="${nonce}">
document.getElementById('things').addEventListener('click', doThings);
</script>
<span onclick="doThings();">A thing.</span>
Refactoriza los URIs de javascript:
<a id="foo">foo</a>
<script nonce="${nonce}">
document.getElementById('foo').addEventListener('click', linkClicked);
</script>
<a href="javascript:linkClicked()">foo</a>
Quita eval()
de tu código JavaScript
Si tu aplicación usa eval()
para convertir serializaciones de cadenas JSON en objetos JS, debes refactorizar esas instancias a JSON.parse()
, que también es más rápido.
Si no puedes quitar todos los usos de eval()
, puedes configurar una CSP estricta basada en nonce, pero debes usar la palabra clave de CSP 'unsafe-eval'
, lo que hace que tu política sea un poco menos segura.
Puedes encontrar estos y más ejemplos de esa refactorización en este codelab de CSP estricto:
Paso 4 (opcional): Agrega resguardos para admitir versiones anteriores de navegadores
Si necesitas admitir versiones anteriores de navegadores, haz lo siguiente:
- El uso de
strict-dynamic
requiere agregarhttps:
como resguardo para versiones anteriores de Safari. Cuando lo hagas, ocurrirá lo siguiente:- Todos los navegadores que admiten
strict-dynamic
ignoran el resguardo dehttps:
, por lo que esto no reducirá la solidez de la política. - En navegadores antiguos, las secuencias de comandos de origen externo solo se pueden cargar si provienen de un origen HTTPS. Esto es menos seguro que una CSP estricta, pero aún evita algunas causas comunes de XSS, como las inyecciones de URIs
javascript:
.
- Todos los navegadores que admiten
- Para garantizar la compatibilidad con versiones de navegador muy antiguas (más de 4 años), puedes agregar
unsafe-inline
como resguardo. Todos los navegadores recientes ignoranunsafe-inline
si hay un nonce o un hash de CSP.
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
Paso 5: Implementa tu CSP
Después de confirmar que tu CSP no bloquea ninguna secuencia de comandos legítima en tu entorno de desarrollo local, puedes implementarlo en la etapa de pruebas y, luego, en tu entorno de producción:
- (Opcional) Implementa tu CSP en modo solo de informes con el encabezado
Content-Security-Policy-Report-Only
. El modo solo de informes es útil para probar un cambio potencialmente perjudicial, como un nuevo CSP en producción, antes de comenzar a aplicar las restricciones del CSP. En el modo solo de informes, tu CSP no afecta el comportamiento de tu app, pero el navegador aún genera errores de consola y informes de incumplimiento cuando encuentra patrones incompatibles con tu CSP, de modo que puedas ver qué se habría dañado para tus usuarios finales. Para obtener más información, consulta la API de Reporting. - Cuando tengas la seguridad de que tu CSP no dañará tu sitio para los usuarios finales,
implementa tu CSP con el encabezado de respuesta
Content-Security-Policy
. Te recomendamos que configures tu CSP con un encabezado HTTP del servidor porque es más seguro que una etiqueta<meta>
. Después de completar este paso, tu CSP comienza a proteger tu app de XSS.
Limitaciones
Por lo general, un CSP estricto proporciona una capa adicional de seguridad sólida que ayuda a mitigar los XSS. En la mayoría de los casos, la CSP reduce significativamente la superficie de ataque, ya que rechaza patrones peligrosos, como los URIs javascript:
. Sin embargo, según el tipo de CSP que uses (nonces, hashes, con o sin 'strict-dynamic'
), hay casos en los que el CSP tampoco protege tu app:
- Si generas un nonce para una secuencia de comandos, pero hay una inserción directamente en el cuerpo o en el parámetro
src
de ese elemento<script>
. - Si hay inyecciones en las ubicaciones de secuencias de comandos creadas de forma dinámica (
document.createElement('script')
), incluidas las funciones de bibliotecas que crean nodos DOMscript
según los valores de sus argumentos. Esto incluye algunas APIs comunes, como.html()
de jQuery, así como.get()
y.post()
en jQuery < 3.0. - Si hay inserciones de plantillas en aplicaciones de AngularJS anteriores. Un atacante que puede inyectar en una plantilla de AngularJS puede usarla para ejecutar JavaScript arbitrario.
- Si la política contiene
'unsafe-eval'
, inyecciones eneval()
,setTimeout()
y algunas otras APIs que rara vez se usan.
Los desarrolladores y los ingenieros de seguridad deben prestar especial atención a estos patrones durante las revisiones de código y las auditorías de seguridad. Puedes encontrar más detalles sobre estos casos en Content Security Policy: A Successful Mess Between Hardening and Mitigation.
Lecturas adicionales
- CSP Is Dead, Long Live CSP! Sobre la inseguridad de las listas de entidades permitidas y el futuro de la Política de Seguridad del Contenido
- Evaluador de CSP
- Conferencia de LocoMoco: Política de seguridad del contenido: un desastre exitoso entre el endurecimiento y la mitigación
- Charla de Google I/O: Cómo proteger las apps web con funciones de plataformas modernas