分类 Java安全 下的文章

CVE-2018-1270 Remote Code Execution with spring-messaging

影响版本

  • Spring Framework 5.0 to 5.0.4
  • Spring Framework 4.3 to 4.3.14

漏洞分析

Spring Framework通过spring-messageing模块和STOMP代理对象通讯。根据漏洞描述可以知漏洞出现在spring-message模块 或者是 stomp-websockets模板块,下面逐一分析:

spring-websockets 模块

存在的敏感方法

@Nullable

public String[] decode(String content) throws IOException {
return (String[])this.objectMapper.readValue(content, String[].class);
}

反序列化使用的jackson组件,但是没有开启autotype功能,并且代码指定了反序列化类型为String[].class,利用jndi注入方式会导致异常,没有成功。

625fbd72201477f4d64f9b4a2e79a5a1.png

分析spring-message模块

DefaultSubscriptionRegistry类中方法addSubscriptionInternal存在expression = this.expressionParser.parseExpression(selector)(危险的SPEL表达式语句)。
根据上下文可以初步判定selector参数可以控制,但是了解SPEL注入的同学应该知道,要想达到代码执行,需要调用expression.getValue()或者expression.setValue()方法。继续寻找会发现该文件的154-164行调用了expression对象的getValue方法。

复现下漏洞的触发流程:

点击Connet按钮抓包

52b29eca6c5ea5429201c48c8ad96bee.png

修改请求报文,插入如下字段

\nselector:new java.lang.ProcessBuilder("/Applications/Calculator.app/Contents/MacOS/Calculator").start()

43ec3a11b117e9b86582d16d1922fc13.png

回到聊天窗口,发送任意消息即可触发恶意代码

c25b1fe4957ce9fcd465d867e1d010df.png

9d57f7cb1ad30ab1e747922601163798.png

586b85fa87889b1950535a76e5c25553.png

修复方案

  • 5.0.x users should upgrade to 5.0.5
  • 4.3.x users should upgrade to 4.3.15

漏洞二:xss vul in gs-messaging-stomp-websocket demo

2f1a64a5f7f2e688198fc8ffe5d805eb.png

Patch:https://github.com/spring-guides/gs-messaging-stomp-websocket/commit/6d68143e04ea1482b724c3f620688ec428089bc0

From:https://pivotal.io/security/cve-2018-1270

Jolokia JNDI Injection&XXE Vulnerability分析复现

0x01 JNDI Injection CVE-2018-1000130

1.1 什么是JNDI注入

参考:https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf

1.2 漏洞复现

Jolokia的JNDI注入问题出现在jsr160模块,根据官方文档可以很容找到注入点

6.6. Proxy requests For proxy requests, POST must be used as HTTP method so that the given JSON request can contain an extra section for
the target which should be finally reached via this proxy request. A
typical proxy request looks like

{
    "type" : "read",
    "mbean" : "java.lang:type=Memory",
    "attribute" : "HeapMemoryUsage",
    "target" : {
         "url" : "service:jmx:rmi:///jndi/rmi://targethost:9999/jmxrmi",
         "user" : "jolokia",
         "password" : "s!cr!t"
    }
  }

根据补丁信息很容易确定漏洞位置:

url within the target section is a JSR-160 service URL for the target
server reachable from within the proxy agent. user and password are
optional credentials used for the JSR-160 communication.

1.png

1.3 漏洞利用

2.png

3.png

4.png

1.4 影响

5.png

1.5 漏洞Bypass

补丁信息,可以看到增加ldap的黑名单:service:jmx:rmi:///jndi/ldap:.*

https://github.com/rhuss/jolokia/commit/2f180cd0774b66d6605b85c54b0eb3974e16f034

6.png

但是JDNI注入支持的协议有LDAP、RMI、Cobra三种,所以补丁后还是存在问题的。

7.png

8.png

0x02 XXE Vulnerability In PolicyDescriptor Class

2.1 漏洞复现

9.png

10.png

11.png

12.png

2.2 漏洞利用

由于对Jolokia不熟悉,目前还没有找到用户可控输入点。

0x03 参考

Java反序列化漏洞学习实践三:理解Java的动态代理机制

0x01 基础

代理模式:为其他对象提供一种代理以便控制对这个对象的访问(所谓控制,就是可以在其调用行为,前后分别加入一些操作)。

代理模式分类:

  1. 静态代理(其实质是类的继承,比较容易理解)
  2. 动态代理(这是我们需要关注的重点,)比较重要!!

0x02 静态代理Demo和理解

package Step3;

/*
 * 代理模式的简单demo,静态代理
 */

public class proxyTest{
    public static void main(String[] args) {
        Subject sub=new ProxySubject();
        sub.request();
    }
}

abstract class Subject
{//抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
    //类比网络代理,比如http代理,都支持http协议
    abstract void request();
}

class RealSubject extends Subject
{//真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
    //类比真实的http请求
       public RealSubject()
       {
       }

       public void request()
       {
              System.out.println("From real subject.");
       }
}

class ProxySubject extends Subject//关键是类的继承
{//代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
    //类比通过代理发出http请求,这个代理当然可以对http请求做出任何想要的修改。
    private RealSubject realSubject; //以真实角色作为代理角色的属性

       public ProxySubject()
       {
       }

       public void request() //该方法封装了真实对象的request方法
       {//所谓的“控制”就体现在这里
        preRequest(); 
              if( realSubject == null )
        {
                     realSubject = new RealSubject();
              }
        realSubject.request(); //此处执行真实对象的request方法
        postRequest();
       }

    private void preRequest()
    {
        //something you want to do before requesting
        System.out.println("Do something before requesting");
    }

    private void postRequest()
    {
        //something you want to do after requesting
        System.out.println("Do something after requesting");
    }
}

运行效果:

1.png

0x03 动态代理Demo及理解

在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler(Interface)、另一个则是Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。

大致逻辑流程就是:

定义一个接口(抽象角色)-->基于以上接口实现一个类(真实角色)-->基于InvocationHandler实现一个处理器类;

接着调用流程:

实现一个真实角色对象-->实现一个处理器对象-->构建一个新的代理对象,这个对象基于已有的处理器 和接口的类-->再把这个代理对象转换为【接口的类型、抽象角色的类型】,不能是真实角色的类型,为什么?

package Step3;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/*
 * 代理模式的简单demo,动态代理,动态代理利用了反射机制
 * 每一个动态代理类都会有一个与之关联的invocation handler。真正的调用是在invocation handler的invoke()方法里完成的。
 * 感谢蝶离飞、廖新喜2为师傅的指导
 */

public class proxyTest2{
    public static void main(String[] args) {
        DynamicSubject sub=new RealDynamicSubject();//之前这里sub的类型是RealDynamicSubject,不对;但是为什么呢?
        Handler handler = new Handler(sub);
        DynamicSubject sub2 = (DynamicSubject)Proxy.newProxyInstance(DynamicSubject.class.getClassLoader(), new Class[]{DynamicSubject.class}, handler); 
        //CLassLoader loader:指定动态代理类的类加载器
        //Class<?> interfaces:指定动态代理类需要实现的所有接口
        //InvocationHandler h: 指定与动态代理类关联的 InvocationHandler对象
        DynamicSubject sub3 = (DynamicSubject)Proxy.newProxyInstance(DynamicSubject.class.getClassLoader(), sub.getClass().getInterfaces(), handler);

        DynamicSubject sub4 = (DynamicSubject)Proxy.newProxyInstance(DynamicSubject.class.getClassLoader(), RealDynamicSubject.class.getInterfaces(), handler);

        System.out.println("sub.getClass() = "+sub.getClass());
        System.out.println("DynamicSubject.class = " +DynamicSubject.class);
        System.out.println(new Class[]{DynamicSubject.class});
        System.out.println(RealDynamicSubject.class.getInterfaces());

        sub2.request();
        sub3.request();
        sub4.request();
    }
}

interface DynamicSubject
{//抽象角色:通过接口或抽象类声明真实角色实现的业务方法。注意:动态代理只能是接口,否则代理类转成该类型事会报错
    //类比网络代理,比如http代理,都支持http协议
    abstract void request();
}

class RealDynamicSubject implements DynamicSubject
{//真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理handler处理调用。
    //类比真实的http请求
       public RealDynamicSubject()
       {
       }

       public void request()
       {
              System.out.println("From real subject.");
       }
}

/**
 * 处理器
 */
class Handler implements InvocationHandler{
    private Object obj; //被代理的对象,不管对象是什么类型;之前声明成RealDynamicSubject,不应该这么做
    /**
     * 所有的流程控制都在invoke方法中
     * proxy:代理类
     * method:正在调用的方法
     * args:方法的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//接口必须实现的方法,也是逻辑核心
        System.out.println("Do something before requesting");
        Object xxx = method.invoke(this.obj, args);
        System.out.println("Do something after requesting");
        return xxx;
    }
    public Handler(Object obj) {
        //构造函数,把真实角色的实例传递进来,这个代理handler的目的就是处理它
        this.obj = obj;
    }
}

运行效果:

2.png

0x03 思考总结

在后续将要学习的反序列化PoC构造过程中,我们需要用到这个动态代理机制,因为它提供一种【方法之间的跳转,从任意方法到invoke方法的跳转】,是我们将参数入口和代码执行联系起来的关键!

本文代码下载地址:

https://github.com/bit4woo/Java_deserialize_vuln_lab/tree/master/src/Step3

0x04 参考

Java反序列化漏洞学习实践二:Java的反射机制(Java Reflection)

0x00 前言

学习Java的反射机制是为了理解Apache Commons Collections中的反序列化漏洞做准备的。

0x01 基础

Java反射机制

  • 指的是可以于运行时加载,探知和使用编译期间完全未知的类.
  • 程序在运行状态中, 可以动态加载一个只有名称的类, 对于任意一个已经加载的类,都能够知道这个类的所有属性和方法;
    对于任意一个对象,都能调用他的任意一个方法和属性;
  • 加载完类之后, 在堆内存中会产生一个Class类型的对象(一个类只有一个Class对象),
    这个对象包含了完整的类的结构信息,而且这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以被称之为:反射.
  • 每个类被加载进入内存之后,系统就会为该类生成一个对应的java.lang.Class对象,通过该Class对象就可以访问到JVM中的这个类.

Class对象的获取方法

  • 实例对象的getClass()方法;
  • 类的.class(最安全/性能最好)属性;(如Demo代码和下图)
  • 运用Class.forName(String className)动态加载类,className需要是类的全限定名(最常用).
    注意,有一点很有趣,使用功能”.class”来创建Class对象的引用时,不会自动初始化该Class对象,使用forName()会自动初始化该Class对象

注意,有一点很有趣,使用功能”.class”来创建Class对象的引用时,不会自动初始化该Class对象,使用forName()会自动初始化该Class对象

1 (1).png

0x02 通过反射方法调用函数

该Demo主要实践主要学习使用反射方法来调用类中的函数。

package Step2;

import java.lang.reflect.Method;

public class reflectionTest {

    public static void main(String[] args){

        Method[] methods = test.class.getMethods();
        //获取类的方法二,有点类似python的getattr()。java中每个类型都有class 属性
        //通过类的class属性获取对应的Class类的对象,通过这个Class类的对象获取test类中的方法集合

        /* String name = test.class.getName();
         * int modifiers = test.class.getModifiers();
         * .....还有很多方法
         * 也就是说,对于一个任意的可以访问到的类,我们都能够通过以上这些方法来知道它的所有的方法和属性;
         * 知道了它的方法和属性,就可以调用这些方法和属性。
         */

        //调用test类中的方法
        for(Method method : methods){
            if(method.getName().equals("int2string")) {
                System.out.println("method = " + method.getName());

                Class[] parameterTypes = method.getParameterTypes();//获取方法的参数
                Class returnType = method.getReturnType();//获取方法的返回类型
                try {
                    //method.invoke(test.class.newInstance(), 666);
                    Object x = method.invoke(new test(1), 666);
                    System.out.println(x);
                    // new关键字能调用任何构造方法。 newInstance()只能调用无参构造方法。
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

        try {
            Method method = test.class.getMethod("int2string", Integer.class);
            Object x = method.invoke(new test(2), 666);//第一个参数是类的对象。第二参数是函数的参数
            System.out.println(x);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class test{
    private Integer n;  

    public test(Integer n){ //构造函数,初始化时执行
        this.n = n;
    }
    public String int2string(Integer n) {
        System.out.println("here");
        return Integer.toString(n);
    }
}

0x03 通过反射方法弹个计算器

Step1中,我们通过重写readObject方法,直接在里面使用Runtime.getRuntime().exec("calc.exe")来执行代码。现在需要改造一下,使用反弹方法来实现,成功调试的代码如下:

package Step2;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;

/*
 * 有了反射方法的基础,再结合step1,实现一个基于反射方法的弹计算器。
 */

public class reflectionTest2 implements Serializable{
    private Integer n;  

    public reflectionTest2(Integer n){ //构造函数,初始化时执行
        this.n = n;
    }
    public String int2string(Integer n) {
        System.out.println("here");
        return Integer.toString(n);
    }
    private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException{
        in.defaultReadObject();//调用原始的readOject方法

        try {//通过反射方法执行命令;
        Method method= java.lang.Runtime.class.getMethod("exec", String.class);
        Object result = method.invoke(Runtime.getRuntime(), "calc.exe");    
        }
        catch(Exception e) {
            e.printStackTrace();
        }
        }

    public static void main(String[] args){
        //reflectionTest2 x= new reflectionTest2(2);
        //operation.ser(x);
        operation.deser();
    }
}



class operation {
    public static void ser(Object obj) {
        //序列化操作,写数据
        try{
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.obj"));
            //ObjectOutputStream能把Object输出成Byte流
            oos.writeObject(obj);//序列化关键函数
            oos.flush();  //缓冲流 
            oos.close(); //关闭流
        } catch (FileNotFoundException e) 
        {        
            e.printStackTrace();
        } catch (IOException e) 
        {
            e.printStackTrace();
        }
    }

    public static void deser() {
        //反序列化操作,读取数据
        try {
            File file = new File("object.obj");
            ObjectInputStream ois= new ObjectInputStream(new FileInputStream(file));
            Object x = ois.readObject();//反序列化的关键函数
            System.out.print(x);
            ois.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

执行结果:

2.png

0x04 思考总结

程序的两大根本:变量与函数

所以总的来说,要想控制程序实现命令执行,有2个方向:

  • 控制代码、函数:就像命名注入等注入类漏洞一样数据被当作了代码执行;或者和上面的Demo代码一样重写readObject,加入自定义的代码(当然这种场景基本不存在,任意文件上传和执行勉强算是属于这种情况)。
  • 控制输入、数据、变量:利用代码中已有的函数和逻辑,通过改变输入内容的形态实现流程的控制(不同的输入会走不同的逻辑流程,执行不同代码块中的代码)。

对于java反序列化漏洞,属于控制数据输入,它有2个基本点必须要满足:

  1. 有一个可序列化的类,并且该类是重写了readObject()方法的(由于不存在代码注入,只能查找已有代码逻辑中是否有这样的类)。
  2. 在重写的readObject()方法的逻辑中有 method.invoke函数出现,而且参数可控。

再稍作抽象:

  1. 有一个可序列化的类,并且该类是重写了readObject()方法的。(主线流程,反序列化漏洞都是这个主线逻辑流程)
  2. 在重写的readObject()方法的逻辑中有
    直接或间接使用类似method.invoke这种可以执行调用任意方法的函数,而且参数可控。(是否还有其他函数可以达到相同的目的呢?)

本文代码可从github下载:

https://github.com/bit4woo/Java_deserialize_vuln_lab/tree/master/src/Step2

0x05 参考

Java反序列化漏洞学习实践一:从Serializbale接口开始,先弹个计算器

0x01 基本概念

  • 什么是序列化和反序列化

Serialization(序列化)是指把Java对象保存为二进制字节码的过程;反序列化deserialization是把二进制码重新转换成Java对象的过程。

  • 什么情况下需要序列化

a.当你想把的内存中的对象保存到一个文件中或者数据库中时候;

b.当你想用套接字在网络上传送对象的时候;

c.当你想通过RMI传输对象的时候;

总之,序列化的用途就是传递和存储。

  • 如何实现序列化

将需要序列化的类实现Serializable接口就可以了,Serializable接口中没有任何方法,可以理解为一个标记,即表明这个类可以被序列化。

序列化与反序列化都可以理解为“写”和“读”操作 ,通过如下这两个方法可以将对象实例进行“序列化”与“反序列化”操作。

/**

 * 写入对象内容

 */

private void writeObject(java.io.ObjectOutputStream out)

/**

 * 读取对象内容

 */

private void readObject(java.io.ObjectInputStream in)
  • 一些注意点

当然,并不是一个实现了序列化接口的类的所有字段及属性,都是可以序列化的:

如果该类有父类,则分两种情况来考虑:

1.如果该父类已经实现了可序列化接口,则其父类的相应字段及属性的处理和该类相同;

2.如果该类的父类没有实现可序列化接口,则该类的父类所有的字段属性将不会序列化,并且反序列化时会调用父类的默认构造函数来初始化父类的属性,而子类却不调用默认构造函数,而是直接从流中恢复属性的值。

如果该类的某个属性标识为static类型的,则该属性不能序列化。

如果该类的某个属性采用transient关键字标识,则该属性不能序列化。

a.当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;

b.当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;

0x02 简单实例

以下代码是一个简单的实现序列化和反序列化操作的Demo:

package Step1;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class deserTest implements Serializable {  

    /**
     * 创建一个简单的可被序列化的类,它的实例化后的对象就是可以被序列化的。
     */
    private static final long serialVersionUID = 1L;

    private int n;  

    public deserTest(int n){ //构造函数,初始化时执行
        this.n=n;
    }

    public static void main(String[] args) {
        deserTest x = new deserTest(5);//实例一个对象
        operation.ser(x);//序列化
        operation.deser();//反序列化
    }
}

class operation {
    public static void ser(Object obj) {
        //序列化操作,写数据
        try{
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.obj"));
            //ObjectOutputStream能把Object输出成Byte流
            oos.writeObject(obj);//序列化关键函数
            oos.flush();  //缓冲流 
            oos.close(); //关闭流
        } catch (FileNotFoundException e) 
        {        
            e.printStackTrace();
        } catch (IOException e) 
        {
            e.printStackTrace();
        }
    }

    public static void deser() {
        //反序列化操作,读取数据
        try {
            File file = new File("object.obj");
            ObjectInputStream ois= new ObjectInputStream(new FileInputStream(file));
            Object x = ois.readObject();//反序列化的关键函数
            System.out.print(x);
            ois.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

运行结果:

1.png

0x03 自定义反序列化的行为:弹个计算器

自定义序列化和反序列化过程,就是重写writeObjectreadObject方法。

对以上代码进行改造,加入readObject方法的重写,再重写函数中加入自己的代码逻辑。

package Step1;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class deserTest2 implements Serializable {  

    /**
     * 创建一个简单的可被序列化的类,它的实例化后的对象就是可以被序列化的。
     * 然后重写readObject方法,实现弹计算器。
     */
    private static final long serialVersionUID = 1L;

    private int n;

    public deserTest2(int n){ //构造函数,初始化时执行
        this.n=n;
    }
    //重写readObject方法,加入了弹计算器的执行代码的内容
    private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException{
        in.defaultReadObject();//调用原始的readOject方法
        Runtime.getRuntime().exec("calc.exe");
        System.out.println("test");
    }

    public static void main(String[] args) {
        //deserTest2 x = new deserTest2(5);//实例一个对象
        //operation2.ser(x);//序列化
        operation2.deser();//反序列化
    }
}

class operation2 {
    public static void ser(Object obj) {
        //序列化操作,写数据
        try{
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.obj"));
            //ObjectOutputStream能把Object输出成Byte流
            oos.writeObject(obj);//序列化关键函数
            oos.flush();  //缓冲流 
            oos.close(); //关闭流
        } catch (FileNotFoundException e) 
        {        
            e.printStackTrace();
        } catch (IOException e) 
        {
            e.printStackTrace();
        }
    }

    public static void deser() {
        //反序列化操作,读取数据
        try {
            File file = new File("object.obj");
            ObjectInputStream ois= new ObjectInputStream(new FileInputStream(file));
            Object x = ois.readObject();//反序列化的关键函数
            System.out.print(x);
            ois.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

以上代码,先取消main函数中的注释运行一次,以便生成正确的object.obj文件。而后加上注释运行一次,以保证只从object.obj文件中读取内容进行反序列化操作。如果无错误,第二次执行将看到弹计算器。

2.png

本文代码可从github下载:

https://github.com/bit4woo/Java_deserialize_vuln_lab/tree/master/src/Step1

0x04 参考