Clarifications on the Incapsula Redis security report

Salvatore Sanfilippo

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

원문은 Salvatore Sanfilippo님이 에 게재했습니다. 이 블로그 구독하기

며칠 전 하루를 시작하는데 트위터 피드가 “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 서버처럼 열려 있다는 뜻인데, 대상이 HTTP가 아니라 원래부터 외부에 그대로 노출되도록 설계되지 않은 Redis라는 점이 다르다.

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

그들이 Redis 인스턴스에 대한 공격 유형을 수집한 방법은 일부러 노출된 인스턴스들을 운영하면서 공격자들이 어떻게 접근하는지 모니터링한 것이었다. 많은 공격은 내가 예전에 공개했던 예시 공격[2]의 변형처럼 보인다.

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

또 하나 언급할 만한 점은 어떤 서비스든 노출된 인스턴스를 찾기 위해 IPv4 주소 공간 전체를 스캔하는 일은 아주 간단하다는 것이다. 예를 들어 masscan을 쓰면 된다. 사실 20년 전에도 내가 만든 hping으로 할 수 있었고, 당시에도 실제로 성공적으로 해본 기억이 있다.

TLDR: 인터넷에 열려 있는 Redis 인스턴스가 존재하는 이유는 잘못된 설정 때문이다. 공격자들은 채굴 소프트웨어 등을 설치해 이를 악용한다. 하지만 이야기는 여기서 끝나지 않는다… 계속 읽어보자.

보호 모드

개인적으로 보안은 끔찍한 분야라고 생각한다. 나도 그 분야에서 일한 적이 있지만, 더 이상 언더그라운드만의 일이 아니게 되자 떠나기로 했다. 사람들은 보안 이슈에 대해 할 말은 많으면서도, 정작 실제 시스템에 대한 보안 감사를 수행하는 일 같은 데는 별 관심이 없다(물론 Google Project Zero가 조금씩 바꾸고 있긴 하다). 그래서 N번째로 “Redis가 임시 파일 생성 공격에 취약하다”는 PGP 암호화 이메일을 받고 나서 [2]에 블로그 글을 썼다. 말하고 싶었던 건 “Redis 보안의 핵심을 놓치고 있는 것 아니냐”는 것이었다. 몇 분간의 리서치로 찾을 수 있는, 내 시스템 자체에 대한 꽤 심각한 공격을 그대로 공개했다. 메시지는 다시 말하지만, Redis는 외부에 그대로 노출되도록 설계된 시스템이 아니라는 것이었다. 그리고 정말로 hack3rzzz 놀이를 하고 싶다면 더 흥미로운 것도 많다는 얘기였다. 하지만 그렇게 함으로써 나는 결과적으로 전 세계에 열려 있는 수천 개의 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를 설치하고 있다는 사실조차 모르는 경우도 있다는 사실, 다른 무언가의 의존성으로 딸려 오거나 스크립트에 의해 설치되거나 실행 중인 ABI 이미지에 포함되어 있다는 이유만으로 Redis가 깔려 있다는 사실을 바꿀 수는 없다.

처음에 Shodan에서 검색해 보니 Redis 4.0의 보호 모드가 실제로 꽤 도움이 되는 것처럼 보였다! 내가 찾을 수 있었던 공개 인스턴스 대부분이 “-DENIED”를 반환했다. 훌륭했다. 하지만 [1]이 공개된 후 Shodan에(참고로 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는 암호화 없이 동작하고 이 기능은 opt-in 형태일 것이기 때문에 여기서 논의한 문제에는 별 영향을 주지 않을 가능성이 크다. 그럼에도 SSL은 특정 환경에서 더 안전한 Redis 경험을 위한 한 걸음 전진이기도 하다.

하지만 나는 ACL에 기대를 걸고 있다. 일반 사용자가 default 계정이 관리 명령어를 실행할 수 있도록 설정할 가능성은 낮아 보이기 때문이다. 특히 로컬 호스트에서 들어오는 연결은 바로 “admin” 사용자로 로그인되도록 할 계획이기 때문이다. 내 희망대로 된다면, 노출된 Redis 6 인스턴스는 피할 수 없으니 계속 보게 되겠지만, 적어도 그런 Redis 6 인스턴스들은 전체 시스템을 탈취하기 더 어렵게 만들어 줄 것이다. 적어도 이론상으로는 말이다. Redis의 EVAL 명령어는 Lua 스크립트 실행을 허용하는데, 이는 Redis의 핵심 기능이므로 기본적으로 허용되어야 한다. 우리는 일종의 샌드박스된 Lua 실행 환경을 제공하려 노력하지만, IT 보안을 어느 정도 지켜봐 왔다면 샌드박스는 언제나 불완전하고 봉쇄라기보다는 탈출 방법을 찾는 연습에 가깝다는 것을 알 것이다. 다만 이번에는 주장을 펼치겠다고 공격 방법을 공개하지는 않으려 한다.

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

댓글