Mitiga las secuencias de comandos entre sitios (XSS) con una política de seguridad de contenido (CSP) estricta
Cómo implementar un CSP basado en nonces de scripts o hashes como defensa en profundidad contra el scripting entre sitios.
¿Por qué deberías implementar una política de seguridad de contenido (CSP) estricta? #
Cross-site scripting (XSS) o secuencias de comandos en sitios cruzados, definida como la capacidad de inyectar scripts maliciosos en una aplicación web, ha sido una de las mayores vulnerabilidades de seguridad web durante más de una década.
La Política de seguridad de contenido (CSP) es una capa adicional de seguridad que ayuda a mitigar XSS. La configuración de un CSP implica agregar el encabezado HTTP Content-Security-Policy a una página web y establecer valores para controlar qué recursos puede cargar el agente de usuario para esa página. Este artículo explica cómo usar un CSP basado en nonces o hashes para mitigar XSS en lugar de los CSP basados en listas de permisos de host de uso común que a menudo dejan la página expuesta a XSS, ya que se pueden omitir en la mayoría de las configuraciones.
Una política de seguridad de contenido basada en nonces o hashes a menudo se denomina CSP estricto . Cuando una aplicación usa un CSP estricto, los atacantes que encuentran fallas en la inyección de HTML generalmente no podrán usarlos para forzar al navegador a ejecutar scripts maliciosos en el contexto del documento vulnerable. Esto se debe a que el CSP estricto solo permite scripts hash o scripts con el valor de nonce correcto generado en el servidor, por lo que los atacantes no pueden ejecutar el script sin conocer el nonce correcto para una respuesta determinada.
Por qué se recomienda un CSP estricto en lugar de los CSP de lista de permitidos #
Si tu sitio ya tiene un CSP con este aspecto: script-src www.googleapis.com
, ¡es posible que no sea eficaz contra las secuencias de comandos entre sitios (cross-site scripting)! Este tipo de CSP se denomina CSP de lista de permitidos y tiene un par de desventajas:
- Requiere mucha personalización.
- Se puede omitir en la mayoría de las configuraciones .
Esto hace que los CSP de listas de permisos sean generalmente ineficaces para evitar que los atacantes exploten XSS. Es por eso que se recomienda utilizar un CSP estricto basado en nonces o hashes criptográficos, lo que evita las trampas descritas anteriormente.
Allowlist CSP
: no protege eficazmente su sitio. ❌ - Debe ser altamente personalizado. 😓Strict CSP Una política de seguridad de contenido estricta tiene la siguiente estructura y se habilita configurando uno de los siguientes encabezados de respuesta HTTP: Las siguientes propiedades hacen que un CSP como el anterior sea "estricto" y, por lo tanto, seguro: Utiliza nonces Establece No se basa en listas de URL permitidas y, por lo tanto, no sufre omisiones de CSP comunes. Bloquea scripts en línea que no son de confianza, como controladores de eventos en línea o Restringe Restringe la Para adoptar un CSP estricto, debes: Puedes utilizar la auditoría de prácticas recomendadas Lighthouse (v7.3.0 y superior con flag Hay dos tipos de CSP estrictos, basados en hash y basados en nonce. Así es como funcionan: Criterios para elegir un enfoque de CSP estricto: Al configurar un CSP, tienes algunas opciones: Establece el siguiente encabezado de respuesta HTTP Un nonce es un número aleatorio que se usa solo una vez por carga de página. Un CSP basado en nonce solo puede mitigar XSS si un atacante no puede adivinar el valor de nonce. Un nonce para CSP debe ser: A continuación, se muestran algunos ejemplos sobre cómo agregar un nonce CSP en marcos del lado del servidor: Con un CSP basado en nonce, cada Blocked by CSP Allowed by CSP Establece el siguiente encabezado de respuesta HTTP Para varios scripts en línea, la sintaxis es la siguiente: Todas las secuencias de comandos de origen externo deben cargarse dinámicamente a través de una secuencia de comandos en línea, ya que los hashes CSP sólo son compatibles con los navegadores para las secuencias de comandos en línea (los hashes para las secuencias de comandos de origen no son compatibles con los navegadores). Blocked by CSP Allowed by CSP En el fragmento de código anterior, se añade Los controladores de eventos en línea (como 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: Blocked by CSP Allowed by CSP Blocked by CSP Allowed by CSP Si tu aplicación usa Si no puede eliminar todos los usos de Puedes encontrar estos y más ejemplos de refactorización de este tipo en este estricto CSP Codelab: CSP es compatible con todos los navegadores principales, pero necesitará dos alternativas: El uso de Para garantizar la compatibilidad con versiones de navegador muy antiguas (más de 4 años), puedes agregar Después de confirmar que CSP no está bloqueando scripts legítimos en tu entorno de desarrollo local, puedes continuar con la implementación de tu CSP en su (puesta en escena, luego) entorno de producción: En términos generales, un CSP estricto proporciona una fuerte capa adicional de seguridad que ayuda a mitigar XSS. En la mayoría de los casos, CSP reduce significativamente la superficie de ataque (patrones peligrosos como 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 los casos descritos anteriormente en esta presentación de CSP.¿Qué es una política de seguridad de contenido estricta? #
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';'nonce-{RANDOM}'
o hashes 'sha256-{HASHED_INLINE_SCRIPT}'
para indicar qué <script>
son confiables para el desarrollador del sitio y deben poder ejecutarse en el navegador del usuario.'strict-dynamic'
para reducir el esfuerzo de implementar un CSP basado en hash o nonce al permitir automáticamente la ejecución de scripts creados por un script que ya es de confianza. Esto también desbloquea el uso de la mayoría de las bibliotecas y widgets de JavaScript de terceros.javascript:
URIs.object-src
para deshabilitar complementos peligrosos como Flash.base-uri
para bloquear la inyección de etiquetas <base>
Esto evita que los atacantes cambien la ubicación de los scripts cargados desde URL relativas.Adopción de un CSP estricto #
--preset=experimental
) a lo largo de este proceso para comprobar si tu sitio tiene un CSP y si es lo suficientemente estricto como para ser eficaz contra XSS.Paso 1: Decide si necesitas un CSP basado en hash o en nonce #
CSP basado en Nonce Para páginas HTML renderizadas en el servidor donde puedes crear un nuevo token aleatorio (nonce) para cada respuesta. CSP basado en hash Para páginas HTML servidas estáticamente o aquellas que necesitan ser almacenadas en caché. Por ejemplo, aplicaciones web de una sola página creadas con marcos como Angular, React u otros, que se sirven estáticamente sin renderizado del lado del servidor. Paso 2: Establece un CSP estricto y prepara tus scripts #
Content-Security-Policy-Report-Only
) o modo de aplicación (Content-Security-Policy
). En el modo de solo informe, el CSP no bloqueará los recursos todavía, nada se romperá, pero podrá ver errores y recibir informes de lo que se habría bloqueado. Localmente, cuando estás en el proceso de configurar un CSP, esto realmente no importa, porque ambos modos te mostrarán los errores en la consola del navegador. En todo caso, el modo de aplicación te facilitará aún más ver los recursos bloqueados y modificar tu CSP, ya que tu página se verá rota. El modo de solo informe se vuelve más útil más adelante en el proceso (consulte el Paso 5 ).<meta>
. Para el desarrollo local, una <meta>
puede ser más conveniente para ajustar tu CSP y ver rápidamente cómo afecta tu sitio. Sin embargo: Opción A: CSP basado en Nonce
Content-Security-Policy
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';Generar un nonce para CSP #
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' https:; 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 });
});
}Agregar un
nonce
a los elementos <script>
#<script>
debe tener un nonce
que coincida con el valor nonce aleatorio especificado en el encabezado del CSP (todos los scripts pueden tener el mismo nonce). El primer paso es agregar estos atributos a todos los scripts:<script src="/path/to/script.js"></script>
<script>foo()</script><script nonce="${NONCE}" src="/path/to/script.js"></script>
<script nonce="${NONCE}">foo()</script>Opción B: Encabezado de respuesta de CSP basado en hash
Content-Security-Policy
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'
.Cargar scripts de origen dinámicamente #
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script><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>Acerca de
async = false
y la carga de scripts async = false
no bloquea en este caso, pero utilízalo con cuidado.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 los scripts; eso es porque los scripts se agregan dinámicamente. El analizador solo se detendrá mientras se ejecutan los scripts, tal como se comportaría con los scripts async
. Sin embargo, con este fragmento, ten en cuenta:DOMContentLoaded
antes de agregar los scripts. Si esto causa un problema de rendimiento (porque los scripts no comienzan a descargarse lo suficientemente temprano), puedes usar etiquetas de precarga anteriormente en la página.defer = true
no hará nada. Si necesitas ese comportamiento, tendrás que ejecutar manualmente el script en el momento en que desees ejecutarlo. Paso 3: Refactoriza las plantillas HTML y el código del lado del cliente para eliminar patrones incompatibles con CSP #
onclick="…"
, onerror="…"
) y los URI de JavaScript ( <a href="javascript:…">
) se pueden utilizar para ejecutar scripts. Esto significa que un atacante que encuentre un error XSS podría inyectar este tipo de HTML y ejecutar JavaScript malicioso. Un CSP basado en hash o nonce no permite el uso de dicho marcado. Si tu sitio utiliza alguno de los patrones descritos anteriormente, deberás refactorizarlos en alternativas más seguras.
Para refactorizar los controladores de eventos en línea, vuelve a escribirlos para agregarlos desde un bloque de JavaScript #
<span onclick="doThings();">A thing.</span>
<span id="things">A thing.</span>
<script nonce="${nonce}">
document.getElementById('things')
.addEventListener('click', doThings);
</script>Para
javascript:
URIs, puedes usar un patrón similar #<a href="javascript:linkClicked()">foo</a>
<a id="foo">foo</a>
<script nonce="${nonce}">
document.getElementById('foo')
.addEventListener('click', linkClicked);
</script>Uso de
eval()
en JavaScript #eval()
para convertir las serializaciones de cadenas JSON en objetos JS, debes refactorizar dichas instancias a JSON.parse()
, que también es más rápido.eval()
, aún puedes establecer un CSP estricto basado en nonce, pero tendrás que usar 'unsafe-eval'
que hará que tu política sea un poco menos segura.Paso 4: Agrega alternativas para admitir Safari y navegadores más antiguos #
'strict-dynamic'
requiere agregar https:
como respaldo para Safari, el único navegador importante sin soporte para 'strict-dynamic'
. Al hacerlo:'strict-dynamic'
ignorarán https:
fallback, por lo que esto no reducirá la solidez de la política.javascript:
URIs porque 'unsafe-inline'
no está presente o se ignora en presencia de un hash o un nonce.'unsafe-inline'
como respaldo. Todos los navegadores recientes ignorarán 'unsafe-inline'
si hay un código de acceso 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 #
Content-Security-Policy-Report-Only
. Puedes obtener más información sobre la API de informes. El modo de solo informe es útil para probar un cambio potencialmente importante como un nuevo CSP en producción, antes de aplicar las restricciones de CSP. En el modo de solo informe, tu CSP no afecta el comportamiento de tu aplicación (nada realmente se romperá). Pero el navegador seguirá generando errores de consola e informes de infracción cuando se encuentren patrones incompatibles con CSP (para que pueda ver lo que habría fallado para sus usuarios finales).Content-Security-Policy
Solo una vez que hayas completado este paso, CSP comenzará a proteger tu aplicación de XSS. Configurar tu CSP a través de un encabezado HTTP del lado del servidor es más seguro que configurarlo como una etiqueta <meta>
; usa un encabezado si puedes.Limitaciones #
javascript:
URIs están completamente desactivados). Sin embargo, según el tipo de CSP que estés utilizando (nonces, hashes, con o sin 'strict-dynamic'
), hay casos en los que CSP no protege:src
de ese elemento <script>
.document.createElement('script')
), incluso en cualquier función de biblioteca que cree script
basados en el valor de sus argumentos. Esto incluye algunas API comunes como .html()
jQuery, así como .get()
y .post()
en jQuery <3.0.'unsafe-eval'
, inyecciones en eval()
, setTimeout()
y algunas otras API de uso poco frecuente.Otras lecturas #