https://dreamhack.io/learn/1/29#2
NoSql
=NotOnlySql
NOT Only SQL은 데이터를 다루기 위해 꼭 SQL을 사용하지 않아도 데이터를 다룰 수 있다는 의미를 가집니다.
NoSQL은 SQL을 사용해 데이터를 조회/추가/삭제하는 관계형 데이터베이스(RDBMS)와 달리 SQL을 사용하지 않으며, 이에 따라 RDBMS와는 달리 복잡하지 않은 데이터를 다루는 것이 큰 특징이자 RDBMS와의 차이점입니다. NoSQL 데이터베이스는 단순 검색 및 추가 작업을 위한 매우 최적화된 저장 공간입니다.
NoSQL 중 MongoDB, Redis, CouchDB에 대해서 다루도록 하겠습니다.
MongoDB
- Bug case
- Exploit Technique
- 실습 1,2
Redis
- Bug case 1
- Bug case 2
- Exploit Technique
- Redis 주의 사항
- 실습 1
CouchDB
- bug case
NoSQL - MongoDB
MongoDB는 key-value의 쌍을 가지는 JSON objects 형태인 도큐먼트를 저장합니다.
MongoDB는 아래와 같은 큰 특징을 가지고 있습니다.
- Schema가 존재하지 않아 각 테이블(MongoDB에선 Collection)에 대한 특별한 정의를 하지 않아도 됩니다.
- JSON 형식으로 쿼리를 작성할 수 있습니다.
- _id필드가 Primary Key 역할을 합니다.
https://cionman.tistory.com/46
RDBMS vs Mongodb
$and : 뒤의 조건이 true일때 true
Comparison
NameDescription
$eq | 지정된 값과 같은 값을 찾습니다. (equal) |
$gt | 지정된 값보다 큰 값을 찾습니다. (greater than) |
$gte | 지정된 값보다 크거나 같은 값을 찾습니다. (greater than equal) |
$in | 배열 안의 값들과 일치하는 값을 찾습니다. (in) |
$lt | 지정된 값보다 작은 값을 찾습니다. (less than) |
$lte | 지정된 값보다 작거나 같은 값을 찾습니다. (less than equal) |
$ne | 지정된 값과 같지 않은 값을 찾습니다. (not equal) |
$nin | 배열 안의 값들과 일치하지 않는 값을 찾습니다. (not in) |
Logical
NameDescription
$and | 논리적 AND, 각각의 쿼리를 모두 만족하는 문서가 반환됩니다. |
$not | 쿼리 식의 효과를 반전시킵니다. 쿼리 식과 일치하지 않는 문서를 반환합니다. |
$nor | 논리적 NOR, 각각의 쿼리를 모두 만족하지 않는 문서가 반환됩니다. |
$or | 논리적 OR, 각각의 쿼리 중 하나 이상 만족하는 문서가 반환됩니다. |
Element
NameDescription
$exists | 지정된 필드가 있는 문서를 찾습니다. |
$type | 지정된 필드가 지정된 유형인 문서를 선택합니다. |
Evaluation
NameDescription
$expr | 쿼리 언어 내에서 집계 식을 사용할 수 있습니다. |
$jsonSchema | 주어진 JSON 스키마에 대해 문서를 검증합니다. |
$mod | 필드 값에 대해 mod 연산을 수행하고 지정된 결과를 가진 문서를 선택합니다. |
$regex | 지정된 정규식과 일치하는 문서를 선택합니다. |
$text | 지정된 텍스트를 검색합니다. |
$where | 지정된 JavaScript 식을 만족하는 문서와 일치합니다. |
Bug Case
MongoDB에서 주로 발생하는 취약점으로 MongoDB Injection이 있습니다. MongoDB Injection 취약점은 주로 사용자 입력 데이터에 대한 타입 검증이 충분하지 않아 발생합니다.
오른쪽 탭의 GET 방식과 POST 방식처럼 입력 시 서버에서는 사용자의 입력 데이터를 다른 데이터 타입으로 처리할 수도 있습니다. MongoDB에서는 오브젝트 타입의 입력 데이터로 처리 시 쿼리 연산자등을 사용할 수 있으며, 이를 통해 의도하지 않은 행위를 수행할 수 있습니다.
또한 오른쪽 탭의 Sample Code와 같은 상황에서 사용자가 입력하는 uid와 upw를 오브젝트 타입의 입력 데이터로 조회하여 원하는 데이터를 조회할 수 있습니다.
GET
express 프레임워크 사용
https://yahohococo.tistory.com/40?category=763183
코드에 대한 설명은 위 자료를 참고하자
app.get('/', function(req,res)) : req는 요청, res는 응답
https://korbillgates.tistory.com/183
POST
(앞 express,require은 생략)
입력 처리
http://localhost:3000/query?uid[$ne]=a&upw[$ne]=a=> [{"_id":"5ebb81732b75911dbcad8a19","uid":"admin","upw":"secretpassword"}]
uid가 a가 아니고 upw가 a가 아닌 데이터 보여주기
app.post('/query', function(req,res)
{ db.collection('user').find({ 'uid': req.body.uid, 'upw': req.body.upw }).
toArray(function(err, result)
{ if (err) throw err; res.send(result); });
});
쿼리의 내용이 {'uid': input, 'upw': input} 으로 구성됩니다. req.body에 해당하는 부분은 문자열 형식이 아닌 array, object 등의 타입도 넣을 수 있습니다.
문자열 형식이 아닌 object 형태로 삽입이 가능하면 Query Operator를 사용할 수 있고 의도한 흐름과 다르게 작동시킬 수 있습니다.
객체 형식으로 데이터를 삽입하여 guest의 비번 알아내기
Blind Injection
MongoDB Injection 취약점에서도 SQL Injection와 같이 추출하기 위한 데이터가 직접적으로 확인할 수 없는 상황이 존재합니다.
Blind SQL Injection 기법의 원리는 DBMS의 함수 또는 연산 과정 등을 이용해 데이터베이스 내에 존재하는 데이터와 사용자 입력을 비교하며, 특정한 조건 발생 시 특별한 응답을 발생시켜 해당 비교에 대한 검증을 수행합니다.
MongoDB 쿼리에서 Injection이 발생할 때 MongoDB의 Operator($regex, $where)를 사용해 Blind Injection을 할 수 있습니다.
객체의 첫번째 단어가 'a'일때 'b'일때 ....
- $where Time Based
MongoDB의 sleep함수를 통해 시간 지연을 발생시킬 수 있습니다.
uid과 upw가 해당 값과 같을 때 시간 지연 발생
- $where Error Based
앞이 true라면 asdf로 인한 에러코드가 실행
앞이 false라면 에러코드 실행되지 않고 종료
blind injection
NoSQL - Redis
Redis는 key-value 데이터 모델을 가지며 메모리 기반으로 작동하는 NoSQL DBMS입니다. 메모리 기반이기 때문에 Read/Write 속도가 빨라 다양한 서비스에서 임시 데이터를 캐싱하는 용도로 많이 사용됩니다.
set-설정, get-조회
데이터 관련
명령어구조설명
GET | GET key | 데이터 조회 |
MGET | MGET key [key ...] | 여러 데이터를 조회 multiple get |
SET | SET key value | 새로운 데이터 추가 |
MSET | MSET key value [key value ...] | 여러 데이터를 추가 multiple set |
DEL | DEL key [key ...] | 데이터 삭제 |
EXISTS | EXISTS key [key ...] | 데이터 유무 확인 |
INCR | INCR key | 데이터 값에 1 더함 increase |
DECR | DECR key | 데이터 값에 1 뺌 decrease |
관리 명령어
명령어구조설명
INFO | INFO [section] | DBMS 정보 조회 |
CONFIG GET | CONFIG GET parameter | 설정 조회 |
CONFIG SET | CONFIG SET parameter value | 새로운 설정을 입력 |
Redis를 사용하는 서비스에서 악의적인 공격자가 원하는 명령어를 사용할 수 있는 버그가 발생하는 상황
해당 서비스의 권한을 획득하는 방법을 오른쪽 탭 내용 순서대로 다루도록 하겠습니다.
Bug Case
Redis를 사용하는 서비스에서 의도하지 않은 명령어를 실행할 수 있는 버그가 발생하는 상황에 대해 다룹니다.
- node-redis
- SSRF
Exploit Technique
Redis를 통해 공격할 수 있는 방법들에 대해 다룹니다.
- 다른 어플리케이션과 연계
- Redis 명령어
Redis 주의 사항
Redis를 통해 서비스 사용 시 주의해야할 사항들에 대해 다룹니다.
- 인증 체계
- bind
- Same Key (Prefix 없는 Key 사용)
Bug Case 1
node-Redis 모듈
req.query에 해당하는 부분은 문자열 타입외에도 array, object 타입도 넣을 수 있습니다.
NodeJS에서 많이 사용되는 redis 모듈에서는 command에 array타입이 입력 될 경우 아래와 같이 동작합니다.
첫번째 인자가 array이고 길이가 2이면 callback은 두번째 인자로
개발 시 의도된 value로 값이 설정되지 않고 임의의 값을 value를 사용할 수 있습니다.
http://localhost:3000/init?uid[]=test&uid[]={"level":"admin"}와 같이 array 타입 입력 시,
실제로 Redis에 요청하는 명령어는 Command("set", "test", '{"level":"admin"}')와 같은 명령어가 실행되어 원하는 Value를 가진 데이터를 생성할 수 있습니다.
??? 모르겠다 ㅠㅠ
Bug Case 2
SSRF (Server-Side Request Forgery)
Redis는 기본적으로 인증 체계가 존재하지 않으며, bind 127.0.0.1로 서비스되어 집니다. 그렇기 때문에 직접 접근하여 인증 과정없이 명령어를 실행할 수 있습니다.
https://dgkim5360.tistory.com/entry/install-redis-for-linux-or-windows
위와 같은 이유로 인해 악의적인 공격자는 SSRF 취약점을 이용하여 Redis에 명령어를 실행 시킬 수 있습니다.
Redis에서는 이전 명령어가 유효하지 않은 명령어가 입력되어도 연결이 끊어지지 않고 다음 유효한 명령어를 처리합니다. 아래와 같이 첫번째 줄은 유효한 명령어가 아니지만, 두번째 줄의 명령어는 성공적으로 실행된 것을 확인할 수 있습니다.
그 중 대표적으로 HTTP 프로토콜을 이용한 공격 방법이 있습니다.
아래와 같이 HTTP의 Body부분에 원하는 명령어를 포함시켜 공격하는 방법이 있습니다.
Redis는 HTTP 프로토콜을 이용한 SSRF를 방어하기 위해 HTTP의 주요 키워드가 명령어로 입력되면 해당 연결을 끊어버리는 방식을 통해 HTTP 프로토콜을 이용한 SSRF를 방어하였습니다.
Redis Exploit Technique
Redis 공격을 통해 얻을 수 있는 정보로는 가장 먼저 Redis에 저장되어 있는 정보입니다. 어플리케이션의 로직에 따라 주요한 정보가 포함되어 있을 수 있습니다.
그리고 Redis에 저장된 정보를 기반으로 로직을 실행하는 다른 어플리케이션을 이용하거나, Redis 자체의 명령어를 이용한 공격 방법들이 있습니다.
django-redis-cache
Python 에서 많이 사용되는 웹 프레임워크 중 하나인 Django 에서 Redis를 사용한 cache를 구현할 수 있는 모듈입니다.
아래 코드는 django-redis-cache 모듈을 사용한 예시 코드입니다.
p_set 함수가 실행되면 Redis에 cache_memo 라는 키가 생성됩니다.
cache.set(키,값,[만료 시간])
https://nachwon.github.io/redis/
키 조회
? 근데 cache_memo로 키를 설정했는데 왜 :1:cache_memo로 나오는 건지 모르겠다.
cache_memo 값을 확인해보면 Pickle로 dump된 값을 확인할 수 있습니다.
?? 왜 dump 값이 나오지
django-redis-cache 모듈의 코드를 보면 get_serializer_class 함수에서 serializer_class를 가져올 때 options의 SERIALIZER_CLASS 값을 가져오고 값이 없으면 redis_cache.serializers.PickleSerializer Class를 기본 값으로 사용합니다.
Server-side basic - Language specific Vulnerability 에서 다룬 내용처럼 serialize를 할 때 Python의 pickle 모듈을 사용할 경우 공격에 사용될 수 있습니다. django-redis-cache의 기본 Serializer인 PickleSerializer에서 pickle 모듈을 사용하기 때문에, Redis에 원하는 데이터 저장한 후 해당 데이터가 deserialize하게 되는 과정에서 pickle 모듈을 이용하여 공격을 수행할 수 있습니다.
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=syung1104&logNo=220129337910
pickle의 __reduce__함수가 함수를 반환하여 임의적으로 함수를 실행시킬 수 있다.
Redis Exploit Technique
Redis 명령어 (SAVE)
Redis는 기본적으로 메모리에 데이터를 저장 및 조회하는 인메모리 데이터베이스입니다. 하지만 데이터 손실 방지등의 이유로 지정된 일정 시간마다 메모리의 데이터를 파일 시스템에 저장합니다.
또한 Redis의 명령어를 통해 해당 파일을 저장하는 주기를 변경 또는 즉시 저장하게 할 수 있으며, 저장되는 파일의 경로, 파일 이름 및 저장될 데이터를 설정 할 수 있습니다.
설정 -- 파일 경로 /tmp로, 파일 이름은 redis.php파일로
데이터 설정(추가) test cmd 변수값
저장
xxd 파일: 파일을 16진수로 보여줌
https://twpower.github.io/122-xxd-command-usage
위와 같이 파일 시스템에 원하는 파일을 생성한 후, PHP 등의 다른 어플리케이션과 연계하여 공격을 수행할 수 있습니다.
Redis 명령어 (SLAVEOF / REPLICAOF)
다른 Redis 노드를 명령어를 실행하는 노드의 마스터 노드로 지정할 수 있습니다. 마스터 노드와 성공적으로 연결이 완료되면, 마스터 노드의 데이터를 복제하여 저장합니다.
SLAVEOF보다는 REPLICAOF를 사용하자(같은 기능)
해당 명령어가 성공적으로 실행되기 위해서는 마스터 노드에 연결을 맺는 과정이 포함되는데, 이 때 발생하는 네트워크 트래픽을 통해 Redis 공격이 성공적으로 수행되었다는 점을 원격으로 확인해 볼 수도 있습니다.
위와 같이 원하는 주소와 포트를 입력 시 아래와 같이 노드의 상태를 확인하기 위한 데이터가 수신되는 것을 확인할 수 있습니다.
l : listen모드로 포트 open
k: 소켓에 연결유지 옵션 설정
v: 더 자세하게
https://websecurity.tistory.com/109
https://devanix.tistory.com/307
또한 마스터 노드의 데이터를 가져오기 위한 Sync과정의 RDB 데이터를 임의의 데이터로 조작해 원하는 파일을 업로드하는 공격 방식도 존재합니다.
Redis 명령어 (MODULE LOAD)
Redis 4.0 버전 부터는 Module Load 명령어를 통해 새로운 라이브러리를 추가해 사용할 수 있습니다.
RedisModuleSDK(https://github.com/RedisLabs/RedisModulesSDK)를 기반으로 공유 라이브러리를 제작해 파일 업로드 또는 Sync과정을 조작하여 임의의 데이터를 업로드하는 등의 로직을 통해 해당 Redis 파일 시스템에 업로드한 후 MODULE LOAD를 통해 해당 라이브러리를 로드할 수 있습니다.
위와 같이 제작된 Redis Module을 컴파일 한 후, 아래와 같이 Redis가 접근할 수 있는 디렉토리에 모듈이 존재할 경우 해당 모듈을 로드하여 사용할 수 있습니다.
Redis Module에 system 함수와 같이 OS명령어를 실행하는 함수 또는 원하는 파일 시스템에 접근 할 수 있는 함수등을 포함 시켜 공격에 사용할 수 있습니다.
Redis 주의 사항
인증 체계
Redis는 기본 설정 상 인증 과정이 포함되어 있지 않은 어플리케이션입니다.
그런 이유로 Redis에 접근이 가능한 경우 원하는 명령어를 통해 앞서 배운 공격들을 수행하는데 어려움이 없습니다.
Redis 설정 파일에 아래와 같이 requirepass에 패스워드를 지정할 수 있습니다.
설정 이후 Redis 사용 시 AUTH 명령어를 통해 인증 과정을 거쳐야만 Redis 명령어를 사용할 수 있습니다.
Redis 버전 6.0 부터는 Multi Users와 ACL(Access Control List)를 통해 접근 제어를 할 수 있는 기능이 추가되었습니다. 이를 통해 다양한 유저에 대한 인증 및 권한/명령어 등을 분리하여 사용할 수 있습니다.
bind
Redis는 3.2.0 버전 부터 Bind 127.0.0.1 로 기본 설정되어 서비스됩니다. 하지만 구조적 문제 또는기능상의 구현을 위해 0.0.0.0 등 위험한 Bind 범위를 지정하여 서비스되는 경우가 있습니다. 악의적인 공격자에게 해당 서비스가 노출되어 직접 접근이 될 경우 앞서 배운 공격 방법 등을 통해 서비스에 위협이 발생할 수 있습니다.
Same Key (prefix 없는 key 사용)
기본 설정 상 Redis는 Key-Value로 저장되는 구조이고 RDBMS의 Schema/table 또는 MongoDB의 Collection과 같이 시스템적으로 특정한 영역으로 구분지어 저장되지 않습니다.
키 중복 문제 등을 방지하기 위해 object-type:id 같은 형식으로 스키마를 사용하고 여러 필드를 사용할 경우 ., -문자로 구분하여 사용해야 합니다. https://redis.io/topics/data-types-intro#redis-keys
예를 들어 "object-type:id"는 "user:1000"에서와 같이 좋은 생각입니다.
점 또는 대시는 "comment:1234:reply.to" 또는 "comment:1234:reply-to"와 같이 여러 단어로 된 필드에 자주 사용됩니다.
예를 들어, Cache를 Redis에서 구현할 때 Cache Type(e.g. 유저 정보 캐시/인증 관련 캐시/임시 데이터 등)별 Redis서버를 분리하지 않을 때 Key 값이 중복되어 사용될 경우 문제가 발생할 수 있습니다.
redis.io의 설명처럼 Redis에서 사용되는 Cache Type별로 Key 값에 구분자를 붙여 사용하지 않으면 서로 중복되어 사용될 수 있고 문제가 발생할 수 있습니다.
Redis에서 Key를 구분자 없이 사용하고, 사용자의 입력에 의해 key가 결정 된다면 사용자가 임의로 다른 로직의 키를 생성시켜 어플리케이션 로직에 혼선을 발생시킬 수 있습니다.
예를 들어 Redis에 사용자의 메일 인증번호와 인증 횟수를 확인하기 위한 정보를 저장하는 어플리케이션에서 Prefix없는 Key를 사용하게 되면 아래와 같은 문제가 발생할 수 있습니다.
기존에 사용되던 키가 새로 저장되는 키와 중복되어 이전에 저장된 랜덤한 인증 번호가 아닌 추측 가능한 숫자로 변경되었으며, 이를 통해 실제 인증 과정 없이 인증 로직을 우회할 수 있습니다.
그 외에도 각 데이터 별로 서로 다른 구분자를 사용하여 구현 시 문제가 발생할 수도 있습니다.
??
https://dreamhack.io/learn/1/29#17
풀이:
"/email_send" 페이지에서는 이메일 전송 시 유저가 입력한 email을 key로 랜덤한 번호가 설정되며, {email}_count 형태의 키로 인증 횟수가 생성됩니다.prefix가 지정되지 않은 키를 사용하기 때문에 유저가 임의로 키를 지정할 수 있다면, 해당 키의 데이터가 어떤 값이 오게 될지 예측할 수 있게 됩니다.1. user@dreamhack.io_count 로 이메일 전송.redis에서는 아래와 같은 값이 설정됩니다.user@dreamhack.io_count = {random number}user@dreamhack.io_count_count = 02. user@dreamhack.io 로 이메일 전송.redis에서는 아래와 같이 이전 키와 중복되어 새로운 값이 설정됩니다.user@dreamhack.io = {random number}user@dreamhack.io_count = 0user@dreamhack.io_count_count = 0추측 가능한 값으로 설정되기 때문에 이를 입력하여 인증 코드 과정을 우회할 수 있습니다.또한 "/email_verify"과정에서 {email}_count와 비교하여 우회할 수도 있습니다.
모르겠당..
NoSQL - CouchDB
CouchDB는 key-value의 쌍인 구조로 JSON objects 형태인 도큐먼트를 저장합니다. HTTP 기반 서버로 REST API 형식으로 HTTP Method(GET, POST, PUT, DELETE 등)를 기반해 요청을 처리합니다.
https://www.joinc.co.kr/w/Site/cloud/BigData/CouchDB/Tutorial
CouchDB에서 일반적으로 _ (밑줄) 문자로 시작하는 URL구성요소/JSON필드는 특수 구성요소를 나타냅니다. 예를 들어, /_all_dbs(URL)은 CouchDB 인스턴스의 모든 데이터베이스 목록을 가져옵니다.
JSON 내부의 특수필드로 _(밑줄) 문자로 시작하는 필드중 속성 값으로 사용되는 필드는 아래와 같습니다.
- _id : 도큐먼트의 아이디, 처음에 설정하지 않으면 랜덤한 값으로 설정되고 Primary Key 역할을 합니다.
- _rev : 문서의 버전 정보입니다.
Server
- / : 인스턴스에 대한 메타 정보를 반환합니다.
- /_all_dbs : 인스턴스의 databases 목록을 반환합니다.
- /_utils : 관리자페이지(Fauxton administration interface)로 이동합니다.
Databases
- /db : 지정된 데이터베이스에 대한 정보를 반환합니다.
- /{db}/_all_docs : 지정된 데이터베이스에 포함된 모든 도큐먼트를 반환합니다.
- /{db}/_find : 지정된 데이터베이스에서 JSON 쿼리에 해당하는 모든 도큐먼트를 반환합니다.
Bug Case
CouchDB에서는 주로 사용자 입력 데이터에 대한 타입 검증이 충분하지 않거나 특수 구성요소로 사용되어지는 값들에 대한 접근으로 인해 취약점이 발생합니다.
NodeJS에서 CouchDB를 사용할 때 apache에서 개발한 nano 패키지를 주로 사용합니다. nano 패키지에서 데이터를 가져올 때 get 함수를 사용해 문서의 _id 기반으로 가져오거나 find 함수를 사용해 쿼리 기반으로 값을 가져올 수 있습니다.
get 함수를 사용할 경우 앞에서 배운 특수 구성요소(_all_docs, _db 등)에 접근할 수 있으며, 이를 통해 의도하지 않은 행위를 수행할 수 있습니다.
find 함수를 사용할 경우 MongoDB에서 다룬 내용과 같이 오브젝트 타입의 입력 데이터로 처리 시 연산자등을 사용할 수 있으며, 이를 통해 의도하지 않은 행위를 수행할 수 있습니다.
'보안공부 > 배운내용정리' 카테고리의 다른 글
[dreamhack-ReverseEngineering] 2. x64 기초 (0) | 2021.09.03 |
---|---|
[dreamhack -Reverse Engineering] 1.리버스 엔지니어링이란 (0) | 2021.09.03 |
dreamhack05. Sql injection (0) | 2021.07.08 |
칼리리눅스 설치 (0) | 2021.05.23 |
dreamhack 04.Client-side Advanced (0) | 2021.05.21 |