1. 程式人生 > >一次mybatis中ognl引發的bug排查

一次mybatis中ognl引發的bug排查

現象

  專案組一妹子程式設計師求助,說mybatis有bug,有一個值明明設定的是A.prop1=XXX,但是存到資料庫裡面卻會自動變成A.prop1=true,嘗試了各種調整也找不原因,都快急瘋了!我以前確實沒有研究過mybatis原始碼,本著專(ba)研(mei)問(zhuang)題(b)的精神,決定通過debug對mybais一探究竟。

定位

  debugDao的入口類便是org.apache.ibatis.binding.MapperProxy ,可以看到它實現了InvocationHandler,很明顯mybatis使用了jdk的動態代理,參見檢視我以前關於動態代理類的培訓ppt如下,那麼它一定有地方使用了newProxyInstance生成代理類,果然在MapperProxyFactory中就可以找到對應的方法,只不過這次動態代理是對純介面進行代理,而不是對實現類代理(當然滿足動態代理中被代理類需要實現介面的要求了!)圖片描述

  進入到MapperMethod中的execute方法,可以看到mybatis對select、update、delete、insert有不同的分支處理:圖片描述  進入到出問題的update方法中,可以定位到sqlSession.update執行時修改了傳入的引數值,把XXX改成了true,這個update方法到底藏了什麼玄機?繼續進入,發現sqlSession也是spring sessionTemplate生成的一個動態代理,主要是增加獲取連結和事物操作,通過代理層的操作,進入Mybatis的DefaultSqlSession中,接下來就是Mybatis預編譯要動態生成sql語句了,在動態生成語句時終於最終定位到了罪魁禍首ifSqlNode.apply方法(整體呼叫棧見下圖)!圖片描述

解決

  mybatis中會更具mapper檔案生成一個SqlNodeTree,然後根據入參的資料有選擇的生成最終的SQL,例如mapper檔案中支援<if test>語法,if test的條件動態決定update或者where的sql語句是否存在,例如傳過來的A物件中沒有prop1屬性,那麼if test A.prop1!=null判斷一下,如果沒有的話,最終的sql就不包含update prop1=?,達到靈活和提高效能的目的。這個ifSqlNode就是mapper if test語法的具體Node,但是這個node應該只是判斷是否存在這個條件啊!怎麼會改值的?  進入到具體的判斷方法內部,發現了原來這邊判斷if test是使用的Ognl表示式引擎啊,Ognl是一個功能非常強大的JAVA表示式引擎,但是由於過於強大了,導致使用它的Struts2漏洞漫天飛,你Http請求中傳參'Runtime.getRuntime("shutdown")',它真的就給你執行關機了你敢信!!。

/*    */   public boolean evaluateBoolean(String expression, Object parameterObject)
/*    */   {
/* 29 */     Object value = OgnlCache.getValue(expression, parameterObject);
/* 30 */     if (value instanceof Boolean) return ((Boolean)value).booleanValue();
/* 31 */     if (value instanceof Number) return (!(new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO)));
/* 32 */     return (value != null);
/*    */   }

  敏銳的我對著expression看了一遍又一遍,突然,我眼花了麼?

A.prop1=!null !
A.prop1=!null !!
A.prop1=!null !!!

  你妹啊! "!="寫成了"=!"!ognl又一次立功了,在需要它判斷的時候,它忠實的執行了賦值(怪OGNL不怪妹子是幾個意思?),修改了mapper檔案中的錯誤,終於恢復了正常!

擴充套件

  上網搜尋下看看有沒有犯同樣錯誤的同學,沒想到還真有人在mybatis中這樣玩OGNL的,根據if test判斷的結果給table名賦值,無疑是提供了一種嶄(hun)新(luan)的思路!!  給你們這些挖坑的跪了!

<if test="@[email protected](_parameter) 
            or dynamicTableName == null 
            or dynamicTableName == ''">
            select from ${dynamicTableName }