最近终于下定决心开始研究一下java web的安全。相比起php web, 调试java web往往会出现各种各样的环境问题,其中一个原因就是开发java web应用会大量引用已存在的各种成熟组件,如同建筑一栋高楼,如果其中一根底层支柱出了问题,整栋大楼就构建不起来。大部分想要去做安全研究的同学其实都没有太多的开发经验,因此在环境调试上面的问题会变得非常棘手。

这就成为了不少想要入门java web安全的同学的一道高门槛,比如你兴致勃勃地拿到了一个java web应用项目代码,开始构建应用的时候发现爆了一个错误,解决这个错误可能就会耽搁你一下午,等到你解决完了这个问题,你最初的激情可能已经磨灭了一大半了。其实如果最终能够解决问题也还算不错,更糟的情况可能是你至始至终都没有解决这个问题。

以上其实就是我个人在这段时间研究java web时的一些感想。废话不多说了,下面我会以一个java web安全初学者的视角,比较详细地记录自己调试struts2-016的过程。

0x01 环境搭建

Struts是apache基金会jakarta项目组的一个开源项目,采用MVC模式,能够很好的帮助我们提高开发web项目的效率。Struts主要采用了servlet和jsp技术来实现,把servlet、jsp、标签库等技术整合到整个框架中。

我下载的struts2源码版本是 struts-2.3.14.3-src.zip(不带jar包),目录结构如下。
E1B40911-87AD-4A05-85E5-F70834958AC6.png

我常用的ide是jetbrain idea,在示例web应用中选择了代码最简洁的struts-blank应用。首先open project,选择apps/blank文件夹,然后等待maven下载完依赖的jar包。
依次打开project structure ,选择library,如下图所示关联struts2-core 和 xwork-core两个包的源码,源码分别在core目录和xwork-core目录。
14B97AB1-CC0C-40B5-A2B2-F5DF175EF7BC.png

然后配置下运行环境,这个就不多介绍了。最终成功运行如下。
FD0A85EA-EA25-45B3-93EF-702A656154B3.png

用以下poc打下

redirect:$%7b%23w%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter(),%23w.println('struts2-016'),%23w.flush(),%23w.close()%7d

1E87F568-061C-4940-A87C-1F3BDE48994B.png

0x02 动态调试

StrutsPrepareAndExecuteFilter 是struts2框架的默认filter,所有的filter都需要在web.xml中进行配置。
D6419166-1F8E-4A11-AA7C-A182F87BDC29.png

StrutsPrepareAndExecuteFilter 作用是过滤所有的action请求并进行映射,简单的理解就是路由分发。比如用户请求HelloWorld.action时,就会根据相应的路由规则映射到controller层的HelloWorld类,经过处理后再渲染到view层/example/HelloWorld.jsp。相应的映射规则在struts.xml中配置。

struts.xml
EA15D4BF-5649-48A4-9A0C-9EB67261ADAC.png

example.xml
9026EE95-BDC7-4256-AFB3-44338B81D5AF.png

简单的了解了StrutsPrepareAndExecuteFilter的作用后,我们开始在第77行下一个断点进行调试。
BCED8879-5026-49B1-B8D5-BB8B6F331323.png

上图中是doFilter的代码,理解这个方法其实就是理解整个struts2框架路由映射的关键。像struts2这样知名的开源框架,其命名必然是有道理的,有的时候我们可以根据方法名来直接判断这个方法的大致作用(这样可以略过一些于漏洞本身无关紧要的方法)。
比如上图77行的setEncodingAndLocale(),字面意思就是设置编码和场景,其作用就是设置编码和初始化环境。
78行的createActionContext(),即创建action的上下文。ActionContext正如其名,是Action执行的上下文,它包含了request、session、application、action、attr、parameters等属性。
83行这里先略过。17年三月份爆发的struts2 045漏洞就与它有关。
84行findActionMapping()。寻找action的映射,按f7跟进函数中。
EB1FE903-E855-439B-A775-0E1EBC2BDE48.png

再跟进getMapping(),可以看到一个handleSpecialParameters()的调用。函数名的字面意思为“处理特殊的参数”,而特殊的参数就是我们poc里面的redirect:${xxxxx}
B1244EA6-ECDE-4116-ABD1-2819CEB36A18.png

f7跟进handleSpecialParameters()。首先提取出了key redirect:${xxxxx},然后把key作为参数之一调用了parameterAction.execute()。
4EB4E5B8-844B-4C47-A9B1-48913717FEF0.png

继续f7,进入了第一个箭头所指的代码段。提取出了ognl表达式${xxxxx}并赋值给了redirect的location属性。
66C3372E-58A5-43A4-A5F7-0A151A436D06.png

至此。我们已经分析完了 struts2 中特殊参数redirect的作用。如http://localhost:8081/example/HelloWorld.action?redirect:http://www.baidu.com就会把http://www.baidu.com赋值为redirect的location属性并302跳转到百度。

那么什么时候最终执行了ognl表达式呢?我们跳出到最开始的doFilter()里面。。
E0D46F01-209F-4E12-87C1-C48FBD5B0AAE.png

跟进91行,executeAction()。这里没什么好说的。
63D348DD-CC4C-48C2-8876-E67CCE99C58D.png

继续f7,进入了Dispatcher类的serviceAction()。前面已经完成了action的映射(正常情况下是HelloWorld.action->HelloWorld.java,目前是要处理redirect),serviceAction()的544行的作用就是执行映射后的逻辑代码(正常情况下执行HelloWorld.java里的具体逻辑代码,不过这里是执行redirect)。
799DC750-B201-466A-80C9-80B8E22E8984.png

跟进execute()。后面的逻辑都比较简单,按顺序一路跟进。
FB05878E-7E50-4B3E-89EC-73E9B44A1285.png
E4C9B23E-1FEA-4A76-B091-CDDCB175A15A.png

conditionalParse()。包含了ognl表达式的location被作为了conditionalParse()的参数,并最终在translateVariables()中执行了ongl表达式。
4A46FBB8-92C7-449C-AE91-6A32C332C85C.png
87BBEA98-F453-4EA0-A5B9-A5C92A9E4176.png
14473BF2-ACCA-47D8-AFC9-274EE8F301C8.png

0x03 思考

为什么会在conditionalParse()里面解析ognl?开发者是出于一个怎样的意图?

参考资料

攻击JavaWeb应用[5]-MVC安全