Redis Lua 腳本:多項安全漏洞已修復
原文由 Salvatore Sanfilippo 于 發布,訂閱此部落格
一個多月前,我收到一封來自 Apple 資訊安全團隊的電子郵件。在稽核過程中,Apple 團隊在 Redis 的 Lua 子系統中發現了一個安全問題,具體來說是在 cmsgpack 函式庫中。這個函式庫並非 Lua 本身的一部分,而是我自己實作的 MessagePack。在合併一個用於擴充功能的 pull request 的過程中,這個安全問題被引入了。後來,同一個團隊又在 Lua 的 struct 函式庫中發現了一個新問題,同樣地,該函式庫也不是 Lua 本身的一部分,至少在我們使用的 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 使用者而言,風險僅限於將不可信任的資料餵給像是 struct.unpack() 這類函式,且是在 format 參數中選用了特別危險的解碼格式「bc0」的情況下。
安全公告的協調
在 Apple 資訊安全團隊、我本人以及各家 Redis 雲端服務供應商的合作與友善溝通下,我試著在聯繫了所有主要的 Redis 服務商後,再協調漏洞的發布時程,讓他們能在漏洞公開前先修補好系統。我提供了一個單一的修補程式,讓各家供應商能輕鬆套用到自家系統。最後,在昨天到今天之間,我準備好了包含這些安全修正的 Redis 3、4 和 5 的新修補版本。如果你正在閱讀這篇部落格文章,代表它們都已經發布了。遺憾的是,我沒能聯繫上規模較小或較新的雲端服務商。光是處理與 Redis Labs、Amazon、Alibaba、Microsoft、Google、Heroku、Open Redis 和 Redis Green 之間的溝通,就已經耗費了極大的心力,若再擴大資訊分享的範圍,洩漏的風險也會更高(每家公司都有許多人參與處理流程)。如果你是直到今天才得知此漏洞的 Redis 服務商,我感到很抱歉,我已經盡力了。
我想向 Apple 資訊安全團隊以及所有其他供應商,感謝他們在此問題上提供的提示與協助。
Lua 的問題
老實說,當初設計 Redis 的 Lua 引擎時,並沒有考慮到客戶與雲端服務供應商對立的這種安全模型。當時的假設有點是,你可以信任會去操作你 Redis 伺服器的人。因此,一般來說並未對 Lua 函式庫進行嚴格的安全審查。當時的想法是,反正如果你已經能存取 Redis API,本來就能做出更糟糕的事。
然而,後來情況有所演變,雲端服務供應商限制了對外開放給客戶的 Redis API,才得以提供代管式的 Redis 執行個體。不過,雖然像 CONFIG 或 DEBUG 這類指令被禁止了,但你其實很難避免開放 EVAL 和 EVALSHA。Redis 的 Lua 腳本功能是我們社群中最受歡迎的功能之一。
所以,在我沒有真正察覺的情況下,Lua 函式庫也逐漸成為一種攻擊途徑,這是在 Redis 暴露與提供給最終使用者的方式改變後,本應由 Redis 來處理的安全模型。如我所說,在這個模型中,受影響的與其說是 Redis 使用者,不如說是提供代管式 Redis 的「雲端」服務商,但無論如何,這都是一個必須處理的問題。
為了改善雲端服務供應商目前在 Lua 腳本這個特定問題上的安全現狀,我們能做些什麼?接下來幾個月,我規劃了幾件想做的事。
- Lua 堆疊保護。看起來 Lua 可以用一種能確保不會誤用 Lua 堆疊 API 的方式來編譯,代價是些許的效能損失。公平來說,我認為 Lua 對堆疊所做的假設有點過於簡單,導致函式庫開發者必須不斷檢查堆疊上是否還有足夠空間來推入新值。其他在相同抽象層級的語言,其 C API 就沒有這個問題。因此,我會試著了解在 Lua 底層 C API 中加入更多防護措施所帶來的效能下降是否可以接受,如果可以,就會加以實作。
- 安全稽核與模糊測試。即使時間有限,我已經在 Lua 的 struct 函式庫上進行了一些模糊測試。接下來我會持續進行相關工作,檢查這個領域中是否還有其他錯誤。我確信還有更多問題存在,我們之所以只找到特定數量的錯誤,僅僅是因為沒有更多時間去深入調查腳本子系統。因此,這是一項將會執行的重要工作。同樣地,在工作結束後,我會與各家 Redis 廠商協調,讓他們能及時修補。
- 從 Redis 使用者的角度來看,當有不可信任的資料要傳送至 Lua 引擎時,使用 HMAC 來確保資料未被竄改是非常重要的。舉例來說,有一種常見的作法是將使用者的狀態直接儲存在使用者端的 cookie 中,稍後再進行解碼。這類資料之後可能會被當作 Redis Lua 函式的輸入。這就是一個絕對需要使用 HMAC,以確保我們讀取到的正是先前儲存內容的例子。
- 更進一步的 Lua 沙箱化。關於這個主題應該有大量的文獻和最佳實務。我們已經實作了部分沙箱機制,但以我過去做安全工作的經驗來看,沙箱化終究是一場貓捉老鼠的遊戲,永遠無法做到完美。以 Redis 的目標而言,要追蹤 CPU/記憶體的濫用情況可能就過於複雜。然而,我們至少應該確保違規行為只會導致「優雅地」中止,而不會引發任何記憶體內容違規的問題。
- 也許是時候升級 Lua 引擎了?我不確定新版的 Lua 在安全性方面是否更為先進,不過我們面臨一個巨大的難題,那就是升級 Lua 可能會導致舊有的腳本無法再正常運作。這對 Redis 社群來說是一個非常大的問題,特別是考慮到 Redis 使用者平常撰寫的腳本類型,更先進的 Lua 版本其實只有些微的好處。
相關問題
已修復的問題列於以下 commit:
- 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.
第一個 commit 與此次的修復工作無關,是一個 redis-cli 的緩衝區溢位問題,只有在命令列中傳入過長的 host 參數時才會被觸發。其他問題則是我們在 cmsgpack 和 struct 套件中發現的問題。
用來重現這些問題的兩個腳本如下:
https://gist.github.com/antirez/82445fcbea6d9b19f97014cc6cc79f8a
以及
https://gist.github.com/antirez/bca0ad7a9c60c72e9600c7f720e9d035
兩者皆由 Apple 資訊安全團隊撰寫,不過第一個腳本經由我修改,以更穩定地觸發當機。
受影響的版本
基本上,每個具備 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 腳本子系統所規劃的安全稽核報告。
隨機一篇部落格
留言
登入後參與討論