此文是我前年研究struts框架相关安全特性做的总结,这里发上来,各位安全工作人员在查找Struts相关应用的漏洞有一定用处,但因为是去年的文档,因此可能有一些过时内容,大家在参考时需要注意。另外由于这篇文章主要是用来做总结,因此也没有多少例子,具体案例大家可以关注乌云主站一一对应。
Author: wofeiwo#80sec.com
Date: 2011-06-14
前言
Struts是目前J2ee Web开发中最常见使用的MVC框架。由于使用框架编写的应用程序与原生jsp编写的Web程序区别较大,因此有必要专门撰写一篇文档描述Struts框架的安全特性,及其检查方法。
Struts介绍
Struts是一套MVC框架,目前分为两个大版本号。1.x系列,已经放弃开发,是一个较为简单的框架,安全特性也较少。2.x系列则是直接与webwork这个框架合并之后基于webwork重新开发的一套全新框架。目前主流使用框架基本就是2.x系列。
Struts1
图 2.1 struts1架构图
如上图所示,struts 1.x系列的模型主要分为几个部分:最主要的Dispatcher、Controller是使用J2ee的servlet实现的,用户请求过来,通过用户自定义的一系列过滤器(filter)之后,servlet分析匹配其url结构,再转给各action类,最后进行form的填充控制、View层的调用展现。其主要代码是在action中实现的,可以根据struts-config.xml中的配置,根据url->action->view的对应关系一一查看其代码。Struts1中的统一控制一般做在filter中,或者由form的校验函数做数据有效性控制。
Struts2
图 2.2 struts2架构图
而struts2.x的架构就复杂很多,也细致的多。用户的请求过来,先经过一系列过滤器(Filter实现),最后一个过滤器通过actionProxy才是真正对不同的action派发调用,其中的派发规则由struts.xml定义。这几部分相加,才类似于之前Struts1中的ActionServlet的作用。在调用action之前,还有类似Hook的机制,在action执行前后、view执行前后分别有可以统一执行的用户自定义代码。在struts2的说法中,叫做拦截器(Interceptor)。通常struts2中的统一控制就在过滤器、拦截器、校验器这几个方面做控制,其中后两者最普遍。
如何区分Struts框架应用程序
URL中以.do结尾的,struts1框架;以.action结尾的,struts2框架。
Struts1由于是通过servlet进行的action控制,因此主要配置文件在WEB-INF/web.xml及struts-config.xml这两个文件中;Struts2的配置文件则主要就是WEB-INF/struts.xml。
目录组织结构中如果有Filter、Interceptor的定义实现,通常是Struts2的代码。
如果有专门的xxxForm.java代码,一般是Struts1的项目;Struts2通常只有一些简单的Java Bean定义作为模型。
Struts框架安全特性
Struts1的白盒安全测试方法
之前我们说过,Struts1中的统一控制一般做在filter中,或者由form的校验函数做数据有效性控制。因此,无论是哪一方面的统一控制,例如参数的统一过滤、验证是否登录、文件上传验证之类,通常是在Filter中进行的;而参数的有效性,例如email只允许[0-9a-zA-Z_@]。
建议在读代码时,先通读web.xml和struts-config.xml两个文件,从web.xml中了解filter的设置,将所有filter通读一遍,了解总体上做了什么限制,再从struts-config.xml了解action与actionForm、View的对应关系,然后根据这些关系,检查form中的参数有效性验证方法(validate函数)是否做了足够的检查,以及view中是否有基本的html过滤。具体测试case这里不多言,只要找对地方,java的测试case和其他语言也基本都差不多。
Struts1安全特性
参数获取
通常使用J2ee中最常见的Request.getParamter()方法获取用户输入。此方法结合了GET/POST中提交的变量,如果两者有重名,则GET获取的优先,获取的内容非常原始,没有任何改变。不同于PHP中的$_REQUEST变量,Request.getParamter方法并没有包含cookie中获取的变量。
actionForm中获取的变量也是框架通过上述方法获取的,因此虽然看起来是直接使用没有通过函数获取,实际上也是一样。
文件操作
文件下载在struts中并没有做任何限制。在文件上传中,一般是使用Apache-Common-FileUpload的组件。其中也未替代用户做任何限制和检查,需要手工实现扩展名判断、验证等代码。因此如果action中的逻辑代码没有做任何检查,基本可以判断存在问题。
前端防范
一般Struts1使用jsp作为默认的前端引擎,常见的过滤方式与jsp相同,不做赘述。
CSRF防范
Struts1提供了一个生成并验证Token的api(isTokenVaild),在检查是否防范了CSRF的时候,可以查看应用程序是否使用了这个API(也可以查看提交的请求包中是否包含org.apache.struts.taglib.html.TOKEN这个变量)。如果未使用,就可以检查是否在Filter之类的地方做了统一的自己实现的CSRF token验证防范。
Struts1提供的Token值是存储在SESSION中的,因此这里就存在一个bug。由于Token在session中保存的变量名不变,因此如果一个用户打开了多个窗口,只有最后一个窗口设置的token值有效。这里需要多加注意。
其他
此外,Struts1有个特性:一个Action类在程序周期中只有一个实例,常驻内存。因此,在这个类中的属性和变量是共享的,不同用户访问的不同请求都是如此。因此如果程序中的逻辑依靠Action内部变量传送敏感信息,则其他用户也可能改写、读取。
Struts2的白盒安全测试方法
Struts2的安全测试主要方法论与S1一致,也多是通过查看struts.xml和web.xml中的配置,了解url->action->view的关系,以及不同url中涉及到的过滤器、拦截器、验证器(一般在validation.xml和className-validation.xml中配置)的关系。了解了这些,在头脑中有一个清晰的数据流脉络,然后再检查这些控制措施是否做到位,是否有疏漏即可。一般来说,S2的控制大都在拦截器和验证器中进行,常见的有是否登录、文件上传格式检查、参数有效性检查等。S2默认提供了很多拦截器,做了以上功能,这些在下面的安全特性中会提及。
Struts2的安全特性
参数获取
与S1不同,S2中的参数获取虽然也可以通过Request.getParamter()方法,但是很少使用。因为action类中定义过getter/setter方法的变量,如果用户在请求中提交了同名的变量,框架会自动将其值注入到action实例中的对应变量中去,并且会做响应的类型转换。这一切是通过defaultStack中默认的Param拦截器实现的,利用拦截器,在action执行前,将用户传入的数值使用Request.getParamter()获取后并经过解析自动注入。
这一点请一定注意。因为如果action中的内部成员变量之前有一个默认值,一旦用户提交了同名变量,就可以覆盖此值。这样就会直接导致后续依赖此默认值的逻辑发生重大变化。
文件操作
下载文件没有限制,需要自行编写代码控制。
上传文件,S2默认有一个FileUploadInterceptor的拦截器,提供了三种限制方法,都在struts.xml中配置:
allowedTypes(允许文件的mime-type值,可伪造)
maxmumSize(允许文件最大的字节数)
allowedExtensions(允许文件的扩展名)
由于早期S2框架文档的误导,提供的例子程序中只限制了allowedTypes,因此导致多数s2应用程序存在修改http包修改mime-type导致任意上传漏洞。因此在做安全测试的时候,一定要检查是否设置了allowedExtensions作为白名单,而不单单是allowedTypes。另外即使做了扩展名限制,请注意这个可能还存在多扩展名文件的问题。
如果上传没有使用默认的FileUploadInterceptor,请检查程序逻辑中是否对文件上传做了严格的限定。
CSRF防范
S2中也提供了防范重复提交的Token机制。与S1不同的是此次他是使用TokenInterceptor这个拦截器的方式,在action执行前统一进行验证。Token值也是保存在session中,因此同样存在S1框架中Token的bug。
此外,这个Token机制还有个可以被绕过的0day漏洞:由于TokenInterceptor在验证用户提交上来的请求时,先从用户请求中的struts.token.name值作为session中的key,然后比对用户请求中提交的token值是否与session中key对应的值相匹配,如匹配则通过。但是,如果攻击者预先知晓session中已保存的一个值,例如username=admin,则攻击者可以直接将csrf包中的struts.token.name设置为username,然后token值设置为admin,即可轻易绕过csrf token防范。
因此,如果经检查的应用中使用了默认的token拦截器,请提醒开发人员弥补以上不足。如自行实现防重复提交的代码,请仔细检查其逻辑的严密性。
动态方法调用
Struts2中存在一个特性,叫做动态方法调用。当你请求actionName!methodName.action这样的URL时,框架不会调用action中的默认入口execute(),而是会寻找action中名叫methodName的方法,并直接调用。通过这种方式,可能暴露action类中未经过验证的内部接口,也可能绕过一些权限验证。
因此,在测试过程中,如果程序本身没有使用到这个特性,请建议RD在struts.xml中设置一个constant将struts.enable.DynamicMethodInvocation属性设置为false,禁止此特性。
前端模板
S2自带的taglib中的变量输入都经过了自动html转义。一般无问题。但是请注意参数名前带感叹号,则表示没有转义。安全检测时请务必注意。
如果使用其他模板引擎,请根据其他引擎的过滤规则进行检查。
其他
与s1不同,s2中每一个请求对应一个不同的action实例,没有action实例中变量共享的问题。
S2的action中execute方法,根据方法返回的值来决定最后调用哪一个view模板。因此,结合动态方法调用和自动变量注入的特性,如请求actionName!getUsername.action?username=pass。而action中对应“pass”这个返回结果的view又是个包含敏感信息的模板,就可能产生问题。因此在安全测试的过程中需要特别注意。