Redis Lua 스크립팅: 여러 보안 취약점 수정
원문은 Salvatore Sanfilippo님이 에 게재했습니다. 이 블로그 구독하기
한 달이 조금 넘은 시점에 애플 정보보안팀으로부터 메일을 한 통 받았다. 감사 과정에서 애플 팀은 Redis Lua 서브시스템, 특히 cmsgpack 라이브러리에서 보안 이슈를 발견했다. 이 라이브러리는 Lua 자체에 포함된 것이 아니라 내가 직접 작성한 MessagePack 구현체다. 기능 개선을 위한 풀 리퀘스트를 병합하는 과정에서 보안 이슈가 추가됐다. 이후 같은 팀이 Lua struct 라이브러리에서 또 다른 이슈를 발견했는데, 이 역시 우리가 사용하는 Lua 릴리스에는 원래 포함되지 않은 라이브러리였다. Redis 사용자들이 사용할 수 있는 Lua 인터프리터에 기능을 제공하기 위해 소스 코드를 그대로 임베드한 것이었다. 그 뒤로 내가 같은 struct 패키지에서 또 다른 이슈를 발견했고, 이후 알리바바 팀이 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() 같은 함수에 신뢰할 수 없는 데이터를 전달하는 경우 정도가 해당한다.
권고 조율
애플 정보보안팀과 나, 그리고 Redis 클라우드 제공업체 간의 협력과 원활한 소통 덕분에, 나는 외부의 주요 Redis 제공업체 모두에 연락한 뒤 취약점 공개를 조율하려 했다. 버그가 공개되기 전에 각자 시스템을 패치할 수 있도록 하기 위해서였다. 제공업체들이 쉽게 적용할 수 있도록 단일 패치를 제공했다. 마침내 어제와 오늘 사이에 보안 수정 사항을 포함한 Redis 3, 4, 5의 새로운 패치 릴리스를 준비했다. 이 블로그 글을 읽고 있다면 이미 모두 릴리스된 상태일 것이다. 안타깝게도 규모가 작거나 새로 생긴 클라우드 제공업체에는 연락하지 못했다. Redis Labs, Amazon, Alibaba, Microsoft, Google, Heroku, Open Redis, Redis Green과의 커뮤니케이션을 처리하는 것만으로도 이미 엄청난 노력이 들었고, 정보 공유 대상을 더 늘릴 경우 유출 위험은 더욱 커졌다(각 회사마다 과정에 관여한 인원이 많았다). 오늘에서야 이 취약점을 알게 된 Redis 제공업체가 있다면 유감스럽게 생각한다. 최선을 다하려 노력했다.
이 이슈에 대해 힌트와 도움을 준 애플 정보보안팀과 다른 모든 제공업체에 감사의 말을 전하고 싶다.
Lua의 문제
솔직히 말하면 Redis Lua 엔진이 설계될 당시에는 고객 대 클라우드 제공업체라는 보안 모델을 염두에 두지 않았다. 당시의 가정은 Redis 서버를 건드리는 사람은 신뢰할 수 있다는 정도였다. 그래서 일반적으로 Lua 라이브러리들은 보안 관점에서 면밀히 검토되지 않았다. 당시의 생각은 어차피 Redis API에 접근할 수 있다면 훨씬 더 나쁜 일도 할 수 있다는 것이었다.
하지만 이후 상황이 달라졌고, 클라우드 제공업체들은 관리형 Redis 인스턴스를 제공할 수 있도록 고객에게 노출하는 Redis API를 제한했다. 그런데 CONFIG나 DEBUG 같은 명령어는 차단할 수 있었지만, EVAL과 EVALSHA는 사실상 노출하지 않을 수 없다. Redis Lua 스크립팅은 우리 커뮤니티에서 가장 많이 사용되는 기능 중 하나다.
그래서 내가 제대로 인지하지 못하는 사이에, Redis가 최종 사용자에게 노출·제공되는 방식이 바뀌면서 Lua 라이브러리들이 본래 Redis가 처리해야 할 보안 모델에서 공격 벡터가 되어 버렸다. 앞서 말했듯이 이 모델에서는 Redis 사용자보다 관리형 Redis ‘클라우드’ 제공업체가 더 직접적인 영향을 받지만, 어쨌든 반드시 해결해야 할 문제다.
Lua 스크립팅이라는 특정 문제와 관련해 클라우드 제공업체의 현재 보안 상태를 개선하기 위해 무엇을 할 수 있을까? 앞으로 몇 달간 추진하고 싶은 몇 가지를 정리해 봤다.
- 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
둘 다 애플 정보보안팀이 작성했다. 다만 첫 번째 스크립트는 크래시를 더 안정적으로 일으키도록 내가 수정했다.
영향받는 버전
기본적으로 Lua 스크립팅이 포함된 모든 Redis가 영향을 받는다.
수정 사항은 다음 Github 태그로 제공된다:
- 3.2.12
- 4.0.10
- 5.0-rc2
안정 릴리스(4.0.10)는 예전과 마찬가지로 http://download.redis.io에서도 받을 수 있다.
릴리스 tarball의 해시는 여기에서 확인할 수 있다:
https://github.com/antirez/redis-hashes
릴리스된 버전에는 다른 여러 버그 수정도 함께 포함되어 있으므로, 새 버전으로 전환하면서 어떤 내용이 함께 업데이트되는지 릴리스 노트를 함께 읽어보는 것이 좋다.
향후 Redis의 Lua 스크립팅 서브시스템에 대해 계획된 보안 감사 보고서를 담은 블로그 글로 다시 찾아오길 기대한다.
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기