CouchDB
CouchDB는 키와 값이 하나의 쌍을 이루는 데이터를 저장하며 JSON 객체 형태인 도큐먼트를 저장한다.
HTTP 기반의 서버로 동작하며 REST API 형식으로 HTTP 메소드를 사용해 요청을 받고, 처리한다.
(CouchDB는 HTTP 클라이언트를 사용해 접근할 수 있다)
일반적으로 _ 문자로 시작하는 URL 요소 및 JSON 필드는 특수 구성 요소를 나타낸다.
구성 요소 | 설명 |
/ | 인스턴스에 대한 메타 정보 반환 |
/_all_dbs | 인스턴스의 데이터베이스 목록 반환 |
/utils | 관리자 페이지(Fauxton Administration Interface)로 이동 |
/db | 지정한 데이터베이스에 대한 정보 반환 |
/{db}/_all_docs | 지정한 데이터베이스에 포함된 모든 도큐먼트 반환 |
/{db}/_find | 지정한 데이터베이스에서 JSON 쿼리에 해당하는 모든 도큐먼트 반환 |
(아래는 공식 문서)
https://docs.couchdb.org/en/latest/api/index.html
공격 기법
NodeJS에서 CouchDB를 사용할 때에는 주로 아파치에서 개발한 nano 패키지를 사용한다.
nano 패키지는 get 함수를 사용하여 _id 값을 통해 데이터를 조회하거나
find 함수를 통해 쿼리 기반으로 데이터를 가져올 수 있다.
get 함수를 통해 데이터를 조회하는 경우 특수 구성 요소인 _all_docs, _db 등에 접근해 데이터베이스의 정보를 획득할 수 있다.
find 함수를 사용하는 경우에는 연산자와 같이 객체 타입의 값을 입력해 개발자가 의도하지 않은 행위를 수행할 수 있다.
nano 패키지의 get 함수
nano 패키지의 get 함수가 어떻게 구현되어 있는지 알아보자.
// { ..., get: getDoc, ...}
// https://github.com/apache/couchdb-nano/blob/befbcd9972520faa8850c9425faeb324aab005f5/lib/nano.js#L543-L552
// http://docs.couchdb.org/en/latest/api/document/common.html#get--db-docid
function getDoc (docName, qs0, callback0) {
const { opts, callback } = getCallback(qs0, callback0)
if (missing(docName)) {
return callbackOrRejectError(callback)
}
return relax({ db: dbName, doc: docName, qs: opts }, callback)
}
// ...
function relax (opts, callback) {
// ...
const req = {
method: (opts.method || 'GET'),
headers: headers,
uri: cfg.url
}
// ...
if (opts.db) {
req.uri = urlResolveFix(req.uri, encodeURIComponent(opts.db))
}
// ...
if (opts.path) {
req.uri += '/' + opts.path
} else if (opts.doc) {
if (!/^_design|_local/.test(opts.doc)) {
// http://wiki.apache.org/couchdb/HTTP_Document_API#Naming.2FAddressing
req.uri += '/' + encodeURIComponent(opts.doc)
} else {
// http://wiki.apache.org/couchdb/HTTP_Document_API#Document_IDs
req.uri += '/' + opts.doc
}
// http://wiki.apache.org/couchdb/HTTP_Document_API#Attachments
if (opts.att) {
req.uri += '/' + opts.att
}
}
// ...
if (typeof callback === 'function') {
// return nothing - feedback via the callback function
httpAgent(req, responseHandler(req, opts, null, null, callback))
} else {
// return a Promise
return new Promise(function (resolve, reject) {
httpAgent(req, responseHandler(req, opts, resolve, reject))
})
}
}
}
relax 함수의 초기화 과정에서 할당된 cfg.url 뒤에 DB 이름을 합친 뒤
입력받은 doc을 URL에 추가해 GET 메소드로 요청을 전송한다.
이 때, 이용자의 입력값을 get 함수의 인자로 사용할 경우 문제가 발생할 수 있다.
nano 패키지의 get 함수는 전달된 인자에 대한 필터링이 없어
공격자는 특성 구성 요소를 통해 데이터베이스의 정보를 획득할 수 있다.
nano 패키지의 find 함수
다음으로 nano 패키지의 find 함수가 어떻게 구현되어 있는지 알아보자.
// https://github.com/apache/couchdb-nano/blob/befbcd9972520faa8850c9425faeb324aab005f5/lib/nano.js#L941-L952
function find (query, callback) {
if (missing(query) || typeof query !== 'object') {
return callbackOrRejectError(callback)
}
return relax({
db: dbName,
path: '_find',
method: 'POST',
body: query
}, callback)
}
전달된 쿼리가 NULL인지와 객체 타입이 아닌지를 검사한다.
객체 형태의 데이터를 전달해서 사용하는 것을 알 수 있다.
객체 형태의 데이터를 입력받을 때 입력 값에 대한 검증이 없거나 미흡하면 문제가 발생할 수 있다.
selector 안에서 operator를 사용할 수 있는데 연산자가 이에 포함된다.
따라서 공격자가 조건 식을 만들고 조건에 해당하지 않는 데이터를 모두 획득할 수 있다.
find 함수의 인자로 이용자의 입력 값이 전달되고 객체 타입의 값을 입력할 수 있다면
데이터베이스의 중요 정보를 획득할 수 있다.
레퍼런스
'background > web' 카테고리의 다른 글
Command Injection for Linux (1) (0) | 2022.11.26 |
---|---|
[NoSQL] Redis DBMS (0) | 2022.11.24 |
SSTI(Server Side Template Injection) (0) | 2022.09.29 |
XXE Injection 이해를 위한 XML 기초 지식 (0) | 2022.08.29 |
XXE(XML External Entity) Injection (0) | 2022.08.27 |