This is the easiest challenge in this CTF. There is only one file, "index.js" in this challenge.
app.get('/flag', needAuth, (req, res) => {
if (req.session.username !== 'admin') {
flash(req, 'only admin can read the flag');
return res.redirect('/');
}
return res.render('flag', { chall_name: CHALL_NAME, flash: getFlash(req), flag: FLAG });
});
When you enter "/flag" endpoint with admin session, you can get the flag.
const User = sequelize.define('User', {
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
profile: {
type: DataTypes.STRING
}
}, {});
await User.sync({ force: true });
await User.create({
username: 'admin',
password: crypto.randomUUID(),
profile: 'Hi, I am admin.'
});
Before starting, the service made admin data with encrypted password.
app.post('/login', async (req, res) => {
// make sure given username and password are valid
const { username, password } = req.body;
if (!username || !password) {
flash(req, 'username or password not provided');
return res.redirect('/login');
}
if (typeof username !== 'string' || typeof password !== 'string') {
flash(req, 'invalid username or password');
return res.redirect('/login');
}
// then check if there is a user with given username nad password
const user = await User.findOne({
where: { username, password }
});
if (user == null) {
flash(req, 'invalid username or password');
return res.redirect('/login');
}
// okay, it exists. store user information in session
req.session.loggedIn = true;
req.session.username = user.username;
return res.redirect('/');
});
In login function, after check value that we entered, service finds out data that we want to log in and after successing login, we can get session. A small thing we have to check is that we can't create new users with duplicate ID. That is, we can't make another user ID with "admin"
app.post('/user/:username/delete', needAuth, async (req, res) => {
const { username } = req.params;
const { username: loggedInUsername } = req.session;
if (loggedInUsername !== 'admin' && loggedInUsername !== username) {
flash(req, 'general user can only delete itself');
return res.redirect('/');
}
// find user to be deleted
const user = await User.findOne({
where: { username }
});
await User.destroy({
where: { ...user?.dataValues }
});
// user is deleted, so session should be logged out
req.session.destroy();
return res.redirect('/');
});
Endpoint of "/user/:username/delete" also has no vulnerabilites. It's too normal code!
It seems to have no vulnerability in this chall :( . To get the falg, we need a tricky idea.
[ Point ]
Login function has a big problem. For solving this chall, need a tricky idea.
// find user to be deleted
const user = await User.findOne({
where: { username }
});
await User.destroy({
where: { ...user?.dataValues }
});
// user is deleted, so session should be logged out
req.session.destroy();
In user dlete funciton, what if there is no data in "user?.data.Values" ?
Delete from `User`
Above query will be executed. As a result of this, all data wil be gone. So, now we can make a user which ID is "admin".
Then, we can enter "/flag" endpoint and get flag.
All we have to do is so simple. First step, We can access this challenge in two separate windows and log in to each. Instead, We'll need to access one in a regular window and one in an incognito tab. Second step, register a test id, log in with it in each tab and access to "/user/:username/delete" endpoint. Third step, delete user in each tab.
At here, first request of deleting user is execute in appropriate way. But in second request, DB can't find user data so delete every data in DB.
Fourth step, make a new admin ID. And then enjoy your flag :)
Flag
zer0pts{fire_ice_storm_di_acute_brain_damned_jugem_bayoen_bayoen_bayoen_10cefab0}