Redis Lua scripting: several security vulnerabilities fixed

Salvatore Sanfilippo

Redis Lua 腳本:多項安全漏洞已修復

一個多月前,我收到了一封來自 Apple Information Security team 的電子郵件。在稽核過程中,Apple 團隊在 Redis Lua 子系統中發現了一個安全問題,具體是在 cmsgpack 函式庫中。這個函式庫並非 Lua 本身的一部分,而是我自己撰寫的 MessagePack 實作。在合併一個用於改進功能集的 pull request 的過程中,加入了一個安全問題。之後,同一個團隊又在 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 使用者的角度來看,風險僅限於在格式參數中選擇了特別危險的解碼格式「bc0」後,將不受信任的資料餵給像是 struct.unpack() 這類函式。

協調公告

感謝 Apple Information Security team、我本人以及 Redis 雲端供應商之間的合作與友善溝通,我嘗試在聯繫了所有主要的 Redis 供應商後,協調漏洞的發布,讓他們能在錯誤公開前修補其系統。我提供了一個單一的修補程式,讓供應商能輕鬆地套用到其系統中。最後,在昨天到今天之間,我準備了包含這些安全性修復的 Redis 3、4 和 5 的新修補版本。如果您正在閱讀這篇部落格文章,它們都已經發布了。遺憾的是,我未能聯繫到規模較小或較新的雲端供應商。光是處理與 Redis Labs、Amazon、Alibaba、Microsoft、Google、Heroku、Open Redis 和 Redis Green 之間的溝通就已經耗費巨大心力,而若將資訊分享範圍擴大到其他對象,外洩的風險也會更高(每家公司都有許多人員參與處理流程)。如果您是今天才得知此漏洞的 Redis 供應商,我深感抱歉,我已盡力而為。

我想向 Apple Information Security team 以及所有其他供應商,感謝他們針對此問題提供的提示與協助。

Lua 的問題

老實說,當初設計 Redis Lua 引擎時,並未考慮到這種客戶對上雲端供應商的安全模型。當時的假設有點像是,你可以信任會去碰觸你 Redis 伺服器的人。因此,一般來說,Lua 函式庫並未針對安全性進行嚴格檢視。當時的想法是,如果你已經能存取 Redis API,無論如何你都能做出更糟的事。

然而,後來情況有所演變,雲端供應商限制了向客戶暴露的 Redis API,使得提供代管式 Redis 執行個體成為可能。然而,雖然像是 CONFIG 或 DEBUG 這類指令被禁用,你卻無法真正避免暴露 EVAL 和 EVALSHA。Redis Lua 腳本是我們社群中最常被使用的功能之一。

因此,在我沒有真正注意到的情況下,Lua 函式庫也逐漸成為攻擊媒介,落入一個本應由 Redis 來處理的安全模型中,這是由於 Redis 向最終使用者暴露與提供的方式發生了變化。如我所說,在這個模型中,受影響的與其說是 Redis 使用者,不如說是代管式 Redis「雲端」供應商,但無論如何,這都是一個必須處理的問題。

針對 Lua 腳本這個特定問題,我們能做些什麼來改善雲端供應商目前的安全狀況呢?在接下來的幾個月裡,我列出了幾件想要著手的事項。

  1. Lua stack protection(Lua 堆疊保護)。看起來 Lua 可以透過某種方式編譯,雖然會帶來一些速度上的損失,但能確保無法濫用 Lua stack API(Lua 堆疊 API)。平心而論,我認為 Lua 對堆疊所做的假設有點過於簡單,導致 Lua 函式庫開發者必須不斷檢查堆疊上是否有足夠空間來推入新值。在相同抽象層級的其他語言,其 C API 並沒有這個問題。因此,我會試著了解在 Lua 低階 C API 中加入更多保護措施所造成的速度下降是否可以接受,若可以接受,就會加以實作。
  2. 安全性稽核與 fuzz testing(模糊測試)。即使我的時間有限,我已經在 Lua struct 函式庫中進行了一些 fuzz testing。我將會持續進行一項活動,檢查此領域中其他錯誤。我確信還有更多問題存在,而我們只發現了一定數量的錯誤,僅僅是因為沒有更多時間去調查腳本子系統。因此,這是一項將會執行的重要工作。同樣地,在活動結束時,我會與 Redis 供應商協調,讓他們能夠及時修補。
  3. 從 Redis 使用者的角度來看,當某些不受信任的資料被傳送到 Lua 引擎時,使用 HMAC(雜湊訊息鑑別碼)以確保資料未被竄改是很重要的。舉例來說,有一種常見的模式是將使用者的狀態儲存在使用者 cookie 本身中,稍後再進行解碼。這類資料之後可能會被當作 Redis Lua 函式的輸入。這就是一個絕對需要 HMAC 來確保我們讀取到的是先前所儲存內容的範例。
  4. 更多的 Lua sandboxing(沙箱化)。關於這個主題,應該有大量的文獻與最佳實務。我們已經實作了一些 sandboxing,但以我在資安領域的經驗來看,sandboxing 終究是一場貓捉老鼠的遊戲,永遠無法以完美的方式執行。舉例來說,CPU/記憶體的濫用對於 Redis 的目標而言,可能過於複雜而難以追蹤。然而,我們至少應該確保違規行為能夠導致「優雅地」中止,而不會造成任何記憶體內容違規問題。
  5. 也許是時候升級 Lua 引擎了?我不確定較新版本的 Lua 在安全性方面是否更為先進,然而我們面臨一個巨大的問題,那就是升級 Lua 可能會導致舊有腳本無法再運作。這對 Redis 社群來說是一個非常大的問題,特別是因為對於 Redis 使用者通常開發的腳本類型而言,更先進的 Lua 版本僅能帶來些微的幫助。

相關問題

已修復的問題列於下列提交中:

  • ce17f76b Security: 修復 redis-cli 緩衝區溢位。
  • e89086e0 Security: 修復 Lua struct 套件的偏移處理。
  • 5ccb6f7a Security: 由 @soloestoy 提供的更多 cmsgpack 修復。
  • 1eb08bcd Security: 為了安全性更新 Lua struct 套件。
  • 52a00201 Security: 修復 Lua cmsgpack 函式庫堆疊溢位。

第一個提交與此次工作無關,是一個僅在命令列中傳遞過長的主機參數時才可能被利用的 redis-cli 緩衝區溢位。其他問題則是我們在 cmsgpack 與 struct 套件上發現的問題。

用於重現這些問題的兩個腳本如下:

https://gist.github.com/antirez/82445fcbea6d9b19f97014cc6cc79f8a

以及

https://gist.github.com/antirez/bca0ad7a9c60c72e9600c7f720e9d035

兩者皆由 Apple Information Security team 撰寫。然而,第一個腳本經由我修改,以使其更可靠地引發當機。

受影響的版本

基本上,每個具備 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 腳本子系統所規劃之安全性稽核的報告。

原文由 Salvatore Sanfilippo 發布

本文章由 muse-spark-1.2-contributor 進行翻譯