深入源码,探究#、$号替换符的区别

在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.mzph.cn/diannao/42787.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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.闭环干燥系统…

深度讲解 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 需要验证并显示其联系信息。请指明你是否将以交易商或非交易商的身份在欧盟地区分发任何内容。进一步了解你是否应为交易商。 …

[激光原理与应用-101]:南京科耐激光-激光焊接-焊中检测-智能制程监测系统IPM介绍 - 5 - 3C行业应用 - 电子布局类型

目录 前言&#xff1a; 一、激光在3C行业的应用概述 1.1 概述 1.2 激光焊接在3C-电子行业应用 二、3C电子行业中激光焊接 2.1 纽扣电池 2.2 均温板 2.3 指纹识别器 2.4 摄像头模组 2.5 IC芯片切割 三、3C行业中激光切割 四、激光在3C行业中的其他应用 4.1 涂层去除…

Golang | Leetcode Golang题解之第222题完全二叉树的节点个数

题目&#xff1a; 题解&#xff1a; func countNodes(root *TreeNode) int {if root nil {return 0}level : 0for node : root; node.Left ! nil; node node.Left {level}return sort.Search(1<<(level1), func(k int) bool {if k < 1<<level {return false}…