Hacking/Web.

ch4n3-world의 중복 인증 이슈에 관하여

2020. 2. 28. 21:10

꽃새우에 캐비어가 올라간 초밥, 캐비어의 짭짤한 맛과 꽃새우의 쫀쫀함이 잘 어울렸다. @스시스미레

 내가 운영하는 워게임에 가끔 중복 인증에 관한 문의가 꾸준히 들어왔었는데 어이없게도 오늘에서야 그 이유를 알게 되어 블로그에 글을 포스팅하게 되었다. 

 

router.post('/auth', async (req, res) => {
    let getChallengeByFlag = async (flag) => {
        let sqlData = await ChallengeAPI.getChallengeByFlag(flag);
        if (sqlData)
            return sqlData.dataValues;
        return null;
    };

    let isSolvedChallenge = async (user_no, challenge_no) => {
        let sqlData = await SolversAPI.isSolvedChallenge(challenge_no, user_no);
        if (sqlData)
            return sqlData.dataValues;
        return null;
    };

    let insertAuthLog = async (challenge_no, user_no, user_flag, state) => {
        return await LogAPI.insertAuthLog(challenge_no, user_no, user_flag, state);
    };

    let insertIntoSolvers = async (challenge_no, user_no) => {
        return SolversAPI.addSolver(challenge_no, user_no);
    };


    let main = async () => {

        if (!req.session.email)
            return res.json({status: 'not signed in'});

        let user_flag = req.body.flag;
        let user_no = req.session.user_no;
        let challenge = await getChallengeByFlag(user_flag);
        let result = {};

        if (challenge) {
            let challenge_no = challenge.no;
            result.challenge = challenge;
            
            if (await isSolvedChallenge(user_no, challenge.no)) {
                result.status = 'already solved';
                await insertAuthLog(challenge_no, user_no, user_flag, "ALREADY SOLVED");
            }

            else {
                result.status = 'solved';
                await insertIntoSolvers(challenge_no, user_no);
                await insertAuthLog(challenge_no, user_no, user_flag, "CORRECT");
            }
        }

        else {
            result.status = 'invalid_flag';
            await insertAuthLog(0, user_no, user_flag, "WRONG");
        }

        return res.json(result);
    };

    await main();
});

 

 위의 소스코드는 /auth 에서 POST로 flag 인증을 검사하는 코드이다. 다음 코드의 실행 과정을 정리하면 다음과 같다.

 

1. 사용자가 HTTP POST로 flag 를 보낸다.

2. getChallengeByFlag() 함수를 이용해서 해당 flag에 맞는 문제를 불러온다.

3. 해당 flag를 갖는 문제가 있다면 이미 푼 문제인지 확인하고 Solvers 테이블에 사용자 데이터를 넣는다.

3-1. 문제를 불러오지 못했다면 끝낸다.

4. flag 인증한 내역을 DB에 로깅한다.

 

 중복 인증이 된다는 것은 위의 소스코드에서 isSolvedChallenge() 함수가 예상과는 다르게 동작한다는 것을 의미한다. 그래서 디버깅을 하려고 다음과 같은 코드를 추가해보았다.

 

console.log("=================================");
console.log(await isSolvedChallenge(user_no, challenge.no));
console.log("=================================");

 

 그랬더니.. ?

 

띠용?

이럴리가 없는데? 하면서 SolversAPI.isSolvedChallenge가 정의된 코드를 봤는데..

 

async isSolvedChallenge(challenge_no, user_no) { 
	return await API.Solvers.count({where: {user_no: user_no, challenge_no: challenge_no}});
}
let isSolvedChallenge = async (challenge_no, user_no) => {
	let sqlData = await SolversAPI.isSolvedChallenge(challenge_no, user_no);
	if (sqlData)
		return sqlData.dataValues;
	return null;
};

 

아,,, count() 메소드를 사용해서 dataValues를 리턴할 필요가 없었던 것이었다. 하... 그런데 왜 될 때도 있고 안될 때도 있었던 걸까? 자바스크립트 원리 상으로 될리가 없는데? 그건 생각해보기 귀찮아서 나중에 생각해보는 걸로 했다. 앞으로 이런 실수는 하지 말자. ...

 

끝.