我正在尝试击败这个 CTF:一个正在建设中的网站,它有一个简单的登录页面,您可以在其中登录或注册新用户。
它使用node express
和一个SQLite
数据库。
分析源代码我发现了这个查询:
getUser(username){
return new Promise((res, rej) => {
db.get(`SELECT * FROM users WHERE username = '${username}'`, (err, data) => {
if (err) return rej(err);
res(data);
});
});
},
在中间件检查会话 cookie 后调用此函数,jsonwebtoken
以在您 GET 时检索用户名'/'
。
这就是它的名称:
router.get('/', AuthMiddleware, async (req, res, next) => {
try{
let user = await DBHelper.getUser(req.data.username);
if (user === undefined) {
return res.send(`user ${req.data.username} doesn't exist in our database.`);
}
return res.render('index.html', { user });
}catch (err){
return next(err);
}
});
授权中间件:
module.exports = async (req, res, next) => {
try{
if (req.cookies.session === undefined) return res.redirect('/auth');
let data = await JWTHelper.decode(req.cookies.session);
req.data = {
username: data.username
}
next();
} catch(e) {
console.log(e);
return res.status(500).send('Internal server error');
}
}
由于这似乎是唯一以这种方式格式化的查询(其他人都使用?
)并且通常是唯一明显的漏洞,我怀疑该标志存储在数据库中的某个位置。
由于调用该函数的唯一方法是进行活动会话,因此我使用“恶意”sql 代码注册了一个用户作为用户名。起初我试图关闭报价并附加一个OR WHERE
以获取所有用户:
SELECT * FROM users WHERE username = '${username}'
+
bob' OR WHERE username IS NOT NULL
=
SELECT * FROM users WHERE username = 'bob' OR WHERE username IS NOT NULL
这至少应该抛出一个错误,因为它WHERE username = 'bob' OR WHERE username IS NOT NULL
应该返回一个包含数据库中所有用户的集合,同时它在网页上呈现为
欢迎 {{ user.username }}
这个网站正在开发中。
请待会再过来。
我期待至少“数组上没有用户名属性”或类似的东西。相反,它总是返回我给他的完整用户名
欢迎 bob' OR WHERE username IS NOT NULL
此站点正在开发中。
请待会再过来。
我错过了什么吗?有没有办法逃避在 cookie 读取过程中可能添加的最终引号?
编辑:
这是您尝试登录 /auth 路由时调用的函数:
router.post('/auth', async (req, res) => {
const { username, password } = req.body;
if((username !== undefined && username.trim().length === 0)
|| (password !== undefined && password.trim().length === 0)){
return res.redirect('/auth');
}
if(req.body.register !== undefined){
let canRegister = await DBHelper.checkUser(username);
if(!canRegister){
return res.redirect('/auth?error=Username already exists');
}
DBHelper.createUser(username, password);
return res.redirect('/auth?error=Registered successfully&type=success');
}
// login user
let canLogin = await DBHelper.attemptLogin(username, password);
if(!canLogin){
return res.redirect('/auth?error=Invalid username or password');
}
let token = await JWTHelper.sign({ // Maybe something can be done with this function?
username: username.replace(/'/g, "\'\'").replace(/"/g, "\"\"")
})
res.cookie('session', token, { maxAge: 900000 });
return res.redirect('/');
});
尝试登录():
attemptLogin(username, password){
return new Promise((res, rej) => {
db.get(`SELECT * FROM users WHERE username = ? AND password = ?`, username, password, (err, data) => {
if (err) return rej();
res(data !== undefined);
});
});
}
编辑 2.0:
我刚刚注意到它存储会话 cookie 的部分:
let token = await JWTHelper.sign({
username: username.replace(/'/g, "\'\'").replace(/"/g, "\"\"")
})
它显然'
用\'\'
. 我可以通过转义引号使其变为\\'\'
. 这允许我关闭username=''
语句,但我仍然需要找到一种方法来使第二个\'
.