深入源碼,探究#、$號替換符的區別

在Mybatis的日常使用過程中以及在一些技術論壇上我們都能常常聽到,不要使用$符號來進行SQL的編寫,要使用#符號,否則會有SQL注入的風險。那么,為什么在使用$符號時會有注入的風險呢,以及#號為什么不會有風險呢?這一期我們來從源碼分析一下。

$號占位符

在Mybatis替換SQL占位符時,會針對$#號進行解析替換操作。然而對于$號來說,僅僅只會將該參數對應的值拼接在SQL中而已。

前置知識

在Mybatis中,SQL會被解析成一個個的SqlNode,對于不同的SqlNodeMybatis的解析處理都是不一樣的。
一般情況來說,SQL中存在$號的話,都會被解析成TextSqlNode

解析并替換

Mybatis中,解析TextSqlNode的占位符主要使用到兩個類

  1. GenericTokenParser:用于查找SQL中具體的占位符以及占位符代表的屬性名
  2. TokenHandler:根據占位符的屬性名獲取對應的值
public String parse(String text) {if (text == null || text.isEmpty()) {return "";}// search open token// 找到$號所在的位置int start = text.indexOf(openToken);if (start == -1) {return text;}char[] src = text.toCharArray();int offset = 0;final StringBuilder builder = new StringBuilder();// 占位符中的變量名StringBuilder expression = null;do {if (start > 0 && src[start - 1] == '\\') {// this open token is escaped. remove the backslash and continue.builder.append(src, offset, start - offset - 1).append(openToken);offset = start + openToken.length();} else {// found open token. let's search close token.if (expression == null) {expression = new StringBuilder();} else {expression.setLength(0);}builder.append(src, offset, start - offset);offset = start + openToken.length();int end = text.indexOf(closeToken, offset);while (end > -1) {if ((end <= offset) || (src[end - 1] != '\\')) {expression.append(src, offset, end - offset);break;}// this close token is escaped. remove the backslash and continue.expression.append(src, offset, end - offset - 1).append(closeToken);offset = end + closeToken.length();end = text.indexOf(closeToken, offset);}if (end == -1) {// close token was not found.builder.append(src, start, src.length - start);offset = src.length;} else {// 從TokenHandler中解析出變量名對應的參數值builder.append(handler.handleToken(expression.toString()));offset = end + closeToken.length();}}start = text.indexOf(openToken, offset);} while (start > -1);if (offset < src.length) {builder.append(src, offset, src.length - offset);}return builder.toString();}

在這個parse方法中,最終的解析方法在47行:

builder.append(handler.handleToken(expression.toString()));

在這一行代碼會調用TokenHandler這個類的handleToken方法,獲取參數名對應的結果

public String handleToken(String content) {Object parameter = context.getBindings().get("_parameter");if (parameter == null) {context.getBindings().put("value", null);} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {context.getBindings().put("value", parameter);}Object value = OgnlCache.getValue(content, context.getBindings());String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"checkInjection(srtValue);return srtValue;
}

這個方法主要涉及幾個操作

  1. 使用Ognl獲取該參數名對應的值。

該結果值是直接使用String.valueOf進行解析,那么在這一步中,就有可能導致SQL注入的問題了。

  1. 檢查結果是否有注入風險。

這個方法名checkInjection看起來就像是用于檢查解析后的結果是否有注入SQL的風險的。但是呢,這個方法并不會起任何作用。因為這個方法起作用的前提是injectionFilter得不為null,但是在Mybatis中,并沒有對這個屬性進行任何的賦值行為,所以也就沒有任何用處了。

private void checkInjection(String value) {if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());}
}

解析例子

現在有一條使用了$號的SQL:

SELECT * FROM log WHERE content='${id}'

content哈哈哈時,經過Mybatis的解析后,會變成什么樣呢?

SELECT * FROM log WHERE content='哈哈哈'

這樣的SQL并沒有任何問題,但是如果此時content的值為哈哈哈'; DROP TABLE log --的話,SQL解析后的結果就長這樣了:

SELECT * FROM log WHERE content='哈哈哈'; DROP TABLE log --'

就會導致整個log表的數據被清除了,而這正是不當使用**$**的問題了。

#號占位符

既然$號有這么多的問題,為什么#號卻不會有SQL注入的問題呢?我們來從實際例子來逐步展開。
現在有一個簡單的SQL語句:

SELECT * FROM log WHERE content=#{id}

這個語句唯一不同的點就是將'${id}'換成了#{id},但是在Mybatis中的解析卻是天差地別了。

初始化解析

$號不一樣的是,在初始化Mybatis的MappedStatement時,檢測到#號時,會提前初始化該SQL語句。無論是在注解中寫SQL還是在Xml文件中寫SQL,解析#號的方法最終都會進入到org.apache.ibatis.builder.SqlSourceBuilder#parse這個方法中。

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType,additionalParameters);GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);String sql;if (configuration.isShrinkWhitespacesInSql()) {sql = parser.parse(removeExtraWhitespaces(originalSql));} else {sql = parser.parse(originalSql);}return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

這個解析與$號的解析類似,也是由TokenHandler類進行參數名與對象之間的轉換。
#號的替換中,則是ParameterMappingTokenHandler來進行參數名與對象之間的轉換。
但是這個類的handleToken方法比較特別,返回值居然是一個**?**。并且在返回結果之前,還有一步操作

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

這個buildParameterMapping方法太長了,還是來看看具體返回了啥吧。
image.png
可以看到,這個方法的作用似乎是給SQL中的每一個占位符進行參數解析,將占位符對應的參數的類型、數據庫類型、填充類型等都進行了解析。
這個初始化解析結束后,這一條SQL就變成了下面的樣子了:

SELECT * FROM log WHERE content=?

并且還有一個集合parameterMappings裝載了SQL中占位符的屬性。

實際替換參數

初始化后,Mybatis在真正查詢就會將利用PreparedStatement進行?占位符的替換了。

// org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
public void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());// 這個parameterMappings正是出事話解析SQL得到的參數映射集合List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != null) {MetaObject metaObject = null;// 遍歷每一個參數映射for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);if (parameterMapping.getMode() != ParameterMode.OUT) {// 獲取參數對應的值Object value;String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional paramsvalue = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {if (metaObject == null) {metaObject = configuration.newMetaObject(parameterObject);}value = metaObject.getValue(propertyName);}TypeHandler typeHandler = parameterMapping.getTypeHandler();JdbcType jdbcType = parameterMapping.getJdbcType();if (value == null && jdbcType == null) {jdbcType = configuration.getJdbcTypeForNull();}try {// 設置每個?號對應的值typeHandler.setParameter(ps, i + 1, value, jdbcType);} catch (TypeException | SQLException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);}}}}
}

typeHandler.setParameter這個方法則是利用了PreparedStatement類的方法,將?替換成傳入的參數。
image.png
PreparedStatement在填充具體值會對參數進行轉義,比如上述的SQL以及參數在查詢時則會變成:

SELECT * FROM log WHERE content='哈哈哈''; DROP TABLE log --'

則不會有SQL注入的風險了。

總結

$號:直接替換占位符中的內容,在不對參數進行校驗的情況下,易出現SQL注入問題。
#號:在預編譯SQL的前提下,將參數名替換成?號,并利用PreparedStatement進行占位符的替換,在替換過程中,會對注入值進行轉義避免SQL注入。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/42787.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/42787.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/42787.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C/C+++服務器之libuv的使用實戰

libuv libuv簡介 1: 開源跨平臺的異步IO庫, 主要功能有網絡異步&#xff0c;文件異步等。 2: libuv主頁: http://libuv.org/ 3: libuv是node.js的底層庫; 4: libuv的事件循環模型: epoll, kqueue, IOCP, event ports; 異步 TCP 與 UDP sockets; DNS 解析 異步文件讀寫; 信號處…

Python結合MobileNetV2:圖像識別分類系統實戰

一、目錄 算法模型介紹模型使用訓練模型評估項目擴展 二、算法模型介紹 圖像識別是計算機視覺領域的重要研究方向&#xff0c;它在人臉識別、物體檢測、圖像分類等領域有著廣泛的應用。隨著移動設備的普及和計算資源的限制&#xff0c;設計高效的圖像識別算法變得尤為重要。…

設計模式-結構型-08-組合模式

文章目錄 1、學校院系展示需求2、組合模式基本介紹3、組合模式示例3.1、 解決學校院系展示&#xff08;透明模式1&#xff09;3.2、高考的科目&#xff08;透明模式2&#xff09;3.3、高考的科目&#xff08;安全組合模式&#xff09; 4、JDK 源碼分析5、注意事項和細節 1、學校…

存儲過程編程-創建(CREATE PROCEDURE)、執行(EXEC)、刪除(DROP PROCEDURE)

一、定義 1、存儲過程是在SQL服務器上存儲的已經編譯過的SQL語句組。 2、存儲過程分為三類&#xff1a;系統提供的存儲過程、用戶定義的存儲過程和擴展存儲過程 &#xff08;1&#xff09;系統提供的存儲過程&#xff1a;在安裝SQL Server時&#xff0c;系統創建了很多系統存…

AI機器人在企業拓客上常見的功能有哪些

AI機器人具備多種功能&#xff0c;這些功能主要基于其被設計和訓練的目的。整理了一些常見的AI機器人功能&#xff1a; 1. 語音識別與自然語言處理&#xff1a; - 語音識別&#xff1a;將用戶的語音輸入轉換為文本&#xff0c;以便機器人可以理解和處理。 - 自然語言處理…

QCC5181 歌詞歌曲名多國語言顯示替代QCC5125 CSR8675

QCC518X作為Qualcomm新一代藍牙技術芯片&#xff0c;支持最新藍牙協議V5.4&#xff0c;較QCC512X系列&#xff0c;它有更強大的DSP、CPU。除支持USB、I2S、SPDIF等接口外&#xff0c;還擴展了LE Audio功能&#xff0c;擴展支持AptX Lossless。以5181為例&#xff0c;我們還擴展…

vscode語言模式

1.背景 寫vue3ts項目的時候&#xff0c;用到了volar插件&#xff0c;在單文件使用的時候&#xff0c;鼠標懸浮在代碼上面會有智能提示&#xff1b; 但是最近volar插件提示被棄用了&#xff0c;然后我按照它的官方提示&#xff0c;安裝了Vue-official擴展插件&#xff0c;但是…

Banana Pi BPI-M5 Pro 低調 SBC 采用 Rockchip RK3576 八核 Cortex-A72/A53 AIoT SoC

Banana Pi BPI-M5 Pro&#xff0c;也稱為 Armsom Sige5&#xff0c;是一款面向 AIoT 市場的低調單板計算機 (SBC)&#xff0c;由 Rockchip RK3576 八核 Cortex-A72/A53 SoC 驅動&#xff0c;提供Rockchip RK3588和RK3399 SoC 之間的中檔產品。 該主板默認配備 16GB LPDDR4X 和…

如何大幅減少 Vue.js 中的包大小和加載時間,提升用戶體驗!

大家好,我是CodeQi! 一位熱衷于技術分享的碼仔。 你知道嗎,根據Google 的一項研究,如果網站加載時間超過 3 秒,53% 的移動用戶會離開該網站? 性能優化是一個經常討論的話題,但很多開發人員并不關心提高應用的速度。 在前端開發中,優化包大小和加載時間對于提升用戶體…

下一代 CLI 工具,使用Go語言用于構建令人驚嘆的網絡應用程序

大家好&#xff0c;今天給大家分享一個創新的命令行工具Gowebly CLI&#xff0c;它專注于使用Go語言來快速構建現代Web應用程序。 Gowebly CLI 是一款免費開源軟件&#xff0c;有助于在后端使用 Go、在前端使用 htmx 和 hyperscript 以及最流行的 CSS 框架輕松構建令人驚嘆的 W…

入門PHP就來我這(高級)15 ~ 圖書刪除功能

有膽量你就來跟著路老師卷起來&#xff01; -- 純干貨&#xff0c;技術知識分享 路老師給大家分享PHP語言的知識了&#xff0c;旨在想讓大家入門PHP&#xff0c;并深入了解PHP語言。 今天給大家接著上篇文章實現圖書刪除功能&#xff0c;來實現刪除圖書信息記錄行的功能。 1 刪…

高顏值官網(3):家居用品網站12個,好的創意都在這里。

hello&#xff0c;大家好&#xff0c;我是大千UI工場&#xff0c;本文為大家帶來家居用品網站UI&#xff0c;供大家欣賞。

項目代碼優化(1)——下單邏輯

給一個電商開發的系統排查&#xff0c;發現漏洞很多。很多經驗不夠的開發者很容易忽視的邏輯錯誤陷阱。在給一個項目做二次開發時候&#xff0c;檢測到的相關經典案例。這里整理支付和產品相關的邏輯&#xff0c;方便后續查看。&#xff0c;這里進行一些簡單的邏輯漏洞梳理與修…

Ubuntu 22.04 LTS 上安裝 MySQL8.0.23(在線安裝)

目錄 在線安裝MySQL 步驟1&#xff1a;更新軟件包列表 步驟2&#xff1a;安裝MySQL服務器 步驟3&#xff1a;啟動MySQL服務 步驟4&#xff1a;檢查MySQL狀態 步驟5&#xff1a;修改密碼、權限 在線安裝MySQL 步驟1&#xff1a;更新軟件包列表 在進行任何軟件安裝之前&a…

p9函數(1)

int Add(int x,int y) { int z0; zxy; return z; } int main() { int a10; int b20; int sumAdd(a,b); printf("%d\n",sum); return 0; } 字符串求長度 int main() { char arr1[]"bit"; char arr2[20]"###…

移動UI: 什么特征會被認為是簡潔風格,用案例告訴你

什么是簡潔風格&#xff0c;恐怕一百個人有一百個是理解&#xff0c;本文通過理論分析案例的方式進行探討。 移動 UI 中的簡潔風格通常具有以下幾個特征&#xff1a; 1. 平面化設計&#xff1a; 簡潔風格的移動 UI 善于運用平面化設計&#xff0c;即去除過多的陰影、漸變和立…

水冷液冷負載系統的六種基本類型

您可以選擇六種基本類型的冷卻系統&#xff0c;以滿足負載的冷卻需求。每個人都有其優點和缺點。本文旨在識別不同類型的冷卻系統并確定它們的優缺點&#xff0c;以便您可以根據自己的需求做出明智的選擇。 液體冷卻系統有六種基本類型&#xff1a; 1.液對液 2.閉環干燥系統…

聚類標簽的藝術:SKlearn中的數據聚類標簽分配策略

聚類標簽的藝術&#xff1a;SKlearn中的數據聚類標簽分配策略 在機器學習領域&#xff0c;聚類是一種無監督學習方法&#xff0c;旨在將數據集中的樣本劃分為若干個簇&#xff0c;使得同一簇內的樣本相似度高&#xff0c;而不同簇之間的樣本相似度低。聚類標簽分配是聚類過程中…

深度講解 UUID/GUID 的結構、原理以及生成機制

目錄 一. 前言 二. 被廣泛使用 三. UUID 的結構 3.1. 必須了解的 3.2. 十六進制數字字符&#xff08;hexDigit&#xff09; 3.3. UUID 基本結構 3.4. 類型&#xff08;變體&#xff09;和保留位 3.5. 版本&#xff08;子類型&#xff09; 3.6. 時間戳 3.7. 時鐘序列 …

管理《歐盟數字服務法》交易者要求

《數字服務法》合規性 根據《數字服務法》(DSA) 的要求&#xff0c;對于在歐盟地區 (EU) 通過 App Store 分發 App 的所有交易商&#xff0c;Apple 需要驗證并顯示其聯系信息。請指明你是否將以交易商或非交易商的身份在歐盟地區分發任何內容。進一步了解你是否應為交易商。 …