0

我正在尝试击败这个 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=''语句,但我仍然需要找到一种方法来使第二个\'.

4

0 回答 0