天天看點

Struts2(s2-016)遠端代碼執行漏洞詳細代碼分析

  之前 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);
            ActionMapping mapping = prepare.findActionMapping(request, response, true);
            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);
    }
}
           

通過第 12 行擷取目前通路的 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 {
           mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
            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;
}
           

跟進至第 5 行,這句代碼通過調用 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);
    handleSpecialParameters(request, mapping);
    return parseActionName(mapping);
}
           

第 11 行代碼用于處理特殊參數,本漏洞的觸發點 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) {
                parameterAction.execute(key, mapping);
                uniqueParameters.add(key);
                break;
            }
        }
    }
}
           

繼續跟着流程走,第 15 行代碼之前的操作是從使用者傳入的參數中提取特殊參數,第 15 行則是針對這個參數進行處理的地方。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;
    }
}
           

差不多就這些,應該夠詳細了。 

本文已經過重新排版。

轉載自:http://www.2cto.com/Article/201307/230083.html

繼續閱讀