xwork修补漏洞的悲剧

安全 应用安全
在漏洞发现者发布的POC中,并不能影响xwork 2.1.2之前的一些版本(这个版本之前的一些版本,下文会统称为老版本,之后的叫做新版),例如struts 2.0.14(就是struts修补了N个高危漏洞后的第一个版本,最常用的版本)其实是不能打的,本文会分析这个漏洞的起因,和结果,也会给出通杀POC的思路。

在漏洞发现者发布的POC中,并不能影响xwork 2.1.2之前的一些版本(这个版本之前的一些版本,下文会统称为老版本,之后的叫做新版),例如struts 2.0.14(就是struts修补了N个高危漏洞后的第一个版本,最常用的版本)其实是不能打的,本文会分析这个漏洞的起因,和结果,也会给出通杀POC的思路。

本文希望看懂文章的人,可以专注于分析思路,但是不希望大家拿着POC到处搞站,本文不提供任何黑客工具,所有的POC,都是已经公布过,无数人都知道的。

xwork修补漏洞的悲剧(漏洞历史):

xwork作为struts2和webwork的核心组件,曾经在已经修补过了“xwork参数拦截器允许ognl方法执行”漏洞,并给出了漏洞公告

http://struts.apache.org/2.x/docs/s2-003.html

S2-003

第一次修补

但是这个修补,最终的结果是个悲剧。大家还记得我以前说过,在《Struts2框架安全缺陷》一文中,完全不懂web安全的开发人员,为了修补XSS,竟然仅仅针对POC,过滤了这段字符“〈script〉”,一旦用户提交“〈script xxx〉”就绕过的傻X修补方式。

他们保持一贯的风格,利用正则“{\\p{Graph}&&[^,#:=]}*”修补此漏洞。并且又仅仅针对攻击者给出的POC,做出修补,使用POC测试通过就发布了。

解析下xwork开发人员使用POC的几个悲剧的测试用例。

为了让ParametersInterceptor认为它是个合法ognl语句,变量中必须最终包含#。

这行无法通过验证,java在做字符串运算时,\u0023会被转义为#之后,才会做匹配,所以返回false。下图可以看到,\u0023和#是完全相等的。

所以修补漏洞的开发人员,自作聪明,直接用正则把#干掉了。

攻击者发来的虽然是\u0023,但是这段字符在内存中做字符串运算时,会先变成\\u0023然后才做运算。java在处理用户提交的一段string包含\时,为了保证数据完整性,会自动多加一个\用做转义,比如用户提交了数据“\n”,在内存中作字符串运算时,不会真的用换行做运算,而是拿”\\n”这段字符做比较。那么用户提交的\u0023被转为\\u0023,就会绕过对#的检查。

证据如图:

这两个case没有通过代码验证原因同上。

悲剧就在这里,这里虽然是\\u0023,符合用户提交的场景,悲剧是这段字符里面因为有空格,就在+两边,所以无法通过,而攻击者如果去掉空格,就通过了。

官方就这样发布了,其实发布的是个漏洞版。

这是第一次修补,这次修补其实是大家都知道,因为发了公告出来,官方虽然公告说是高危,但是官方只知道攻击者可以通过ognl表达式修改server端的session等信息。这时官方还没有意识到这其实是个远程代码执行漏洞,ognl不仅仅支持修改,还支持执行一些静态方法,比如@Runtime@getRuntime().exec(“calc”)。 #p#

第二次修补

直到某天,可能是在版本xwork2.1.2时,官方偷偷修改安全配置,默认让SecurityMemberAccess(管理ognl权限的类)的allowStaticMethodAccess为false,这导致静态方法不能执行,并且不知什么原因,偷偷修改正则,也同时放开了对参数名称中空格字符的限制。

漏洞被爆

这次出了远程代码执行(Struts2/XWork < 2.2.0 Remote Command Execution Vulnerability),漏洞的发现者就是看到了\u0023的限制其实无效,研究出了绕过默认安全配置的方法,并且利用ognl允许静态方法执行,达到了远程代码执行的效果。

原理简介:

1、 用户提交了\u0023被转义为\\u0023,通过了对参数名称的验证后,最终ognl处理之前,又变成了\u0023,也就是#,符合了ognl语法。

2、 通过Ognl语句执行,可以在struts2和webwork运行起来时,把ognl上下文中的一些默认配置覆盖掉,漏洞发现者给出了不少可以覆盖的数值。

3、 虽然默认配置是禁止静态方法执行的,但是xwork的配置,其实是可以覆盖的,一旦覆盖掉“用于禁止静态方法执行”的value,当然又可以执行了。

4、 Runtime.getRuntime().exec()这段,其实可以当做静态方法调用,导致执行系统命令。Ognl语句调用静态方法:@Runtime@getRuntime().exec(“calc”)。

漏洞发现者给出了shellcode,但是经过我的测试,shellcode并不能影响所有版本,而是仅仅针对xwork2.1.2及以上的版本有效,xwork2.1.2及以上核心被应用在struts2和webwork某些版本中,所以他们间接受到了影响,但是修补代码,是xwork去做的。

漏洞发现者给出POC:

山寨的修补方式带来的后果

我看到很多人对这个漏洞简单分析了下,有一小撮不明真相的群众认为,这是因为参数名称\\u0023带来的后果,官方自己上次修补的不完善,所以,可以过滤\\u0023,搞定这件事情。

如图是我做见过的“其中一个”漏洞分析者,自己搞的山寨补丁,原理就是禁止\\u0023

摘自互联网某篇文章,不点名了,第二点解决方案居然不符合XML规则-_-!。

这只是其中一种方案,很多人说只要禁止\\u0023就可以了,不得不说,这个人应该分析了漏洞,并且了解了原理。他知道虽然官方的补丁出来了,但是官方用的是白名单形式,对参数名称限制太严厉,很多特殊符号不能使用,可能会导致部分应用出问题,这是典型的开发人员思维。

这是官方的补丁:

http://svn.apache.org/viewvc/struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java?view=markup&pathrev=956389

为了优先保证业务,只要禁止了\\u0023就可以修补漏洞,所以以上山寨方案貌似是可行的,并且经过测试POC打不了了。

我也看到了这个方法,这真是个悲剧,拦截\u0023能解决问题么?

为什么漏洞发现者,通过\u0023绕过了#限制?还有没有其他编码可以绕过?

答案是,还有其他编码可以绕过,仅仅控制了\u0023,是个悲剧。经过我实际测试,发现#号的8进制编码\43,也是在这里使用的,并且\043也是可以的。于是我笑了:

这段新的POC,没有任何一个\u0023,却一样可以执行calc,经过实际测试,绕过了所有仅仅过滤\u0023的防御。让他们慢慢修补吧,我们不着急,等大家都打上了过滤\u0023的补丁,再把这个新的POC放出来。

我看到neeao就很谨慎,直接上官方补丁,这是他对这个漏洞的分析:

http://neeao.com/archives/59/#p#

老版本的struts和webwork的POC不通用问题

在推行修补方案时,开发人员总是从自己的角度和经验修补漏洞,他们采用过滤\u0023的方案也罢了,最起码态度端正。不像有些互联网公司的开发根本不去补,原因很简单,他用的是老版本的struts和webwork。对xwork2.1.2以下核心的struts2和webwork,POC打下去没有任何效果,所以认为这个安全级别不高,还是等等官方公告吧(现在为止,官方没有发布任何公告,修补好代码提交SVN,也没有编译后发布版本)。

出于好奇,决定仔细研究下。我之前也不熟悉xwork源码和ognl,以前仅仅研究过struts2的部分源码,盲目的debug了好几天,解决了N个问题,才搞定:

空格问题:

原POC中,会传三个参数,它们的作用,首先解析下。

1、第一个参数

在高版本的struts中,allowStaticMethodAccess(允许静态方法访问访问,做权限判断)默认是false的。但是低版本的本来就是true,所以传不传都一样,可以省了。

2、第二个参数

这句必须在,否则也不会调用静态方法。

3、第三个参数

这句是shellcode,不能没有。

在老版本中,第二个参数,是不能运行的,把它弄的好看点:

注意,new后面,有空格,在ParametersInterceptor的参数正则验证中,根本过不去。 既然老版本不允许参数中出现空格,那么如果你的shell里如果有空格,会通过么?嘿嘿。。。只要你的shell,无法通过这段验证,就不会执行:

以上shellcode必须对空格和:符号,做16进制的转义,才能执行。

PS:大家都悄悄的,不要告诉那些“只拿poc,不看技术文章的那些不明真相群众”。

所以,要必须先解决的第一个问题是,改这个空格为\u0020,才能进入ognl表达式的流程。

在比较新一点的版本的xwork中,允许空格,当然,也是允许\u0020的,所以\u0020替换空格,就通杀了第一个问题的新老版本struts正则验证。

denyMethodExecution不能修改的问题:

改完之后,发现竟然还是不能赋值,经过调试,在内存中,看到的xwork.MethodAccessor.denyMethodExecution还是true,这说明这个表达式,没有执行成功。原因是这里做了new对象操作。先定义了#foo变量为new java.lang.Boolean类型,默认为false,之后denyMethodExecution等于#foo。这是不允许的,原来的POC导致空指针异常(原因后面说),后来解决了。

总之用这个,可以通杀新老版本,也不会爆空指针:

提交后再次查看内存中的context,发现这个值被修改为false。

看看shellcode

Shellcode的原理是,利用ognl支持静态方法执行,调用java的执行系统命令方法(其实完全可以调用任何java代码,比如写个文件等)。

Shellcode是可以做new操作的。

denyMethodExecution不能new操作,是因为这句执行时的上下文中, denyMethodExecution还是true,执行了这句,才是false,这时才可以new对象。

所以shellcode的上下文,是可以做new对象操作的。 #p#

Xwork的bug问题:

解决了这两个问题,其实已经给shellcode创造了完备的环境,按照xwork的逻辑,应该直接让我们调用静态方法才是,但是在shellcode运行时,居然爆出了空指针,这个问题我研究了好久才搞明白,原来是xwork自己出了bug,到了新版本时,才修补。

翻翻svn,看到在xwork2.1.2时,偷偷修改了一段代码。

SecurityMemberAccess这个类原来有个这样的方法

到了xwork2.1.2时,改为了:

注意标红的,如果name==null,就返回true。

为什么会有这行代码呢?

它调用isExcluded(name),进入isExcluded方法后,做正则表达式的验证。

如果传进来的是个paramName是null,并且excludeProperties是有值的,必然报错。Xwork的bug就是,所有的版本,调用静态方法时,都必然会传进来一个null,并且excludeProperties也是默认有值的。

任何一个静态方法的调用,这里传进来的都是null。

也就是说,要调用静态方法,就必须让excludeProperties这个家伙的值,是一个空的Set对象,否则对null对正则匹配,就会报错。

excludeProperties是个Set,在shell执行的上下文中,它的值是这样来的:

xwork处理用户发进来的ognl表达式时,会用xwork的SecurityMemberAccess做权限判断,以保证静态方法不会被人随便调用。原理是传给shell执行的上下文中几个默认配置,其中一个是默认的allowStaticMethodAccess=true,也会给excludeProperties这个Set对象会被添加一个value,结果就不是空的Set对象了。

所以,在shellcode上下文中,执行静态方法必然会出错。

让shellcode正常执行,解决方法的思路就是new一个新的HashSet。

但是经过我测试,这里还是不能做new的操作,因为new的时候,会调用构造方法,这实质上还是在调用方法,调用方法就会出错。为了证明我的猜测,debug调用到这一步的时候,手工修改它为new HashSet(),立刻就通过了。不能new,又怎么能得到一个空的Set呢?这是最后一道关卡。为了搞定它,我甚至查了struts-default.xml在内存中的位置,考虑是不是使用ognl修改掉。

因为这个放弃了一段时间,很郁闷,当我再次拾起来,却突然看到了这个值的默认值。

跟进emptySet();

这是个有意思的知识点,我也是第一次了解到,可以使用EMPTY_SET,取到一个空的set。

于是当我提交某些东西,让

这一句覆盖了默认的那个有了一个值的map,重新变成了空的map,这才真正的达到了静态方法的终极调用条件。

其实我都快吐血了,明明是xwork自己代码写的不严谨,直到他们新的版本才发现这个bug,并修补,居然导致我研究漏洞的同时还顺带帮他们处理bug。

最后终于找到机会鄙视一下漏洞发现者的poc,他说老版本的struts2可以使用XXX的POC,经过我测试,不可行,原理也很简单,本文说了,POC中不能有空格。

这句话即使在新版本中,也是没什么影响的,虽然会改变一个值,但是不受啥影响, shellcode中,有了这句话,就兼容新老版本,一切通杀了!

最后凑出通杀0DAY在这里:

被和谐了,没办法,毕竟在互联网公司里,要考虑到其他互联网公司同行的处境,虽然他们不一定都是漏洞公布的POC不能打的版本。

POC,不要找我要了,本文仅仅说下技术研究。

回顾下:

适合新版本绕过双引号和\u0023防御的poc:

适合新版本绕过\u0023的poc

仅仅适用老版本(struts2.0.12)的POC

仅仅适用新版本的也就是漏洞发现者放出来的poc

【编辑推荐】

  1. DZ7.1 and 7.2 0远程代码执行漏洞获取Webshell
  2. 利用MS08067远程溢出漏洞抓肉鸡
责任编辑:许凤丽 来源: IT专家网
相关推荐

2014-08-27 17:10:55

2023-10-21 00:20:16

2022-02-21 15:19:10

谷歌漏洞Linux

2023-08-02 09:59:51

2011-03-15 15:27:23

2009-07-06 15:05:23

2013-12-13 10:36:51

2023-06-27 11:19:28

2009-07-21 09:16:56

漏洞windows MobHTC

2009-12-29 14:25:14

phpXSS漏洞

2021-09-26 10:24:42

Windows0 day漏洞

2022-05-24 10:28:48

勒索软件漏洞网络攻击

2024-05-06 12:54:27

2010-06-28 10:46:33

Google浏览器安全安全漏洞

2009-10-10 13:57:42

微软windows 7补丁

2023-06-05 18:19:44

2021-10-15 11:33:11

苹果 iOS 漏洞

2024-04-25 12:17:35

2012-06-19 10:01:10

2011-05-13 10:19:03

点赞
收藏

51CTO技术栈公众号