1. 程式人生 > 實用技巧 >Struts2一個[安全問題]的分析報告

Struts2一個[安全問題]的分析報告

一:起因

最近公司程式碼被掃出有一個xss漏洞,檢查之後,發現大致是這樣一個頁面:

<!-- lang: java -->
public class DemoAction extends ActionSupport {

    private int id;

        @Override
        public String execute() throws Exception {
                return "success";
        }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

模板用的是freemarker,大致是這樣子:

<!-- lang: html -->
<html class="G_N">
<head>


</head>
<body id="top">

${id}
</body>
</html>

如果我們訪問url/demo?id=<script>alert("xss!")</script>,會發現id的引數原封不動的列印到了頁面上,就會出現反射型xss!

二:問題流程

分析漏洞原因前,先要稍微看一下struts結構(自己畫的,可能不嚴謹):

OGNL是底層的表示式引擎,是聯絡起上下文和模板輸出的橋樑。

XWork是個什麼東西呢?它可以理解為一個請求-響應模式的通用框架(不僅僅侷限於Web),這個Action就是一個命令。而struts2可以說是XWork在web領域的一個特定實現。

XWork包括Action/Interceptor/Result幾個大部分,還有用於執行流程的ActionProxy和ActionInvoker,以及處理資料的ActionContext和ValueStack。

所以引數的轉換和注入是在XWork裡進行。Struts的主要執行流程在DefaultActionInvocation裡。大致解釋一下流程:

當Struts捕獲到引數時,會交由ognl進行引數轉換。我們都知道Struts是通過setter方法進行的引數注入,更進一步的,它是通過ognl表示式來查詢方法,並進行屬性注入,程式碼在OgnlRuntime.setProperty

裡。

那麼如果注入不成功呢?OgnlRuntime會丟擲MethodFailedException,然後ConversionErrorInterceptor會將原始引數注入到invocation.getStack()中去,而最終freemarker會讀取這個原始資料,並列印到頁面上(FreemarkerResult)!這個時候,無論欄位最終值是什麼都不重要了,因為在ognl的Stack裡,它已經用原始值給override了!插一句,其實這個值貌似是為了debug用的,會返回名字為"input"的result,這樣會返回找不到方法的404頁面,但是公司使用的貌似不太好用,仍然會正常返回!

<!-- lang: java -->
if (fakie != null) {
    // if there were some errors, put the original (fake) values in place right before the result
    stack.getContext().put(ORIGINAL_PROPERTY_OVERRIDE, fakie);
    invocation.addPreResultListener(new PreResultListener() {
        public void beforeResult(ActionInvocation invocation, String resultCode) {
            Map<Object, Object> fakie = (Map<Object, Object>) invocation.getInvocationContext().get(ORIGINAL_PROPERTY_OVERRIDE);

            if (fakie != null) {
            	   //注入
                invocation.getStack().setExprOverrides(fakie);
            }
        }
    });
}

三:求解

那麼怎麼解決這個問題呢?本著回饋社群的精神,給Struts2官方發了一封郵件,並上傳了demo到https://github.com/code4craft/xssdemo。然後過了一天有個叫Lukasz Lenart的大叔程式設計師回覆我了,老外還是很客氣的,回答也很及時(算上時差)。首先確認了問題的存在,然後說這不是一個bug,你可以用${id?html}來進行輸出轉義。我覺得這個解決方案雖然管用,但是是比較反直觀的,因為一般人都會直覺上因為這裡只是讀取Action中的getter取值,既然是基本型別,哪會還需要轉義?本來想噴回去的,又搜了一下這個Lukasz Lenart的來歷,然後出來這麼個:

被lead亮瞎了!親力親為,這才是開源專案的氛圍嘛!

不過呢,即使大神&作者都發話了,我還是希望有框架內的方案,或許去掉ConversionErrorInterceptor是個不錯的主意?怎麼自定義呢?貌似沒辦法自定義單個interceptor,但是可以定義一個stack。怎麼定義stack?看看struts-core包裡的struts-default.xml就知道了!(PS:自定義interceptor是Struts裡很有用的技巧,大家不妨自己研究一下)。修改過的程式碼:https://github.com/code4craft/xssdemo/blob/master/src/main/resources/config/struts/struts.xml

本文中的解決方案存在風險,請使用之前先進行測試!

參考資料:教育推廣的方法有哪些