MongoDB Injection으로 알아보는 NoSQL Injection

⚠️ NoSQL Injection에 대한 전반적인 내용은 Cullinan > NoSQL Injection 페이지에서 관리하고 있습니다. 해당 페이지에서 최신 데이터가 유지되니 참고 부탁드려요 :D

웹 취약점 분석에서 나름 많은 부분을 차지하는 Injection. 그 중 DB 관련한 NoSQL Injection에 대한 이야기를 할까 합니다. 기존 SQL Injection과 비슷하나, 나름 다른점도 있고 RDBMS 이외 NoSQL DB를 사용하는 서비스도 점점 많아져 알아두고 있어야 할 부분입니다.

NoSQL이란?

NoSQL은 RDBMS와 다르게 Not only SQL, Non-Relational Operational Database로 불리며 말 그대로 SQL로 이루어지지 않은 DB를 의미합니다. Data Model이 단순하기 때문에 가용성과 확장성이 좋습니다. 대표적으로 MongoDB가 있습니다.

MongoDB Injection

모든 인젝션 기법이 그렇듯이 문제는 사용자 입력값에 대한 검증 부족에서 발생합니다. Web Request를 통해 넘어온 값에 Special Character에 대해 필터링 없이 DB에서 처리되어 문법의 흐름을 바꿔 공격자가 원하는 이득을 취하는것이 기본 구조입니다.

NoSQL Injection도 동일한 형태입니다. 적용되는 대상만 다를 뿐입니다. 우리는 특수문자 등을 이용해서 mongodb의 쿼리가 다른 액션을 취하도록 유도만 하면 됩니다.

db.myCollection.find( { $where: function() { return obj.credits - obj.debits < USER_INPUT; } } ); 

위와 같이 db 하단의 myCollection의 find 메소드를 통해 데이터를 찾는 구조를 가지고 있는 NoSQL 구문이 있습니다. 여기서 우리가 입력한 부분은 USER_INPUT 입니다.

;을 통해 line의 끝을 쓰고, 새로 구문을 작성하여 로직의 흐름을 바꿔놓을 수 있습니다. 아래 예제에서는 date에 Date 클래스로 할당받아 while을 돌며 계속 빙빙 도는 코드를 넘겨주게 됩니다.

Attack

USER_INPUT;var date=new Date(); do{curDate = new Date();}while(curDate-date<10000)

실제로 이 부분이 필터링 없이 넘어가게 되면 아래와 같이 우회된 구문이 완성되겠지요.

Query

function() { return obj.credits - obj.debits < USER_INPUT;var date=new Date(); do{curDate = new Date();}while(curDate-date<10000); }

이런 형태로 쉽게 흐름의 변환이 가능합니다. 물론 db 데이터를 보고있는 상태에서 바로 확인이 가능하지만 blackbox test에서도 output이나 error 등을 보고 판단하여 차근차근 구문을 만들어 갈 수도 있습니다. 또 테스트를 해볼까요?

db.noon.findMember()

이 쿼리는 Member를 찾는 기능을 한다고 가정하고 아래와 같이 인자값을 줄 수 있습니다.

db.noon.findMember({ $or : [ { name : [INPUT1] } , { level : ($lte[INPUT2]) } ] } )

여기서 공격자는 테스트를 통해 대략적인 구문 형태를 추측합니다. 우리가 공격구간으로 볼 수 있는 부분은 [INPUT1], [INPUT2] 정도가 있을 것 같습니다. [INPUT1] 에 공격을 수행한다고 하면 아래와 같이 흐름을 바꿀 수 있는 구문을 만들어야겠지요.

Input

{$ne:HaHwul}

위와 같이 입력하면 $ne 연산자로 인해 name과 문자열 HaHwul이 같지 않으면 찾게되어 다수 데이터가 나타나게 됩니다.

Query

db.noon.findMember({ $or : [ { name : {$ne:"HaHwul"}} , { level : ($lte[INPUT2]) } ] } )

사실 따지고 보면 SQL Injection이랑 별로 다를게 없습니다. 다만 공격이 수행되는 장소만 다를뿐이지요. 대체로 테스트에선 $ne 연산자 값이 상황을 뒤집을 수 있는 연산자가 많이 사용되니 로직을 파악하는 방법과 연산자에 집중하면 좋습니다.

어떻게 막아야 할 것인가?

상세한 대응방법은 플랫폼, 코드별로 조금씩 다를 수 있곘지만 대부분의 웹 공격의 대안처럼 사용자 입력값에 대한 철저한 필터링입니다.

db.noon.findMember({ $or : [ { name : [INPUT1] } , { level : ($lte[INPUT2]) } ] } )

[INPUT1] , [INPUT2] 부분 모두 입력될 때 로직 자체를 우회 시킬 수 있는 특수문자 $ { } [ ] ( ) 등에 대해서 필터링 처리해야합니다. 꼭 필요하다면 db가 처리하지 않도록 검증해야 합니다.

알아두면 좋은 MongoDB 연산자

  • $ne : 같지않음
  • $not : 복수의 데이터간 여집합을 반환
  • $exists : 특정키를 가지고 있는지 질의
  • $lt : <
  • $gt : >
  • $lte : <=
  • $gte : >=

Reference

https://www.owasp.org/index.php/Testing_for_NoSQL_injection http://aroundck.tistory.com/949