Clarifications on the Incapsula Redis security report

Salvatore Sanfilippo

Incapsula Redis 보안 보고서에 대한 해명

며칠 전 하루를 시작하는데 트위터 타임라인이 온통 “Redis 서버의 75%가 악성코드에 감염됐다”는 식의 기사로 도배되어 있었습니다. 명백히 잘못 인용된 문장이었죠. Incapsula의 연구가 실제로 밝힌 내용은 인터넷에 아무런 보호 장치 없이 공인 IP로 열려 있는 Redis 인스턴스 중 75%가 감염되어 있다는 것이었습니다[1].

[1] https://www.incapsula.com/blog/report-75-of-open-redis-servers-are-infected.html

컴퓨터 보안이나 Redis 동작 방식에 대해 어느 정도 아는 분들에게는 굳이 해명할 필요도 없는 이야기입니다. 맥락을 어렵지 않게 파악할 수 있기 때문입니다. 그럼에도 이 글을 쓰는 이유는 두 가지입니다. 하나는 보안이나 Redis에 익숙하지 않은 언론이나 일반 사용자가 무슨 일이 일어나고 있는지 이해하는 데 도움이 될 수 있기 때문이고, 다른 하나는 외부에 노출된 Redis 인스턴스가 ‘안전한 기본값’에 대한 사례 연구로서 보안 업계에 흥미로운 시사점을 주기 때문입니다.

Incapsula 보고서

Incapsula 보고서부터 살펴보겠습니다. 이들이 한 일은 인터넷에 노출된 Redis 인스턴스를 분석한 것입니다. 공인 IP에서 비밀번호 보호도 없이 연결을 대기하고 있어 인터넷 어디서든 누구나 접속할 수 있는 인스턴스들이 대상이었습니다. 마치 HTTP 서버처럼 열려 있지만, 그 대상이 Redis라는 점이 다릅니다. Redis는 애초에 외부에 그대로 노출되도록 설계된 시스템이 아닙니다.

이는 전혀 새로운 이야기가 아닙니다. Redis가 워낙 널리 쓰이다 보니 전체 설치 수가 엄청나게 많고, 그중 일부는 노출된 채 방치됩니다. 사실상 처음부터 그래 왔습니다. 클라우드 업체에서 가상 머신을 하나 띄우고 Redis를 설치한 뒤 접속이 안 되자 VM의 포트를 모두에게 개방해 버리고, 그 순간 인스턴스는 무방비 상태로 동작하게 됩니다. 달라진 점이 있다면 과거에는 그렇게 노출된 인스턴스 상당수가 별다른 피해 없이 그냥 돌아갔다는 것입니다.せいぜい 스크립트 키디가 접속해 INFO 같은 명령어 몇 개로 내부를 들여다보는 정도에서 끝나는 경우가 대부분이었습니다. 이제는 암호화폐 채굴 열풍이 공격자들에게 타인의 시스템에 침입할 아주 좋은 동기를 제공하고 있습니다. 사실 최고의 동기죠. 돈이니까요. 그래서 같은 노출 인스턴스들이 이제는 암호화폐를 채굴하는 소프트웨어를 설치하기 위해 공격을 받고 있습니다. Incapsula는 그렇게 침해된 것으로 보이는 인스턴스의 비율을 조사한 것입니다.

이들이 Redis 인스턴스에 대한 공격 유형을 수집한 방법은 일부러 노출된 인스턴스를 여러 개 띄워 놓고 공격자들이 어떻게 표적으로 삼는지 모니터링하는 것이었습니다. 공격 중 상당수는 제가 예전에 공개한 예제 공격[2]의 변형처럼 보입니다.

[2] http://antirez.com/news/96

덧붙이자면 어떤 서비스든 노출된 인스턴스를 찾기 위해 IPv4 주소 공간 전체를 스캔하는 일은 아주 간단합니다. 예를 들어 masscan을 쓰면 됩니다. 하지만 20년 전에도 제가 만든 hping으로 같은 일을 할 수 있었고, 실제로 그때 성공적으로 해 본 기억이 있습니다.

요약하자면, 인터넷에 열려 있는 Redis 인스턴스가 존재하는 이유는 설정이 잘못됐기 때문이며, 공격자들은 채굴 소프트웨어 등을 설치해 이를 악용하고 있습니다. 하지만 이야기는 여기서 끝나지 않습니다. 계속 읽어 보시기 바랍니다.

보호 모드

보안은 솔직히 끔찍한 분야라고 생각합니다. 저도 그 분야에서 일한 적이 있지만, 더 이상 언더그라운드의 영역이 아니게 되자 떠나기로 했습니다. 사람들은 보안 이슈에 대해 할 말은 많은 반면, 정작 실제 시스템을 대상으로 보안 감사를 수행하는 일 같은 데에는 별 관심이 없습니다(참고로 Google Project Zero가 이 부분을 조금씩 바꾸고 있기는 합니다). 그래서 N번째로 ‘Redis가 임시 파일 생성 공격에 취약하다’는 PGP 암호화 메일을 받고 나서 [2]에 블로그 글을 썼습니다. “Redis 보안의 핵심을 놓치고 있는 것 아닙니까”라고 말하고 싶었을 뿐입니다. 몇 분 연구해서 찾을 수 있는, 매우 심각한 공격을 제 시스템에 대해 직접 공개했습니다. 메시지는 다시 말하지만 Redis는 외부에 노출된 채 두도록 설계된 것이 아니라는 것이었습니다. 정말로 해커 흉내를 내고 싶다면 더 흥미로운 것들이 많다는 얘기이기도 했습니다. 하지만 그렇게 함으로써 저는 곳곳에 열려 있는 수천 개의 Redis 인스턴스를 뚫을 수 있는 강력한 무기를 스크립트 키디들의 손에 쥐여 준 셈이 되었습니다.

제 잘못을 만회하고 속죄하려는 마음으로 상황을 조금이라도 개선할 방법을 고민하기 시작했습니다. Redis 3.x의 한 가지 문제는 기본적으로 모든 IP 주소에서 연결을 받는다는 점이었습니다. 그래서 설정 실수가 아주 쉬웠습니다. 포트만 열거나 방화벽을 전혀 두지 않으면 인스턴스가 그대로 노출되는 구조였습니다. 이에 대한 보안계의 주문은 ‘안전한 기본값으로 실행하라’는 것이었습니다. Redis 같은 네트워크 캐시의 경우, 기본 설정이 사실상 대부분의 실제 사용자에게는 설치 직후 제대로 동작하지 않는 상태가 된다는 뜻입니다. 더 나쁜 점은 아무것도 모르는 사용자가 문제를 해결하려고 그냥 ‘bind *’를 해 버리면 다시 원점으로 돌아간다는 것이었습니다. 그래서 조금 더 영리한 방법을 찾아보았고, 그 결과 ‘보호 모드(protected mode)’라는 기능을 만들었습니다. 덕분에 며칠 동안 집 앞에 모여 시위한 386 프로세서 그룹을 크게 실망시키기는 했습니다.

보호 모드의 아이디어는 모든 주소에서 연결을 받되, 로컬이 아닌 연결이 들어오면 왜 예상대로 동작하지 않는지, 어떻게 해야 하는지, 그리고 보호 모드를 꺼서 인스턴스를 모두에게 개방하는 어리석은 행동에 어떤 위험이 따르는지를 설명하는 오류 메시지를 서버가 돌려주도록 하는 것입니다. 이 기능이 어떻게 동작하는지 예시는 다음과 같습니다.

$ nc 192.168.1.194 6379
-DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server. 3) If you started the server manually just for testing, restart it with the '--protected-mode no' option. 4) Setup a bind address or an authentication password. NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside.

이 방식의 장점은 다음과 같습니다.

  1. 사용자가 상황을 해결하기 위해 무엇을 해야 하는지 알 수 있습니다. “connection refused”만 띄우는 것보다 훨씬 낫습니다.
  2. 사용자가 위험을 이해하지 못한 채 보호 모드를 끄는 일을 막고, 비밀번호를 설정하거나 특정 인터페이스에 바인딩하는 등 다른 해결책이 있음을 알리려고 합니다.

환상 깨기

제 환상은 어쩌면 이 방식이 안전한 기본값보다 더 도움이 될 수 있지 않을까 하는 것이었습니다. 하지만 안타깝게도 일정 비율의 사용자는 애초에 신경을 쓰지 않는다는 사실, 심지어 자신이 Redis를 설치하고 있다는 것조차 모른다는 사실, Redis가 다른 소프트웨어의 의존성으로 딸려 오거나 스크립트에 의해 설치되거나 실행 중인 ABI 이미지에 포함되어 있을 뿐이라는 사실은 어떻게 할 수 없습니다.

처음에 Shodan에서 검색해 봤을 때는 Redis 4.0의 보호 모드가 실제로 꽤 큰 도움이 되고 있는 것처럼 보였습니다. 제가 찾을 수 있었던 공개 인스턴스 대부분이 “-DENIED”를 반환했기 때문입니다. 아주 고무적이었습니다. 하지만 [1]이 공개된 뒤 Shodan에(트위터에서 정말 친절하고 큰 도움을 주었습니다) 노출된 Redis 인스턴스의 버전별 현황을 요청했고, 결과는 여전히 엄청나게 많은 Redis 4.0 인스턴스가 노출되어 있다는 것이었습니다[3].

[3] https://asciinema.org/a/8heQvivQkFmUrisbb9FQLfMBj

물론 3.0 인스턴스보다는 적지만 여전히 많습니다. 더 조사해 보니 설치 스크립트에서 기본적으로 Redis 보호 모드를 아예 제거해 버리는 VM 이미지도 있다는 얘기를 들었습니다. 보안 기능이 귀찮다고 느끼는 사람들이 있기 때문입니다. 네트워크상에서 설치하자마자 바로 동작하기를 원하니, 귀찮은 것을 그냥 없애 버리는 것입니다. 그러다 그 이미지가 인기를 끌고, 많은 사람들이 무슨 일이 일어나고 있는지 모른 채 설치하면서 Redis 4가 보호 모드가 꺼진 채 설치되고 인스턴스가 노출되는 것입니다.

이는 사용자가 너무 쉽게 비활성화할 수 있는 안전한 기본값에 대한 교훈이라고 생각합니다. 어느 정도 사고 건수를 줄이는 데는 도움이 될 수 있어 보이지만, 사고를 예외적인 일로 만들 정도는 아닙니다.

앞으로는?

Redis 보안 모델의 근본적인 문제 중 하나는 CONFIG 같은 특수 명령을 이용해 일반 API로 서버를 재설정할 수 있다는 점입니다. 이는 Redis의 매우 유용한 기능이지만, Redis API에 접근할 수 있게 되면 인스턴스를 침투하기가 훨씬 쉬워지며, Redis를 사용하는 일반 애플리케이션은 이 수준의 접근 권한이 필요하지 않습니다.

그래서 Redis 6에서는 ACL을 도입할 예정입니다. 이번에도 기존 사용자가 불편을 겪지 않도록 하는 방식으로 기능을 도입하려 합니다. 아무런 인증 정보 없이 접속하면 Redis는 자동으로 ‘default’ 사용자로 클라이언트를 로그인시키는데, 이 사용자는 애플리케이션이 일반적으로 하는 모든 작업은 수행할 수 있지만 모든 관리 명령은 거부됩니다. 물론 설정을 다르게 하여 더 엄격하게 만들 수도 있고, 특정 패턴과 일치하는 키에 대해서만 특정 명령을 호출할 수 있는 새 사용자를 만드는 식으로도 구성할 수 있습니다.

Redis 6에서는 SSL 연결 지원도 함께 병합할 계획입니다. 기본적으로 Redis는 암호화 없이 동작하고 이 기능은 선택 사항이기 때문에 여기서 논의한 문제에는 별 영향이 없을 가능성이 크지만, SSL 역시 특정 환경에서 더 안전한 Redis 경험을 위한 한 걸음입니다.

하지만 저는 ACL에 기대를 걸고 있습니다. 일반 사용자가 default 계정에 관리 명령 실행 권한을 부여할 가능성은 낮아 보이기 때문입니다. 특히 로컬 호스트에서 들어오는 연결은 바로 ‘admin’ 사용자로 로그인되도록 할 계획이기 때문입니다. 제 기대대로 된다면 Redis 6 인스턴스가 계속 노출되는 일은 피할 수 없겠지만, 적어도 그러한 Redis 6 인스턴스는 전체 시스템을 손상시키기 더 어렵게 만들 것입니다. 적어도 이론상으로는 그렇습니다. Redis의 EVAL 명령은 Lua 스크립트 실행을 허용하는데, 이는 Redis의 핵심 기능이므로 기본적으로 허용되어야 합니다. 어느 정도 샌드박스된 Lua 실행 환경을 제공하려 노력하고 있지만, IT 보안을 오래 지켜봐 온 분이라면 샌드박스가 언제나 불완전하며 봉쇄책이라기보다는 탈출 방법을 찾는 연습에 가깝다는 것을 아실 겁니다. 다만 이번에는 주장을 펼치기 위해 공격 방법을 공개하는 일은 피하려고 합니다.

원문은 Salvatore Sanfilippo님이 에 게재했습니다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.