中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

河北建設(shè)網(wǎng)站企業(yè)鎖在哪下載剛剛地震最新消息今天

河北建設(shè)網(wǎng)站企業(yè)鎖在哪下載,剛剛地震最新消息今天,知名b2b平臺,wordpress 去掉 頂部前言之前我一個搞網(wǎng)絡(luò)安全的朋友問了我一個的問題,為啥用 PreparedStatement 預(yù)編譯的 SQL 就不會有被 SQL 注入的風(fēng)險?第一時間我聯(lián)想到的是八股文中關(guān)于 Mybatis 的腳本 ${} 和 #{} 的問題,不過再想想,為啥 ${} 會有 SQL 注入的…

前言

之前我一個搞網(wǎng)絡(luò)安全的朋友問了我一個的問題,為啥用 PreparedStatement 預(yù)編譯的 SQL 就不會有被 SQL 注入的風(fēng)險?

第一時間我聯(lián)想到的是八股文中關(guān)于 Mybatis 的腳本 ${}#{} 的問題,不過再想想,為啥 ${} 會有 SQL 注入的風(fēng)險,而 #{} 就沒有?是因為到 PreparedStatement 做了什么處理嗎?不知道。

然后我又想了想,預(yù)編譯到底是個什么概念?預(yù)編譯或者不預(yù)編譯的 SQL 對數(shù)據(jù)庫來說有什么區(qū)別嗎?PreparedStatement 又在這個過程中扮演了怎樣的角色?不知道。

好吧,我發(fā)現(xiàn)我確實對這個問題一無所知,看來需要親自研究一下了。

一、數(shù)據(jù)庫預(yù)編譯

當(dāng)我們說到關(guān)于持久層框架的功能,必然需要先想想這個功能的源頭到底是不是直接通過數(shù)據(jù)庫提供的。實際上和事務(wù)一樣,SQL 預(yù)編譯的功能也是需要數(shù)據(jù)庫提供底層支持的。

1、預(yù)編譯SQL的用法

以 MySQL 為例,在 MySQL 中,所謂預(yù)編譯其實是指先提交帶占位符的 SQL 模板,然后為其指定一個 key,MySQL 先將其編譯好,然后用戶再拿著 key 和占位符對應(yīng)的參數(shù)讓 MySQL 去執(zhí)行,用法有點像 python 中的 format 函數(shù)。

一個標(biāo)準(zhǔn)的預(yù)編譯 SQL 的用法如下:

prepare prepare_query from'select * from s_user where username = ?' # 提交帶有占位符的參數(shù)化 SQL,也可以理解為 SQL 模板
set@name='%王五'; # 指定一個參數(shù)
execute prepare_query using@name; # 指定參數(shù)化 SQL 的 key 和參數(shù),讓 MySQL 自己去拼接執(zhí)行

先通過 prepare 設(shè)置一個 SQL 模板,然后通過 execute 提交參數(shù),MySQL 會自行根據(jù)參數(shù)替換占位符,到最后執(zhí)行的 SQL 就是:

select*from s_user where username ='%王五'

2、預(yù)編譯的原理

這里有個有意思問題,按網(wǎng)上的說法,prepare 執(zhí)行的時候?qū)嶋H上 SQL 已經(jīng)編譯完了,所以可以防止注入,因為后續(xù)不管塞什么參數(shù)都不可能在調(diào)整語法樹了,換個角度想,這是不是說明,如果我們一開始就讓 prepare 執(zhí)行的 SQL 模板的關(guān)鍵字變成占位符,是不是應(yīng)該在這個時候就編譯不通過?

比如,可以把查詢的表名改成占位符:

prepare prepare_query from'select * from ? where username = ?'# >1064- You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to 
# use near '? where username = ?'at line 1

實際上也確實不行,因為編譯時必須確定主表,因此在 from 后面加占位符會導(dǎo)致預(yù)編譯不通過。

那么只在查詢字段里面套一個嵌套查詢呢?

prepare prepare_query from'select ? from s_user';
SET@c='(select * from s_user) as q';
EXECUTE prepare_query using@c;# 查詢結(jié)果
# (select*from s_user) as q
# (select*from s_user) as q
# (select*from s_user) as q
# ......

查詢成功了,不過得到的結(jié)果的固定的 (select * from s_user) 這個字符串,我們檢查一下 MySQL 的執(zhí)行日志,看看最終執(zhí)行的 SQL 變成什么樣了:

Prepare	select ? from s_user
Query	SET@c='(select * from s_user) as q'
Query	EXECUTE prepare_query using@cExecute	select'(select * from s_user) as q'from s_user # 最終執(zhí)行的SQL

顯然,(select * from s_user) 參數(shù)本身被直接轉(zhuǎn)義為了一串普通的字符串,我們試圖“注入”的 SQL 片段完全不會生效。

換而言之,對于預(yù)編譯 SQL 來說,我們作為模板的參數(shù)化 SQL 已經(jīng)完成的編譯過程,這段 SQL 包含幾條有效語句?查哪張表?查哪些字段?作為條件的字段有哪些?......這些在 prepare 語句執(zhí)行完后都是固定的,此后我們再通過 execute 語句塞進(jìn)去的任何參數(shù),都會進(jìn)行轉(zhuǎn)義,不會再作為 SQL 的一部分。這就是為什么說預(yù)編譯 SQL 可以防止注入的原因。

二、JDBC的預(yù)編譯

現(xiàn)在我們知道了預(yù)編譯在數(shù)據(jù)庫中是個怎樣的功能,那么 JDBC 又是如何把這個功能提供給開發(fā)者使用的呢?

1、PreparedStatement

從最開始學(xué) JDBC 時,我們就知道通過 JDBC 連接數(shù)據(jù)庫一般是這樣寫的:

Class.forName(JDBC_DRIVER); // 加載驅(qū)動Connectionconnection= DriverManager.getConnection(URL, USERNAME, PASSWORD); // 獲取連接PreparedStatementpreparedStatement= connection.prepareStatement(sql); // 獲取sqlStatement
preparedStatement.setString(1, foo); // 設(shè)置參數(shù)ResultSetresultSet= preparedStatement.executeQuery(); // 執(zhí)行SQL

這里有一個關(guān)鍵角色 PreparedStatement,相比起它的父接口 Statement,它最大的變化是多了各種格式為 setXXX 的、用于設(shè)置與占位符對應(yīng)的參數(shù)的方法,顯然它正對應(yīng)著上文我們提到的預(yù)編譯 SQL。

2、虛假的“預(yù)編譯”

不過事情顯然沒有這么簡單,我們依然以 MySQL 為例,默認(rèn)情況下 MySQL 驅(qū)動包提供的 PreparedStatement 實現(xiàn)類 ClientPreparedStatement 也能起到防止 SQL 注入的功能,但是方式跟我們想的不太一樣。

假設(shè)現(xiàn)有如下代碼,我們嘗試模擬進(jìn)行一次 SQL 注入:

Stringsql="select * from s_user where username = ?";
PreparedStatementpreparedStatement= connection.prepareStatement(sql);
preparedStatement.setString(1, "王五' union select * from s_user");
ResultSetresultSet= preparedStatement.executeQuery();

運(yùn)行上述代碼并正常的請求數(shù)據(jù)庫,然后我們?nèi)?shù)據(jù)庫執(zhí)行日志中查看對應(yīng)的執(zhí)行的 SQL 如下,會發(fā)現(xiàn)只有這么一行:

Query select*from s_user where username ='王五'' union select * from s_user'

顯然跟我們上文說到的先 prepareexecute 流程不同,帶有占位符的原始 SQL 模板并沒有在日志中出現(xiàn),但是代碼中的 王五' 確實也被轉(zhuǎn)義為了 '王五''。

數(shù)據(jù)庫到底收到了哪些數(shù)據(jù)?

那么數(shù)據(jù)庫到底拿到的就是這條 SQL,還是原始的 SQL 模板 + 參數(shù)呢?

為了了解這一點,我們打斷點跟蹤 ClientPreparedStatement.executeQuery 方法,一路找到它組裝請求數(shù)據(jù)庫的參數(shù)的那一行代碼:

MessagesendPacket= ((PreparedQuery<?>) this.query).fillSendPacket();

最后我們會進(jìn)入 AbstractPreparedQuery.fillSendPacket 這個方法,這里主要干的事是把我們帶占位符的原始 SQL 模板和參數(shù)合并為最終要執(zhí)行的 SQL ,并封裝到 NativePacketPayload 對象,用于在后續(xù)發(fā)起 TCP 請求時把 SQL 參數(shù)轉(zhuǎn)為二進(jìn)制數(shù)據(jù)包。

為了驗證這一點,我們先拿到 sendPacket 對象,再獲取里面的字節(jié)數(shù)組,最后轉(zhuǎn)為字符串:

可以看到內(nèi)容就是已經(jīng)格式化完的 SQL:

select*from s_user where username ='王五'' union select * from s_user'

現(xiàn)在答案就很明顯了,轉(zhuǎn)義在 preparedStatement.setString 方法調(diào)用的時候完成,而 PreparedStatement發(fā)起請求前就把轉(zhuǎn)義后的參數(shù)和 SQL 模板進(jìn)行了格式化,最后發(fā)送到 MySQL 的時候就是一條普通的 SQL

鑒于此,我們可以說 MySQL 提供的 PreparedStatement 在默認(rèn)情況下是假的“預(yù)編譯”,它只不過在設(shè)置參數(shù)的時候幫我們對參數(shù)做了一下轉(zhuǎn)義,但是最后發(fā)送到數(shù)據(jù)庫的依然是普通的 SQL,而不是按預(yù)編譯 SQL 的方式去執(zhí)行。

3、真正的預(yù)編譯

好吧,那既然 MySQL 提供了這個預(yù)編譯的功能,那通過 JDBC 肯定也還是有辦法用上真正的預(yù)編譯功能的,實際上要做到這點也很簡單,就是直接在驅(qū)動的 url 上配上 useServerPrepStmts=true ,這樣就會真正的啟用 MySQL 的預(yù)編譯功能。

依然以上文的代碼為例:

Stringsql="select * from s_user where username = ?";
PreparedStatementpreparedStatement= connection.prepareStatement(sql);
preparedStatement.setString(1, "王五' union select * from s_user");
ResultSetresultSet= preparedStatement.executeQuery();

設(shè)置了 useServerPrepStmts=true 后再執(zhí)行代碼,去數(shù)據(jù)庫查看執(zhí)行日志有:

Executeselect*from s_user where username ='王五\'unionselect*from s_user'
Prepare select * from s_user where username = ?

此時 MySQL 的預(yù)編譯功能就真正的生效了。

我們回到 ClientPreparedStatement.executeQuery 創(chuàng)建 sendPacket 地方看,此時通過 ((PreparedQuery<?>) this.query).fillSendPacket(); 拿到的 Message 對象是 null,然后進(jìn)一步追蹤到最后向 MySQL 發(fā)送請求的地方 NativeSession.execSQL

public <T extendsResultset> T execSQL(Query callingQuery, String query, int maxRows, NativePacketPayload packet, boolean streamResults,ProtocolEntityFactory<T, NativePacketPayload> resultSetFactory, ColumnDefinition cachedMetadata, boolean isBatch) {// ... ...try {// 如果 sendPacket 為 null,則調(diào)用 sendQueryString 方法,把原始 sql 和參數(shù)序列化為二進(jìn)制數(shù)據(jù)包returnpacket== null? ((NativeProtocol) this.protocol).sendQueryString(callingQuery, query, this.characterEncoding.getValue(), maxRows, streamResults, cachedMetadata, resultSetFactory)// 否則調(diào)用 sendQueryPacket 方法,直接發(fā)送數(shù)據(jù)包: ((NativeProtocol) this.protocol).sendQueryPacket(callingQuery, packet, maxRows, streamResults, cachedMetadata, resultSetFactory);}// ... ...}

更具體的實現(xiàn)就不看了,基本都是關(guān)于序列化請求參數(shù)的邏輯。

三、Myabtis占位符與預(yù)編譯

至此問題真相大白了,不過還是順帶扯一下八股文常提到的 Mybatis 占位符 #{}${} 是如何影響 SQL 注入問題的。

當(dāng)然,看完上面的內(nèi)容其實就已經(jīng)很好猜到原因了:

  • #{} 對應(yīng)的內(nèi)容會作為 SQL 參數(shù)的一部分通過 PreparedStatement.setXXX 裝入請求;

  • ${} 對應(yīng)的內(nèi)容會直接作為 SQL 模板的一部分,而不會視為獨(dú)立的請求參數(shù);

在 Mybatis 中,用于解析占位符的類為 GenericTokenParser ,根據(jù)它我們很容易在源碼中找到占位符的處理方法,從而驗證我們的猜想:

其中,#{} 占位符在 SqlSourceBuilder.ParameterMappingTokenHandler.handleToken 方法中處理:

public String handleToken(String content) {parameterMappings.add(buildParameterMapping(content));return"?";
}

可見 #{} 占位符會被解析為 ? 占位符,而對于的數(shù)據(jù)會被添加到 parameterMappings 用于后續(xù)塞到 PreparedStatement。

${} 占位符在 PropertyParser.VariableTokenHandler.handleToken 方法中被處理:

public String handleToken(String content) {if (variables != null) {Stringkey= content;if (enableDefaultValue) {finalintseparatorIndex= content.indexOf(defaultValueSeparator);StringdefaultValue=null;if (separatorIndex >= 0) {key = content.substring(0, separatorIndex);defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());}if (defaultValue != null) {return variables.getProperty(key, defaultValue);}}if (variables.containsKey(key)) {return variables.getProperty(key);}}return"${" + content + "}";
}

若占位符符合規(guī)范,則占會根據(jù)占位符中的內(nèi)容去用戶給定的參數(shù)中取值,并且讓值直接替換掉原本 SQL 腳本中的 ${} 占位符。

這就是“ Mybatis#{} 而不是 ${} 可以防止 SQL 注入的真相。

總結(jié)

回顧一下全文,當(dāng)我們說“預(yù)編譯”的時候,其實這個功能來自于數(shù)據(jù)庫的支持,它的原理是先編譯帶有占位符的 SQL 模板,然后在傳入?yún)?shù)讓數(shù)據(jù)庫自動替換 SQL 中占位符并執(zhí)行,在這個過程中,由于預(yù)編譯好的 SQL 模板本身語法已經(jīng)定死,因此后續(xù)所有參數(shù)都會被視為不可執(zhí)行的非 SQL 片段被轉(zhuǎn)義,因此能夠防止 SQL 注入。

當(dāng)我們通過 JDBC 使用 PreparedStatement 執(zhí)行預(yù)編譯 SQL 的時候,此處的預(yù)編譯實際上是假的預(yù)編譯(至少 MySQL 是如此,不過其他數(shù)據(jù)庫仍待確認(rèn)),PreparedStatement 只是在設(shè)置參數(shù)的時候自動做了一層轉(zhuǎn)義,最終提交給數(shù)據(jù)庫執(zhí)行的 SQL 仍然是單條的非預(yù)編譯 SQL。

而當(dāng)我們通過在驅(qū)動 url 上開啟 useServerPrepStmts 配置后,預(yù)編譯就會真正的生效,驅(qū)動包發(fā)往數(shù)據(jù)庫的請求就會分成帶占位符的 SQL 模板和參數(shù),到了數(shù)據(jù)庫再由數(shù)據(jù)庫完成格式化并執(zhí)行。

此外,八股文常提到的“Mybatis#{} 相比 ${} 可以防止 SQL 注入”這一點,本質(zhì)上是因為 #{} 占位符會被解析為 SQL 模板中的 ? 占位符,而 ${} 占位符會被直接解析為 SQL 模板的一部分導(dǎo)致的。

最后腦補(bǔ)一下,由于 useServerPrepStmts 不開啟時 PreparedStatement 的預(yù)編譯實際上是假的預(yù)編譯,所以理論上使用 #{} 也并非絕對安全,如果有辦法繞過 PreparedStatement 的檢查,那么數(shù)據(jù)庫拿到被注入過的 SQL 直接執(zhí)行,依然有暴斃的風(fēng)險。

http://www.risenshineclean.com/news/7474.html

相關(guān)文章:

  • 洛陽外貿(mào)網(wǎng)站推廣網(wǎng)站內(nèi)部鏈接優(yōu)化方法
  • 濟(jì)南建設(shè)廳官方網(wǎng)站企業(yè)網(wǎng)絡(luò)營銷方案設(shè)計
  • 唐山中企動力做網(wǎng)站搜索大全引擎入口
  • 怎么看網(wǎng)站空間多大品牌整合營銷方案
  • 哪個公司做網(wǎng)站簡單免費(fèi)制作手機(jī)網(wǎng)站
  • 德國 網(wǎng)站后綴長春做網(wǎng)絡(luò)優(yōu)化的公司
  • wordpress保存帖子數(shù)據(jù)長沙seo研究中心
  • 英文b2c網(wǎng)站建設(shè)鄭州seo關(guān)鍵詞優(yōu)化公司
  • 杭州做網(wǎng)站哪家最好目前最火的自媒體平臺
  • dw做網(wǎng)站實例如何快速搭建網(wǎng)站
  • php網(wǎng)站搭建環(huán)境搭建怎樣有效的做網(wǎng)上宣傳
  • 快速搭建網(wǎng)站后天臺百度搜索鏈接入口
  • 網(wǎng)站建設(shè)方案書的內(nèi)容管理制度長沙網(wǎng)站推廣公司
  • 武安網(wǎng)站制作培訓(xùn)總結(jié)心得體會
  • 廈門網(wǎng)站建設(shè)首選廈門一聯(lián)網(wǎng)絡(luò)網(wǎng)絡(luò)營銷的功能有哪些?
  • wordpress自動生成縮略圖seo優(yōu)化與品牌官網(wǎng)定制
  • 公司網(wǎng)站找誰做谷歌代運(yùn)營
  • cpc引流做網(wǎng)站cpa推廣網(wǎng)站策劃書案例
  • 制作一個網(wǎng)站怎么做目前最牛的二級分銷模式
  • 做冰塊批發(fā)網(wǎng)站安徽網(wǎng)站關(guān)鍵詞優(yōu)化
  • 網(wǎng)站 購買廣州專做優(yōu)化的科技公司
  • dw制作簡單網(wǎng)站模板下載地址推廣軟件賺錢違法嗎
  • 旅游網(wǎng)站建設(shè)策劃書范文東莞seo關(guān)鍵詞排名優(yōu)化排名
  • 招標(biāo)信息網(wǎng)大連seo優(yōu)化
  • 關(guān)于建立企業(yè)網(wǎng)站的方案內(nèi)容seo優(yōu)化培訓(xùn)課程
  • 長沙做電商網(wǎng)站設(shè)計營銷公司網(wǎng)站
  • 做微信投票的網(wǎng)站免費(fèi)seo營銷軟件
  • 安卓軟件下載app商丘seo優(yōu)化
  • 商城網(wǎng)站備案營銷案例100例小故事
  • 湖南網(wǎng)站建設(shè)有限公司網(wǎng)絡(luò)服務(wù)主要包括什么