Struts(S-16/17)远程命令执行漏洞分析

2013-07-21 17:29:13 11 3092


之前winwin已经发过这个漏洞的分析文章,分析的很到位,不过有几个点有些问题,所以我在这里把自己的分析内容发出来,供各位参考。

      这个漏洞的数据污染点和触发点,和其他的Struts不一样,所以本篇分析将从Struts执行流程中剖析此漏洞。在Struts2.3以后,官方将原有的起始过滤器换为:org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.class
      所以我们此次跟踪的第一个断点便下在这个类的doFilter方法中,代码如下:
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        try {
            prepare.setEncodingAndLocale(request, response);
            prepare.createActionContext(request, response);
            prepare.assignDispatcherToThread();
            if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
                chain.doFilter(request, response);
            } else {
                request = prepare.wrapRequest(request);
                [color=Red]ActionMapping mapping = prepare.findActionMapping(request, response, true);[/color]
                if (mapping == null) {
                    boolean handled = execute.executeStaticResourceRequest(request, response);
                    if (!handled) {
                        chain.doFilter(request, response);
                    }
                } else {
                    execute.executeAction(request, response, mapping);
                }
            }
        } finally {
            prepare.cleanupRequest(request);
        }
    }
通过红色代码获取当前访问的action映射,下面我们来看PrepareOperations类中的findActionMapping方法:
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
        ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
        if (mapping == null || forceLookup) {
            try {
                [color=Red]mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());[/color]
                if (mapping != null) {
                    request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
                }
            } catch (Exception ex) {
                dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
            }
        }

        return mapping;
    }
跟进至红色代码处,这句代码通过调用DefaultActionMapper类的getMapping方法来获取action映射,我们继续跟进到这个方法:
public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
        ActionMapping mapping = new ActionMapping();
        String uri = getUri(request);

        int indexOfSemicolon = uri.indexOf(";");
        uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;

        uri = dropExtension(uri, mapping);
        if (uri == null) {
            return null;
        }

        parseNameAndNamespace(uri, mapping, configManager);
[color=Red]        handleSpecialParameters(request, mapping);[/color]
        return parseActionName(mapping);
    }
红色代码部分是用于处理特殊参数,本漏洞的触发点redirect就属于它处理的内容,下面我们来看下它的代码:
public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) {
        // handle special parameter prefixes.
        Set<String> uniqueParameters = new HashSet<String>();
        Map parameterMap = request.getParameterMap();
        for (Object o : parameterMap.keySet()) {
            String key = (String) o;

            // Strip off the image button location info, if found
            if (key.endsWith(".x") || key.endsWith(".y")) {
                key = key.substring(0, key.length() - 2);
            }

            // Ensure a parameter doesn't get processed twice
            if (!uniqueParameters.contains(key)) {
                ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key);
                if (parameterAction != null) {
[color=Red]                    parameterAction.execute(key, mapping);[/color]
                    uniqueParameters.add(key);
                    break;
                }
            }
        }
继续跟着流程走,在红色代码段之前的操作是从用户传入的参数中提取特殊参数,红色代码则是针对这个参数进行处理的地方。DefaultActionMapper类针对四种不同的特殊参数,分别定义了不同的execute方法,这里我们只看处理redirect参数的方法,看下它的代码:
public void execute(String key, ActionMapping mapping) {
                        ServletRedirectResult redirect = new ServletRedirectResult();
                        container.inject(redirect);
                        redirect.setLocation(key.substring(REDIRECT_PREFIX
                                .length()));
                        mapping.setResult(redirect);
                    }
代码比较简单,就是新建一个ServletRedirectResult对象,将用户输入的参数值插入到它的Location属性中,最后将这个对象覆盖掉映射的result属性。
      问题的关键点就在这个result属性中,Struts在处理action后,返回的内容都是依赖result属性中的内容。平常这个属性都是通过配置文件来设置的,但是在这里,用户可以通过redirect来控制这个属性的内容。而且,用来解析返回内容的conditionalParse方法使用了translateVariables方法处理参数,这个方法会将其参数作为Ognl表达式执行,从而导致此漏洞的触发。conditionalParse方法代码如下:
    protected String conditionalParse(String param, ActionInvocation invocation) {
        if (parse && param != null && invocation != null) {
            return TextParseUtil.translateVariables(param, invocation.getStack(),
                    new TextParseUtil.ParsedValueEvaluator() {
                        public Object evaluate(String parsedValue) {
                            if (encode) {
                                if (parsedValue != null) {
                                    try {
                                        // use UTF-8 as this is the recommended encoding by W3C to
                                        // avoid incompatibilities.
                                        return URLEncoder.encode(parsedValue, "UTF-8");
                                    }
                                    catch(UnsupportedEncodingException e) {
                                        if (LOG.isWarnEnabled()) {
                                            LOG.warn("error while trying to encode ["+parsedValue+"]", e);
                                        }
                                    }
                                }
                            }
                            return parsedValue;
                        }
            });
        } else {
            return param;
        }
    }
差不多就这些,应该够详细了。利用方法就不说了,铺天盖地都是,实在不行,随便搞个利用工具抓下包。想看原理的话,90sec上倒是有个人分析的不错,推荐一看。

关于作者

唐门三少17篇文章105篇回复

评论11次

要评论?请先  登录  或  注册