Redis Lua 스크립팅: 여러 보안 취약점 수정
한 달이 조금 넘은 시점에 Apple Information Security 팀으로부터 메일을 한 통 받았습니다. 감사 과정에서 Apple 팀은 Redis Lua 서브시스템, 특히 cmsgpack 라이브러리에서 보안 이슈를 발견했습니다. 이 라이브러리는 Lua 자체에 포함된 것이 아니라 제가 직접 작성한 MessagePack 구현체입니다. 기능 개선을 위한 풀 리퀘스트를 병합하는 과정에서 보안 이슈가 추가되었습니다. 이후 같은 팀이 Lua struct 라이브러리에서도 새로운 이슈를 발견했는데, 이 라이브러리 역시 적어도 저희가 사용하는 Lua 릴리스에서는 Lua 자체에 포함된 것이 아니었습니다. Redis 사용자가 사용할 수 있는 Lua 인터프리터에 일부 기능을 제공하기 위해 소스 코드를 그대로 임베드한 것이었습니다. 이후 제가 같은 struct 패키지에서 또 다른 이슈를 발견했고, 뒤이어 Alibaba 팀이 cmsgpack과 Lua API를 사용하는 다른 코드 경로에서 다수의 이슈를 추가로 발견했습니다. 짧은 시간 안에 저는 Lua 관련 취약점 더미 위에 앉아 있는 신세가 되었습니다.
이러한 취약점은 클라우드에서 관리형 Redis 서버를 제공하는 경우에 특히 중요합니다. Redis 서버에 직접 접근하지 않고는 발견된 취약점을 악용할 가능성이 매우 낮기 때문입니다. 많은 Redis 사용자는 cmsgpack이나 struct 패키지를 전혀 사용하지 않으며, 사용하더라도 신뢰할 수 없는 입력을 전달할 가능성은 극히 낮습니다. 하지만 클라우드 제공업체의 상황은 다릅니다. 이들은 때로는 멀티 테넌시 구성으로 Redis 인스턴스를 운영하며, 서비스를 구독한 사용자에게 그대로 노출합니다. 사용자는 해당 Redis 인스턴스에 무엇이든 전송할 수 있고, 이를 통해 취약점을 촉발하고 메모리를 손상시키며 Redis 프로세스를 침해하고, 잠재적으로는 Redis 프로세스에 대한 완전한 제어권을 탈취할 수 있습니다.
예를 들어 다음의 간단한 Python 프로그램은 cmsgpack 취약점 중 하나를 이용해 Redis를 크래시시킬 수 있습니다 [1].
[1] https://gist.github.com/antirez/82445fcbea6d9b19f97014cc6cc79f8a
다만 자신의 인스턴스에 전송되는 데이터를 직접 제어하는 일반적인 Redis 사용자의 관점에서는, 위험은 format 인자에서 특히 위험한 디코딩 형식인 “bc0”를 선택한 뒤 struct.unpack() 같은 함수에 신뢰할 수 없는 데이터를 전달하는 경우로 제한됩니다.
권고문 조율
Apple Information Security 팀과 저, 그리고 Redis 클라우드 제공업체 간의 협력과 원활한 소통 덕분에, 저는 외부의 주요 Redis 제공업체에 모두 연락을 취한 뒤 취약점 공개 시점을 조율하려 했습니다. 버그가 공개되기 전에 각자 시스템을 패치할 수 있도록 하기 위해서였습니다. 제공업체들이 쉽게 적용할 수 있도록 단일 패치를 제공했습니다. 마침내 어제와 오늘 사이에 보안 수정 사항을 포함한 Redis 3, 4, 5의 새로운 패치 릴리스를 준비했습니다. 이 블로그 게시물을 읽고 계시다면 이미 모두 릴리스된 상태일 것입니다. 안타깝게도 규모가 작거나 신규인 클라우드 제공업체까지는 연락을 드리지 못했습니다. Redis Labs, Amazon, Alibaba, Microsoft, Google, Heroku, Open Redis, Redis Green과의 커뮤니케이션을 처리하는 것만으로도 이미 엄청난 작업이었고, 정보 공유 대상을 늘릴수록 유출 위험도 더 커졌습니다(각 회사마다 이 과정을 담당하는 인원이 많았기 때문입니다). 만약 오늘에서야 이 취약점에 대해 알게 된 Redis 제공업체가 계시다면 죄송하다는 말씀을 드립니다. 최선을 다하려 노력했습니다.
Apple Information Security 팀과 이 이슈에 대해 힌트와 도움을 주신 모든 제공업체에 감사의 말씀을 전하고 싶습니다.
Lua의 문제
솔직히 말하면 Redis Lua 엔진을 설계할 당시에는 고객 대 클라우드 제공업체라는 보안 모델을 염두에 두지 않았습니다. 당시의 가정은 Redis 서버를 건드리는 사람은 신뢰할 수 있다는 것이었습니다. 그래서 일반적으로 Lua 라이브러리에 대해 보안 검토를 철저히 하지 않았습니다. 당시의 생각은 Redis API에 접근할 수 있다면 어차피 훨씬 더 큰 피해도 줄 수 있다는 것이었습니다.
하지만 이후 상황이 달라졌고, 클라우드 제공업체들은 관리형 Redis 인스턴스를 제공할 수 있도록 고객에게 노출하는 Redis API를 제한했습니다. CONFIG나 DEBUG 같은 명령어는 차단하면서도, EVAL과 EVALSHA는 사실상 노출하지 않을 수 없습니다. Redis Lua 스크립팅은 커뮤니티에서 가장 많이 사용되는 기능 중 하나이기 때문입니다.
그래서 제가 미처 인지하지 못하는 사이에, Redis가 최종 사용자에게 노출·제공되는 방식이 변화하면서 Lua 라이브러리 역시 Redis가 처리해야 할 보안 모델에서 공격 벡터가 되었습니다. 말씀드렸듯 이 모델에서는 Redis 사용자보다 관리형 Redis ‘클라우드’ 제공업체가 더 큰 영향을 받지만, 어쨌든 반드시 다뤄야 할 문제입니다.
Lua 스크립팅이라는 특정 문제와 관련해 클라우드 제공업체의 현 보안 상태를 개선하기 위해 무엇을 할 수 있을까요? 향후 몇 달간 진행하고자 하는 몇 가지 과제를 정리했습니다.
- Lua 스택 보호. 약간의 속도 저하를 감수하면 Lua 스택 API를 오용할 수 없도록 보장하는 방식으로 Lua를 컴파일할 수 있는 것으로 보입니다. 솔직히 말하면 Lua가 스택에 대해 하는 가정이 다소 단순하다고 생각합니다. 라이브러리 개발자가 새로운 값을 푸시할 공간이 스택에 충분한지 끊임없이 확인해야 하기 때문입니다. 동일한 추상화 수준의 다른 언어들은 이런 문제가 없는 C API를 제공합니다. 따라서 Lua 저수준 C API에 더 많은 안전장치를 적용했을 때의 성능 저하가 수용 가능한지 파악하고, 가능하다면 구현해 볼 계획입니다.
- 보안 감사와 퍼즈 테스팅. 시간이 제한적이었음에도 이미 Lua struct 라이브러리에 대해 일부 퍼즈 테스팅을 수행했습니다. 이 영역의 다른 버그를 점검하는 활동을 계속 이어갈 예정입니다. 분명 더 많은 이슈가 있을 것이며, 지금까지 특정 버그들만 발견된 것은 스크립팅 서브시스템을 조사할 시간이 더 없었기 때문일 뿐입니다. 따라서 이는 앞으로 수행될 중요한 활동입니다. 활동이 끝나면 다시 Redis 벤더들과 조율하여 제때 패치할 수 있도록 하겠습니다.
- Redis 사용자의 관점에서는 신뢰할 수 없는 데이터를 Lua 엔진으로 보낼 때 데이터가 변조되지 않았음을 보장하기 위해 HMAC을 사용하는 것이 중요합니다. 예를 들어 사용자의 상태를 사용자 쿠키 자체에 저장해뒀다가 나중에 디코딩하는 널리 쓰이는 패턴이 있습니다. 이러한 데이터는 이후 Redis Lua 함수의 입력으로 사용될 수 있습니다. 이는 우리가 이전에 저장한 내용을 그대로 읽고 있음을 보장하기 위해 HMAC이 반드시 필요한 사례입니다.
- Lua 샌드박싱 강화. 이 주제에 대해서는 참고할 문헌과 모범 사례가 많을 것입니다. 이미 일부 샌드박싱을 구현해 두었지만, 보안을 다루던 시절의 경험을 비춰보면 샌드박싱은 결국 고양이와 쥐 게임이며 완벽하게 구현될 수 없습니다. 예를 들어 CPU/메모리 남용은 Redis의 목표상 추적하기가 너무 복잡할 수 있습니다. 하지만 적어도 위반이 발생했을 때 메모리 침해 없이 ‘우아하게’ 중단될 수 있도록 보장해야 합니다.
- Lua 엔진을 업그레이드할 때가 된 것일까요? 새로운 버전의 Lua가 보안 관점에서 더 발전했는지는 확실하지 않습니다. 하지만 Lua를 업그레이드하면 기존 스크립트가 더 이상 동작하지 않을 수 있다는 큰 문제가 있습니다. Redis 커뮤니티에는 매우 큰 이슈이며, 특히 Redis 사용자가 일반적으로 작성하는 스크립트 유형을 고려하면 더 발전된 Lua 버전이 주는 이점은 제한적이기 때문입니다.
수정된 이슈
수정된 문제는 다음 커밋에 정리되어 있습니다:
- ce17f76b Security: fix redis-cli buffer overflow.
- e89086e0 Security: fix Lua struct package offset handling.
- 5ccb6f7a Security: more cmsgpack fixes by @soloestoy.
- 1eb08bcd Security: update Lua struct package for security.
- 52a00201 Security: fix Lua cmsgpack library stack overflow.
첫 번째 커밋은 이번 작업과 무관하며, 커맨드 라인에서 긴 host 인자를 전달했을 때만 악용 가능한 redis-cli 버퍼 오버플로입니다. 나머지 이슈들은 cmsgpack과 struct 패키지에서 발견된 문제들입니다.
이슈를 재현하는 두 스크립트는 다음과 같습니다:
https://gist.github.com/antirez/82445fcbea6d9b19f97014cc6cc79f8a
그리고
https://gist.github.com/antirez/bca0ad7a9c60c72e9600c7f720e9d035
두 스크립트 모두 Apple Information Security 팀이 작성했습니다. 다만 첫 번째 스크립트는 크래시를 더 안정적으로 재현할 수 있도록 제가 수정했습니다.
영향을 받는 버전
기본적으로 Lua 스크립팅을 포함하는 모든 Redis가 영향을 받습니다.
수정 사항은 다음 Github 태그로 제공됩니다:
- 3.2.12
- 4.0.10
- 5.0-rc2
안정 릴리스(4.0.10)는 기존과 마찬가지로 http://download.redis.io에서도 제공됩니다.
릴리스 타르볼 해시는 다음에서 확인할 수 있습니다:
https://github.com/antirez/redis-hashes
릴리스된 버전에는 다른 여러 버그 수정도 포함되어 있으므로, 새 버전으로 업그레이드하면서 함께 적용되는 내용을 파악하기 위해 릴리스 노트를 함께 읽어보시는 것을 권장합니다.
향후 Redis Lua 스크립팅 서브시스템에 대해 계획된 보안 감사 결과를 담은 블로그 게시물로 다시 찾아뵐 수 있기를 바랍니다.
글을 무작위로 읽기