摘要:原因就是傳入的和原有的單引號,正好組成了,而后面恒等于,所以等于對這個庫執行了查所有的操作。類比的執行流程和原有的我們使用的方法就是。可以理解為就是用來解析定制的符號的語句。后續的流程,就和正常的流程一致了。
前言
在JDBC中,主要使用的是兩種語句,一種是支持參數化和預編譯的PrepareStatement,能夠支持原生的Sql,也支持設置占位符的方式,參數化輸入的參數,防止Sql注入,一種是支持原生Sql的Statement,有Sql注入的風險。
在使用Mybatis進行開發過程中,隱藏了底層具體使用哪一種語句的細節,我們通過使用#和$告訴Mybatis,我們實際上進行的是怎么樣的操作,需要對語句進行參數化還是說直接保持原生狀態就好。
今天我們主要看一下使用兩種符號使用時系統應對Sql注入的表現和Mybatis在內部是如何對他們處理的源碼分析。
和$在應對Sql注入上的區別表現利用現有應用程序,將(惡意的)SQL命令注入到后臺數據庫引擎執行的能力,它可以通過在Web表單中輸入(惡意)SQL語句得到一個存在安全漏洞的網站上的數據庫,而不是按照設計者意圖去執行SQL語句。
比如說根據學生姓名查學生信息,會傳入一個name的參數,假設學生姓名是方方,那么Sql就是
SELECT id,name,age FROM student WHERE name = "方方";
在沒有做防Sql注入的時候,我們的Sql語句可能是這么寫的
正常情況下查出姓名符合方方的學生信息。
但如果我們對傳入的姓名參數做一些更改,比如改成anything" OR "x"="x,那么拼接而成的Sql就變成了
SELECT id,name,age FROM student WHERE name = "anything" OR "x"="x"
庫里面所有的學生信息都被拉了出來,是不是很可怕。原因就是傳入的anything" OR "x"="x和原有的單引號,正好組成了 "anything" OR "x"="x",而OR后面恒等于1,所以等于對這個庫執行了查所有的操作。
防范Sql注入的話,就是要把整個anything" OR "x"="x中的單引號作為參數的一部分,而不是和Sql中的單引號進行拼接
使用了#即可在Mybatis中對參數進行轉義
我們看一下發送到數據庫端的Sql語句長什么樣子。
SELECT id,name,age FROM student WHERE name = "anything" OR "x"="x"
從上述代碼中我們可以看到參數中的所有單引號統統被轉移了,這都是JDBC中PrepareStatement的功勞,如果在數據庫服務端開啟了預編譯,則是服務端來做了這件事情。
具體可以看我之前寫的這篇: JDBC與Mysql的那些事,里面解釋了為何PrepareStatement能做到這件事情。
源碼在以前的文章中,我們說明過Mybatis的執行流程主要部件,SqlSession 提供給用戶操作的Api,Executor 具體執行對數據庫的操作,但其實在Executor內部還會再委托給StatementHandler這個接口。
這個Handler的實現類就是代表了JDBC中的操作語句,CallableStatementHandler、PrepareStatementHandler和SimpleStatementHandler就會代表對JDBC中的CallableStatement,PrepareStatement和Statement,這些handler的內部就會調用JDBC中的相關Statement。
類比Mybatis的執行流程和JDBC原有的我們使用的方法就是。
Mybatis: Sqlsession -> Executor -> StatementHandler -> ResultHandler
JDBC: Connection -> Statement -> Result
因此我們可以知道對JDBC語句的操作都會在StatementHandler內部。
在PrepareStatementHandler中會使用paramterize對Statement進行參數化,在其中他會委托給DefualtParameterHandler進行操作。我們通過兩種不同的語句,看一下,Debug下這段代碼的不同。
首先是使用$符號,它是會直接在Sql中進行拼接的,從下圖可知,在進行參數化的時候,Sql語句已經被拼接完成了,見originSql。
進入DefualtParameterHandler內部,如下圖可知,我們看到,這兒boundSql的ParameterMappings不存在,所以不用執行第二個紅框處,設置對應占位符的操作。
然后,我們看一下當使用#的時候,同樣的代碼,會得到什么樣的處理結果。從下圖可知,當使用#的時候,原有的#{value}被替換成了?號,也就是我們熟知的JDBC中的占位符。
再進入DefualtParameterHandler的時候, 此時會有ParameterMappings,value -> anything" OR "x"="x",找到合適的TypeHandler塞入PrepareStatement中。
**從上文的分析中,我們得到的就是,當使用的時候,的時候,{value},是直接被替換為了對應的值,沒有參數映射,不會進行設置占位符的操作,當使用#的時候,#{}會被替換為?號,有參數映射,會在DefaultParameterHandler中進行設置占位符的操作。
問題
1 為什么默認使用的語句是PrepareStatementHandler
2 和#是什么時候被替換的,為什么對應的BoundSql,$時沒有映射,#有映射。
帶著這兩個問題我們來看一下,Mybatis的初始化階段,為節省篇幅,僅列出大致路徑,和關鍵代碼。
Mybatis是通過SqlSessionFactory build出來的,會解析映射文件,大致路徑就是
SqlSessionFactoryBuilder -> XmlConfigBuilder->XMLMapperBuilder->XMLStatementBuilder。
在XMLStatementBuilder的parseStatementNode負責了生成MappedStatement,首先回答第一個問題。當你不指定statementType時,Mybatis默認使用的就是PrepareStatementHandler,這里的StatementType,在后續流程中使用RoutingStatementHandler選擇使用哪一個StatementHandler。
然后繼續看第二個問題,$和#是怎么被替換的。
在之前我們提到了,BoundSql中包含了Sql主體,同時其中的參數映射決定了后續是否要進行參數化,在$和#時,表現是不同的。
BoudSql來自于MappedStatement,在MappedStatement中,獲取BoundSql的任務會委托給SqlSource接口。所以我們接下來主要看SqlSource是如何生成的。
XMLLandDriver可以理解為就是用來解析Mybatis定制的XML符號的語句。他會把具體解析符號的職責交給XMLScriptBuilder的parseScriptNode方法。
parseDynamicTags中會把語句用TextSql包裝起來,然后使用isDynamic方法,在方法中使用GerenericTokenParser判斷是否是動態語句。如果其中包含$,就是動態的,如果是#就不是動態的,使用的Handler是DrynamicCheckerTokenParser。
在進入parse方法后,主要看以下這一段。
這里會使用TokenHandler不同的實現類,對表達式進行進一步的處理,這里是對Sql自后的完善,在判斷isDynamic中,使用的是DrynamicCheckerTokenParser,一個最簡單的實現。
parse完成后,如果isDynamic是true的話,就是動態語句,使用DynamicSqlSource。
如果是非動態的話,其實一般就是指使用了#的語句,使用RawSqlSource,在其中,還會進一步解析。
從下圖中可以看到,這個TokenParser這回使用的是#{},而且使用的是ParameterMappingTokenHandler。
ParameterMappingTokenHandler的handlerToken方法中,完成了添加參數映射和替換#{value}為?的職責。
從以上我們可以知道,使用#在初始化階段,會被替換成?號,同時生成參數映射,而使用$在初始化階段,沒有什么特別的地方,僅僅做了一個是否動態語句的判斷。
在初始化完畢后,我們進入getBoundSql方法,看一下DynamicSqlSource和StaticSource在此刻做了什么,首先是DynamicSqlSource。
在其中,首先會生成一個DynamicContext,主要就是 生成bindings,一個是 "_parameter" -> "anything" OR "x"="x",一個是"_databaseId" -> "null"
然后使用了apply方法,我理解這里是要去做替換了。具體還是使用${}去判斷,和上文一致,只不過這里使用的是BindingTokenParser。
看一下BindingTokenParser的HandleToken方法。
上述代碼的效果,就是會使用Ognl,使用value在Bindings中,找對應的值,最后返回,拼接在Sql中,這也就是為什么會有Sql注入風險的原因。使用value是因為Ognl去找的時候,就會使用value這個默認值,所以需要在bindings額外加入這么一個鍵值對,有興趣可以繼續往下看ONGL相關的東西。
接下來是生成SqlSource,使用的是SqlSourceBuilder的parse方法。
在前文介紹過,在這個parse方法里,是用#{}來判斷的,所以走不到ParameterMappingTokenHandler的handlerToken方法,也就無法添加參數映射了,這個直接返回一個StaticSqlSource,這也解釋了為什么使用$時,參數映射為空。
再接下去就是獲取BoundSql,使用的是StaticSqlSource,直接根據參數,實例化了一個,參數映射為空。
當使用#的時候,使用的就是StaticSqlSource,直接實例化,因為參數映射在之前初始化的階段,也生成好了,所以很簡單的一個流程。
后續的流程,就和Mybatis正常的流程一致了。
總結本文主要剖析了Mybatis中$和#兩種符號使用上的不同,以及使用這兩種符號時,源碼流程上的區別。建議大家都使用#號,在orm這層也規避到Sql注入的風險。
倘若您有疑問或者有進一步想了解內容,歡迎留言給我。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67332.html
摘要:無論是在預處理語句中設置一個參數時,還是從結果集中取出一個值時,都會用類型處理器將獲取的值以合適的方式轉換成類型。這個抽象類實現了接口,這個接口主要定義了類型轉換的幾種操作。至于這個抽象類繼承的,主要是提供了獲取這個具體是哪個類型。 TypeHandlers 無論是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,還是從結果集中取出一個值時, 都會用...
摘要:北京解決辦法在字段的時候使用,下面是改動后的映射文件。北京那么我們來看看它是如何生效的,主要的代碼在哪里。源碼層面的話,依舊在的中處理返回集合。總結大致上,完成映射主要是兩種方式。使用預先定義好映射關系,也是最后根據和反射,完成字段的賦值。 前言 考慮到在Select時使用AS和方案一其實沒什么差別,在介紹ResultMap之前,順便帶過一下。 方案二-Select .... AS 當...
摘要:從使用到原理學習線程池關于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實現在軟件開發中,分散于應用中多出的功能被稱為橫切關注點如事務安全緩存等。 Java 程序媛手把手教你設計模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經風雨慢慢變老,回首走過的點點滴滴,依然清楚的記得當初愛情萌芽的模樣…… Java 進階面試問題列表 -...
閱讀 2386·2021-09-22 16:01
閱讀 3154·2021-09-22 15:41
閱讀 1171·2021-08-30 09:48
閱讀 490·2019-08-30 15:52
閱讀 3324·2019-08-30 13:57
閱讀 1713·2019-08-30 13:55
閱讀 3649·2019-08-30 11:25
閱讀 757·2019-08-29 17:25