會有這個後續篇,主要是因為笨鳥對於用來攻擊的 OGNL 語法不甚了解,於是想研究看看,再加上在找相關資料的過程中,看到了這篇文章「Struts2 與 Webwork 遠程命令執行漏洞分析」裏提到,光只過濾「\u0023」仍無法堵住這個漏洞,因此在土法鍊鋼的反覆測試與參考上述文章後,有了些心得可以記錄下來。

因為是土法鍊鋼,所以有些地方笨鳥就只能針對觀察到的現象加以描述,而無法用什麼理論來驗證,這個可能要先說在前頭了。

首先就先從 OGNL 談起好了。(本來想一口氣寫完,不過後來發現我沒那麼有毅力,所以還是分兩篇好了)
由於 OGNL 本身就是一個龐大的東西,因此這裏不會講解整個 OGNL,有興趣的請自行上官網看文件,此處只針對攻擊用的語法「Expression Evaluation」,也就是攻擊語法中的圓括弧用法。

所謂的 Expression Evaluation 指的是當 OGNL 表達式中包含了圓括號,且該圓括號前面並沒有點號時,那麼 OGNL 會試著把圓括號前的表達式評價(Evaluation) 結果作為另一個以圓括號內表達式評價結果作為根物件的表達式來進行評價。圓括號前的表達示評價結果可能會是任何的物件,如果是 AST 物件,OGNL 會假設這是從一個表達式解析而來的,並且簡單的去直譯它。如果不是 AST,那麼 OGNL 會把該物件強制轉成 String 後去解析成 AST 物件再去直譯它。

(原文:If you follow an OGNL expression with a parenthesized expression, without a dot in front of the parentheses,
OGNL will try to treat the result of the first expression as another expression to evaluate, and
will use the result of the parenthesized expression as the root object for that evaluation. The result of the
first expression may be any object; if it is an AST, OGNL assumes it is the parsed form of an expression
and simply interprets it; otherwise, OGNL takes the string value of the object and parses that string to
get the AST to interpret.)

以上的描述實在讓人看得眼花,再加上笨鳥的英文實在菜得很,翻譯可能也有錯誤,所以我們還是直接來看 OGNL 中處理表達式的程式比較快。
以下的程式位在 ognl.ASTEval 類別中的 setValueBody() 方法中。

 
  Object  expr = children[0].getValue(context, target),
          previousRoot = context.getRoot();
  Node    node;

  target = children[1].getValue( context, target );
  node = (expr instanceof Node) ? (Node) expr : 
                                  (Node) Ognl.parseExpression(expr.toString());
  try {
      context.setRoot( target );
      node.setValue( context, target, value );
  } finally {
      context.setRoot( previousRoot );
  }

 

上面的程式中,children[0] 即為圓括號前的表達式,而 children[1] 為圓括號內的表達式。
從第一行我們可以看到,一開始 OGNL 會用目前的 OgnlContext 去解析圓括號前的表達式,取得解析後的物件。之後在第 5 行時才再去解析圓括號內的表達式,然後在第 9 行時把得到的結果放入 OgnlContext 當成根物件。再來回頭看第 6 行,它將圓括號前的表達式解析後的物件拿來判斷看是否為 AST 物件(所有 AST 物件都是實作自 Node 介面),如果不是的話則取得這個物件的字串再來進行解析 (反正就是要得出 AST 物件就是了)。最後的步驟則是第 10 行,以圓括號內表達式解析出來的結果當根物件,再來對圓括前的表達示結果進行解析。

總結上面的結論,用一個比較簡單的流程來表示:
  解析圓括號前的表示式  --> 解析圓括號中的表示式 --> 以圓括號中表示式解析出的結果當根,再來解析圓括號前表示式的結果

接下來用一個非常簡單的例子來解說:
  'length'('ABCDE')

首先這個例子在第上述程式中,'length' 會被放在 children[0],而 'ABCDE' 則是放在 children[1]。一開始第一行的解析結果,得到的是 'length' 這個字串物件,而第 5 行解析出來的結果則是 'ABCDE' 這個字串物件。然後到第 6 行,因為 'length' 這個字串物件不是 AST 物件,因此會再用 Ognl.parseExpression() 來進行解析,最終得到 ognl.ASTProperty 這個 AST 物件。最後再第 9 與第 10 行將 'ABCDE' 當根物件,對內容為 length 的 ASTProperty 物件再做進一步解析,得到的就等同是執行 'ABCDE'.length() 這句指令的結果。

在跑過上面這個例子之後,一定會有疑問說為什麼 length 要設成字串,難道不能寫成 length('ABCDE') 嗎?我們就來看看這個丟下去會變怎樣吧。
首先在第 1 行執行之後,因為一開始預設的根物件沒有 length 這個屬性,因此得出的解析結果為 null,如此到第 6 行的 expr.toString() 時會丟出 NullPointerExcpetion,然後就整個執行不下去了。所以,為了不讓這樣的情況發生,在攻擊語法裏,圓括號前的表達式通常都會是字串。

講到這裏可能又會有一個疑問,就是在攻擊語法裏看到的怎麼都是圓括號。這一樣要從 Expression Evaluation 的規則說起,在規則中,因為圓括號的語法會跟方法呼叫的語法混淆,如下:
  fact(30H)

如果剛好根物件也有一個叫 fact 的方法,那麼 OGNL 就會去執行這個方法,而不是用 Expression Evaluation 的方式來解析,因此為了強制讓 OGNL 使用 Expression Evaluation 來解析,因此將前面那個表達式也用圓括號括起來即可,如下:
  (fact)(30H)

這篇的最後再來個總結一下攻擊語法的「眉眉角角」
1. 圓括號一定是兩兩成對的,為的是要使用 Expression Evaluation 規則的語法。
2. 一般而言,前面圓括號內的表達式會用字串來表示,為的是避過上面講到的解析出 null 的情況。
3. 由於前一個圓括內是字串,所以到最後的執行順序會是先執行後面圓括號內的表達式之後,再執行前面圓括內的表達式。
    至於前後圓括號內表達式的結果再進行一次解析則不重要了。

參考資料:
OGNL 官方文件之 Expression Evaluation 章節
http://hi.baidu.com/%C4%AA%CE%D4%B6%F9de%BB%F0%D1%E6/blog/item/79132926ad38dd0e918f9d9c.html
http://blog.csdn.net/csdn1234/archive/2008/11/26/3378135.aspx

arrow
arrow
    全站熱搜

    大笨鳥 發表在 痞客邦 留言(0) 人氣()