getvalue参数计数不匹配_Struts2漏洞系列之「S2-032」特殊参数名前缀 `method:` 导致的OGNL表达式执行

Smi1e@Pentes7eam

漏洞信息:https://cwiki.apache.org/confluence/display/WW/S2-007

当启用动态方法调用时,由于特殊参数名前缀 method:的处理方法中未对参数进行过滤,而在执行Action时会对其进行OGNL表达式解析,从而导致OGNL表达式执行。

漏洞复现

f46c649b12f21228a334c0d3896f639a.png

影响范围

Struts 2.3.20 – 2.3.28(2.3.20.3和2.3.24.3除外)

漏洞分析

首先该漏洞需要在 struts.xml中将DynamicMethodInvocation设置为true才能利用成功,2.3.15.1之前默认为true,2.3.15.2开始默认为false。

"struts.enable.DynamicMethodInvocation"value="true"/>

该选项是指是否开启 Dynamic Method Invocation动态方法调用。动态方法调用是指:表单元素的action不直接等于某个Action的名字,而是以感叹号后加方法名来指定对应的动作名,如login!test.action。当指定调用某一方法来处理请求时,就不会走默认执行处理请求的execute方法。

我们知道官方为了修复 S2-016 特殊参数名前缀导致的OGNL表达式执行,删除了 DefaultActionMapper中对特殊参数名前缀redirect:redirectAction的处理,只留下了action:method:

DefaultActionMapper处理method:前缀的地方下断点。因为我们开启了DynamicMethodInvocation,所以这里会进入if条件中把我们的参数名method:及其后面的字符串赋值给ActionMapping对象的method属性。

4bab7d3c6d8124cfe08215177ef2571b.png

ActionMapping对象存储有Action的配置信息,它接着会做为findActionMapping方法的结果返回给StrutsPrepareAndExecuteFilte

e56d0e38b7ef349e5add15fce903152c.png

接着会去调用 this.execute.executeAction(request, response, mapping);,根据ActionMapping对象的信息调用对应的Action

public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {

this.dispatcher.serviceAction(request, response, mapping);

}

它调用了DispatcherserviceAction方法,首先使用requestresponsemapping对象封装了一个ContextMap对象,并把OgnlValueStackput进去。然后从mapping中获取 action 对应的命名空间namespace、请求的action名name、请求方法method。接着根据前面获取的信息创建用户自定义Action的ActionProxy对象。

public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {

Map extraContext = this.createContextMap(request, response, mapping);

ValueStack stack = (ValueStack)request.getAttribute("struts.valueStack");

boolean Stack = stack == ;

if (Stack) {

ActionContext ctx = ActionContext.getContext;

if (ctx != ) {

stack = ctx.getValueStack;

}

}

if (stack != ) {

extraContext.put("com.opensymphony.xwork2.util.ValueStack.ValueStack", this.valueStackFactory.createValueStack(stack));

}

String timerKey = "Handling request from Dispatcher";

try {

UtilTimerStack.push(timerKey);

String namespace = mapping.getNamespace;

String name = mapping.getName;

String method = mapping.getMethod;

ActionProxy proxy = ((ActionProxyFactory)this.getContainer.getInstance(ActionProxyFactory.class)).createActionProxy(namespace, name, method, extraContext, true, false);

request.setAttribute("struts.valueStack", proxy.getInvocation.getStack);

if (mapping.getResult != ) {

Result result = mapping.getResult;

result.execute(proxy.getInvocation);

} else {

proxy.execute;

}

......

}

跟进DefaultActionProxyFactorycreateActionProxy方法,它会实例化一个DefaultActionInvocation对象,后面会通过它的invoke来正式执行一系列的拦截器以及Action。最后又调用this.createActionProxy,继续跟进。

4478e2ef4bc7ca5c21dcb60c0b142ded.png

然后会实例化 StrutsActionProxy对象并返回,继续跟进其构造方法。

public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {

StrutsActionProxy proxy = new StrutsActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);

this.container.inject(proxy);

proxy.prepare;

return proxy;

}

这里会调用父类 DefaultActionProxy的构造方法,继续跟进

public StrutsActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {

super(inv, namespace, actionName, methodName, executeResult, cleanupContext);

}

DefaultActionProxy的构造方法会把传入的参数赋值给其对应的属性,在赋值methodName时会调用StringEscapeUtils.escapeHtml4StringEscapeUtils.escapeEcmaScript进行过滤。

escapeEcmaScript(String input) / unescapeEcmaScript(String input):转义/反转义js脚本

escapeHtml4(String input) / unescapeHtml4(String input):转义/反转义html脚本

因此我们的payload中不能出现 < > " ' &等字符串,因此对于字符串参数的传入需要使用#parameters.参数名[0]OgnlValueStack中获取。

d257f0ae657a19eb28302b5df25d41d5.png

最终 StrutsActionProxy对象返回并执行其execute方法。然后执行this.invocation.invoke;this.invocation在上面已经知道是DefaultActionInvocation对象,跟进。

public String execute throws Exception {

ActionContext previous = ActionContext.getContext;

ActionContext.setContext(this.invocation.getInvocationContext);

String var2;

try {

var2 = this.invocation.invoke;

} finally {

if (this.cleanupContext) {

ActionContext.setContext(previous);

}

}

return var2;

}

invoke方法会执行一系列的拦截器以及Action,直接跳过所有拦截器的执行步入执行Action的地方,跟进。

19376af6481c1e01d58f822dfe3b70b7.png

继续跟进

public String invokeActionOnly throws Exception {

return this.invokeAction(this.getAction, this.proxy.getConfig);

}

可以看到这里有一个对 methodName进行getValue的操作,而我们的payload后会拼接一个,因此我们的payload后面要加一个1?#xx:#request.toString来防止报错。

8b0399837cf945be044ff3d034009be8.png

最终执行我们的payload

4f7a702f8af990aea0e78fa6344f477c.png

为什么低版本不行呢?低版本的 com.opensymphony.xwork2.DefaultActionInvocation.invokeAction方法中不会对methodName值做 OGNL 表达式计解析。

b84c8d88a684f1e627a984371879bae2.png

另外Struts 2.3.20 的配置文件中新增加了黑名单配置 struts.excludedClassesexcludedPackageNamePatterns,用来严格验证排除一些不安全的对象类型。

a1c17934add8751f320f9ed33a378204.png fc9acac59962516638780254d6122f36.png

因此以前的payload中通过反射修改 SecurityMemberAccess对象的allowStaticMethodAccess属性和直接调用构造函数已经无法使用了。不过_memberAccess仍然可以访问,而且还有一个静态对象DefaultMemberAccess也可以通过ognl.OgnlContext的静态属性DEFAULT_MEMBER_ACCESS访问,而DefaultMemberAccessSecurityMemberAccess的父类。所以这里可以使用@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS覆盖掉#_memberAccess

1317d9ed3a8a4656585b860d8e30fe91.png

为什么覆盖掉 #_memberAccess就能绕过沙盒呢?

这是因为 OgnlContext中的_memberAccessOgnlValueStack中的securityMemberAccess其实是同一个SecurityMemberAccess类的实例。

首先在初始化 OgnlValueStack对象的时候会调用setRoot,而其中会创建一个SecurityMemberAccess对象。

322a07c0e497337a3a4038b89d802697.png

然后调用 createDefaultContext时会把该SecurityMemberAccess对象赋值给OgnlContext_memberAccess属性。

0ebf7a1e6d8d2180599655de7e2e47a7.png f1f9cea2a08f914a07a1b7cc59ca3a48.png

所以覆盖掉 #_memberAccess就相当于覆盖掉了OgnlValueStack中的securityMemberAccess

漏洞修复

特殊参数名前缀 method:的处理方法中,在对ActionMapping对象的method属性赋值前使用正则[a-zA-Z0-9._!/-]*进行过滤。

a23a14e7f0f3354d21c4594c684cf373.png 606f50be32d0c052a442d4a09f0a876a.png
https://www.easyaq.com
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值