Redis Security

2019-04-01 security Redis

Redis 是一個 in-memory 的開源專案,使用 key-value 的形式來儲存資料,常被用來當作資料庫或是 cache 使用,並透過 rdb 與 aof 機制來作到 persistence,rdb 用來存儲資料庫中的資料,而 aof 則是 log 檔,當 appendonly 為 yes 時,會將對 Redis 的操作 log 到 appendonly.aof 檔中。

預設的 service port 是 6379,版本 >= 3.2.7 之後偵測到指令裡面有 HOST 或是 POST 關鍵字會 abort connection,並且在版本 >= 3.2.0 預設開啟 protected mode,代表當沒有 bind 到任何 IP 位址且沒有密碼要求時,只有 IPv4、IPv6 的 loopback 或 Unix socket 可以訪問該 Redis。

Exploit

除了 memory corruption 相關的 CVE 弱點外,redis open port 還可以透過以下方式來 exploit

  • SLAVEOF
    • 可以對資料庫中的資料做任意的改動藉此來改變程式邏輯,但需要知道資料結構
  • MIGRATE
    • 可以從目標資料庫取得任意資料,但需要知道資料結構
  • CONFIG SET
    • 可以設定資料庫的除存路徑與名稱,可能可以寫 webshell,但需要知道路目標徑且權限為可寫
    • 當 Redis 是由 root 運行且伺服器上有 SSH 服務時可以考慮將公鑰寫入 authorized_keys 中就可以直接以 root 身份連上
    • 或是寫入 crontab 檔案來執行任意指令,但對平台有依賴性
      • debian 系列可執行的 crontab 權限需要是 0600 但 Redis 寫入會是 0644 且寫入的指令無法解析
      • centos 可以成功寫入 crontab 亦可正常解析,其對 crontab 格式要求疑似較為鬆散

Redis 目前支援兩種傳輸方式

  • Plaintext
    • 使用空格分開指令並用換行符號結尾
    • SET keyname value\n
  • Custom
    • 在傳輸的資料中,使用星號表示指令與參數數量,錢字號表示字串長度並以\r\n 區隔與結尾
    • *3\r\n$3\r\nSET\r\n$7\r\nkeyname\r\n$5\r\nvalue\r\n

Redis 中支援不同的 instance 之間互相同步資料,用的是傳統的 Master/Slave 模式,Master 與 Slave 之間的傳輸使用的就是 Redis 本身支援的協定與指令,並沒有特殊傳輸方式。

Rogue server

在找相關資料的時候發現 zeronights.ru 在 2018 時有一篇名為 redis-post-exploitation 的 talk 講述了一種架設 rogue server 來 RCE 的技巧。

Redis 要求的同步流程如下

  • PING, 測試目標是否存活
    • +PONG 回應表示連線正常
  • REPLCONF, 用以傳輸 master 與 slave 之間的複製訊息
    • +OK
  • PSYNC/SYNC <replid>, slave 會開始嘗試與 master 進行 full sync 或 partial sync
    • +CONTINUE <replid> 0

之後就可以透過 rogue server 對 slave 發送任意 Redis 指令,但這中間有一個問題,Redis 之間的同步並不會有指令執行結果,不過可以透過 Redis 中的 SCRIPT DEBUG 模式來解決。

首先使用 SLAVEOF 指令讓目標 Redis 成為自己 rogue server 的 slave,如此一來便可以對目標 Redis 下任意指令,但是兩個 Redis 同步時並不會顯示指令結果,但 Redis 在 SCRIPT DEBUG 模式時是可以執行 Redis 指令且會有結果顯示的,利用這個 feature 就可以取得 Redis 中的任意資料了。

解決取得回應問題後,為了要 RCE 還需要用到另外一個 feature,Redis 有開放 RedisModulesSDK供使用者撰寫自己的 Redis module,在PSYNC/SYNC請求階段透過回應 FULLRESYNC 將惡意 SO 檔當成 PAYLOAD 傳給 slave,slave 就會把檔案存成 dump.rdb(這是預設檔名,可能因設定而異),之後可以透過指令 MODLUE LOAD 來載入該 module,基本上編譯成 SO 又得以載入後就可以為所欲為了。

但在 Redis 5.0 之後,SCRIPT DEBUG 模式下無法使用 CONFIG 指令,這將導致無法透過 CONFIG GET/SET 取得或更動目標資料庫名稱,從而不知道要去哪裡載入 module。

這裡有另外一個解法,當 PAYLOAD 還沒完全送達時,Redis 會在目錄下存放一個暫存檔,檔名格式為 temp-<time>.<pid>.rdb,這樣一來只要透過前面取得回應的方式用 TIME 與 INFO SERVER 指令,一樣可以取得 Redis 目前的相關資訊,這樣一來便可以載入該檔案。

需要注意的是可能因為網路或是傳送指令執行有時間差,載入時可能需要嘗試後面一秒的檔案名稱,另外 pid 是對應 process_id 欄位沒錯,但 SCRIPT DEBUG 模式下的 pid 值與非 SCRIPT DEBUG 模式下的會相差一個整數,可能是因為 fork 出另外一個 process 執行的關係或是跟 OS 相關,純屬猜測尚未釐清。

exploit 步驟如下

  1. 使用 SLAVEOF 使對方成為 rogue server 的 slave
  2. 透過 SCRIPT DEBUG 取得 time 與 info server 中的 pid
  3. 使用新的連線發起 FULLRESYNC,回應惡意 module 與不正確的長度使對方進入等待
  4. 使用 MODULE LOAD ./temp-<time>.<pid>.rdb 載入惡意 module

Conclusion

slide 後面還有提到 Redis cluster 與 Redis sentinel 方面的 exploit 方式,基本上大同小異,自己在寫 PoC 的時候遇到一些坑,像是 pid 問題或是 Redis 4 與 Redis 5 之間使用的協定不同,不過基本上都可以透過 socat 來看兩者間的傳輸資料來釐清問題,然後再讀一下 RedisModuelsSDK 怎麼寫就差不多可以弄出來了。

slide 最後有提到說有向 Redis 官方回報這些問題,但官方表示 document 寫得很清楚了,如果將 Redis service port 開放出來又沒有任何密碼驗證要求,那本身就已經是危險至極的事情了,所以這不是他們的問題。

  • 可以考慮的安全設定
    • 修改 redis.conf
      • 開啟保護模式 protected-mode yes
      • 限定本機訪問 bind 127.0.0.1
      • 登入時要求密碼 requirepass XXXX
      • 更改預設埠號 port XXXX
      • 將危險 Redis 指令重新命名為空字串,將使得該指令變為不可用 rename-command FLUSHALL ""
    • 使用 iptables 設定訪問白名單

最後,在相關的參考資料中都有講到,可以使用 flushall 來提高寫入檔案的成功率(避免其他資料干擾寫入的檔案),但這將會對目標造成影響,不過 slide 中都沒有相關警告,而且在同步 DB 的過程中亂送東西的話一樣可能導致 redis crash 退出,或是同步之後有其他應用程式對 redis 執行查詢的動作,這一樣會導致出錯而 crash,雖然不是 100% 不會爆炸的 exploit 方式,但提供了另外一種攻擊的思維。

References