1 基础环境搭建
1.1 mysql数据库搭建
phpStudy是一个PHP调试环境的程序集成包,PHP+Mysql+Apache。 通过phpstduy下载与安装指
定版本的mysql数据库【可以同时下载多个版本,便于应对不对的系统及复现漏洞便捷切换多个版本】
完成下载后,启动服务即可。

(2)使用navicat连接mysql服务后可进行相关操作,连接成功后,打开数据库,可以打开数据表直接读写,也可以执行相关SQL命令

1.2 mysql数据库配置
MySQL 安装包:MySQL :: Download MySQL Community Server
MySQL安装教程:MySQL 安装 | 菜鸟教程
MySQL配置文件



允许远程连接
mysql>GRANT ALL PRIVILEGES ON *.* TO ‘root’@‘%’ IDENTIFIED BY‘密码’ WITH GRANT OPTION; mysql>flush privileges
MySQL开启日志记录
找到MySQL的配置文件 my.ini,在 [mysqld] 中port参数下方添加一行记录:
log="D:/phpstudy_pro/Extensions/MySQL5.1.60/data" 保存重启数据库 此时会在指
定路径下新增一个日志文件。

2 在线SQL练习平台
2.1 SQL Fiddle
SQL Fiddle - Online SQL Compiler for learning & practice, SQL Fiddle 支持 MySQL、SQL Server、SQLite 等主流的 SQL 引擎,在这
里可以选择练习的数 据库以及版本号 。

2.2 SQLZOO
https://sqlzoo.net/, SQLZOO包括了 SQL 学习的教程和参考资料, 支持 SQL Sever、Oracle、MySQL、DB2、 PostgreSQL等多个 SQL 搜索引擎 。

2.3 SQL Bolt
SQLBolt - Learn SQL - Introduction to SQL, SQLBolt 是一个适合小白学习 SQL 的网站,这里由浅及深的介绍了 SQL 的知识,每一个章节是一组相关的 SQL 知识点,且配备着相应的练习 。

3 SQL语法学习
SQL (Structured Query Language:结构化查询语言) 用于管理关系数据库管理系统 (RDBMS),可以访问和处理数据库,包括数据插入、查询、更新和删除。
• 提示:
SQL 对大小写不敏感
分号:分号是在数据库系统中分隔每条 SQL 语句的标准方法,这样就可以在对服务器的相同 请求中执行一条以上的语句
3.1 Select查询
SELECT 语句用于从数据库中选取数据。
语法格式: SELECT column_name,column_name FROM table_name; 或 SELECT * FROM table_name
3.2 WHERE语句
WHERE 子句用于过滤记录.
语法格式: SELECT column_name,column_name FROM table_name WHERE column_name operator value;

例如:
where之=查询数据:select * from javacode_tb1 where audit_id = 1;

3.3 UNION联合查询
UNION 用于合并两个或多个 SELECT 语句的结果集,并消去表中任何重复行。 UNION 内部的
SELECT 语句必须拥有相同数量的列,列也必须拥有相似的数据类型。 同时,每条 SELECT 语句中的
列的顺序必须相同。
语法格式: SELECT column_name FROM table1 UNION SELECT column_name FROM table2
union查询:select audit_author from javacode_tb1 union select audit_author from javacode_tb2 order by audit_author;
提示:默认地,UNION 操作符选取不同的值。如果允许重复的值,请使用 UNION ALL

3.4 ORDER BY 关键字
ORDER BY 关键字用于对结果集进行排序。 默认按照升序对记录进行排序。如果需要按照降序对记录
进行排序,您可以使用 DESC 关键字。
语法结构: SELECT column_name,column_name FROM table_name ORDER BY
column_name,column_name ASC|DESC
例如: 按照 “alexa” 列升序排序: SELECT * FROM Websites ORDER BY alexa; 按照 “alexa”
列降序排序: SELECT * FROM Websites ORDER BY alexa DESC;
(1)order by 后面可以跟字段名,表达式和字段的位置号。
例如: select * from student_table order by student_age

例如: select * from student_table order by 3;
思考:order by 后的索引是从0开始还是从1开始?经过测试mysql数据库的order by 索引从1开始,而非0

当表达式不成立时,执行后者,成立执行前者
提示:if语句返回的是字符类型,不是整型
select * from student_table order by if(1=1,student_age,student_tele)

3.5 like模糊查询
LIKE 操作符用于在 WHERE 子句中搜索列中的指定模式。 "%" 符号用于在模式的前后定义通配符(默认字母)。
基本语法: SELECT column_name(s) FROM table_name WHERE column_name LIKE pattern
例如:select * from javacode_tb1 where audit_author like '%s%';

3.6 in关键字
IN 操作符通常用来在 WHERE 子句中规定多个值【而不是一个范围】
语法格式: SELECT column_name(s) FROM table_name WHERE column_name IN
(value1,value2,...) ;
例如: 选取 student_name 为 "tom" 或 ”jerry" 的所有网站,
SELECT * FROM student_table WHERE student_age IN (17,14);

3.6 group by 分组查询
GROUP BY 语句通常结合一些聚合函数来使用,根据一个或多个列对结果集进行分组。
常用聚合函数包括:count() —— 计数 、sum() —— 求和 、avg() —— 平均数 、max() —— 最大值 、min() —— 最小值
例如1:
(1)创建数据表:
DROP TABLE IF EXISTS employee_tbl; CREATE TABLE employee_tbl ( id INT(11) NOT NULL, name VARCHAR(100) NOT NULL DEFAULT '', date DATETIME NOT NULL, login_count TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '登录次数', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
(2)向数据表中插入数据:
INSERT INTO employee_tbl VALUES ('1','小明', '2016-04-22 15:25:33', '1'), ('2', '小王', '2016-04-20 15:25:47', '3'), ('3', '小丽', '2016-04-19 15:26:02', '2'), ('4', '小王', '2016-04-07 15:26:14', '4'), ('5', '小明', '2016-04-11 15:26:40', '4'), ('6','小明', '2016-04-04 15:26:54', '2'); 
(3)执行group by查询操作:
SELECT name, COUNT(*) FROM employee_tbl GROUP BY name;

3.8. 数据库常见函数
3.8.1 聚焦函数
-  avg:返回指定字段的数据的平均值 
-  count:返回指定字段的数据的行数(记录的数量) 
-  max:返回指定字段的数据的最大值 
-  min:返回指定字段的数据的最小值 
-  sum:返回指定字段的数据之和 
3.8.2 处理字符串函数
-  合并字符串函数:concat(str1,str2,str3…) 
-  比较字符串大小函数:strcmp(str1,str2) 
-  获取字符串字节数函数:length(str) 
-  获取字符串字符数函数:char_length(str) 
-  字母大小写转换函数:大写:upper(x),ucase(x);小写lower(x),lcase(x) 
-  字符串查找函数:find_in_set(str1,str2)、field(str,str1,str2,str3…)、locate(str1,str2)、position(str1 INstr2)、nstr(str1,str2) 
-  获取指定位置的子串:elt(index,str1,str2,str3…)、left(str,n)、right(str,n)、substring(str,index,len) 
-  字符串去空函数:ltrim(str)、rtrim(str)、trim() 
-  字符串替换函数:insert(str1,index,len,str2)、replace(str,str1,str2) 
3.8.3 处理时间日期的函数
-  获取当前日期:curdate(),current_date() 
-  获取当前时间:curtime(),current_time() 
-  获取当前日期时间:now() 
-  从日期中选择出月份数:month(date),monthname(date) 
-  从日期中选择出周数:week(date) 
-  从日期中选择出年数:year(date) 
-  从时间中选择出小时数:hour(time) 
-  从时间中选择出分钟数:minute(time) 
-  从时间中选择出今天是周几:weekday(date),dayname(date) 
3.8.4 处理数值的函数
-  绝对值函数:abs(x) 
-  向上取整函数:ceil(x) 
-  向下取整函数:floor(x) 
-  取模函数:mod(x,y) 
-  随机数函数:rand() 
-  四舍五入函数:round(x,y) 
-  数值截取函数:truncate(x,y) 
3.8.5 与安全相关的函数应用(重要)
-  获取当前用户:(1)select user();(2)select current_user();(3)select current_user;(4)selectsystem_user();(5)select session_user(); 
-  获取当前数据库:(1)select database();(2)select schema(); 
-  获取所有数据库:SELECT schema_name FROM information_schema.schemata; 
-  元数据:人家Mysql自有的数据库中数据库所存储的数据 
-  获取服务器主机名:select @@HOSTNAME; 
-  查询用户是否有读写权限:SELECT grantee, is_grantable FROM information_schema.user_privileges WHERE privilege_type = 'file' AND grantee like '%username%';SELECT file_priv FROM mysql.user WHERE user = 'root';(需要root用户来执行) 
-  注释:(1)单行注释:--(2)多行注释:// 
-  字符串截取:SELECT substr('abc',2,1); 
-  MySQL特有的写法:/! SQL语句 / :这种格式里面的SQL语句可以被当成正常的语句执行。当版本号大于!后面的一串数据,SQL语句则执行 
-  空格被过滤编码绕过:%20, %09, %0a, %0b, %0c, %0d, %a0%a0UNION%a0select%a0NULL 
-  or,and绕过:or可以使用||代替,and可以使用&&代替。 
-  union select被过滤:(1)union(select(username)from(admin)); :union和select之间用(代替空格。 
-  select 1 union all select username from admin; :union和select之间用all,还可以用distinct3 
-  select 1 union%a0select username from admin; :同样的道理%a0代替了空格。select 1 union/!select/username from admin; 。 
-  select 1 union/hello/username from admin; 注释代替空格 
-  关键字where被过滤使用limit来代替(limit被用于强制 SELECT 语句返回指定的记录数) 
-  limit绕过:使用having函数(having:在聚合后对组记录进行筛选,类似于where作用,经常与grop by使用) 
-  having函数绕过:group_concat():将相同的行组合起来 
4 SQL注入漏洞
在输入的字符串中注入 SQL 语句,如果应用相信用户的输入而对输入的字符串没进 行任何的过滤处理,那么这些注入进去的 SQL 语句就会被数据库误认为是正常的 SQL 语句而 被执行。
4.1 万能密码绕过登陆
select * from admin where username='$username'and password='$password' 若输入 ' or 1=1-- (后面有个空格)
则后端变成: select * from admin where username='' or 1=1 -- 'and password='$password’
例如:
打开SQL注入漏洞测试(登录绕过)_SQL注入_在线靶场_墨者学院_专注于网络安全人才培养,
输入 ' or 1=1# 或'or 1=1--
4.2 SQL注入分类
-  有无回显:显注、盲注 
-  按数据库类型:MySQL、Access、SQL Server、Oracle等 
-  注入点参数类型:数字型、字符型 当发生注入点的参数为整数时,比如 ID,num,page等,这种形式的就属于数字型注入漏洞。 当注入点是字符串时,则称为字符型注入,字符型注入需要引号来闭合。 
-  按请求方式:GET注入、POST登录框注入、Cookie验证注入、HTTP头注入 
4.3 常见的注入点
应用程序和数据交互的地方:
-  Authentication(认证页面) 
-  Search Fields (搜索页面)• Post Fields (Post请求) 
-  Get Fields (Get请求) 
-  HTTP Header(HTTP头部) 
-  Cookie 
4.4 数据库特性
(1) 数据库特定表
-  and exists(select count(*) from msysobjects) 
mysysobjects表为access特有,一般返回权限不足
-  and exists(select count(*) from sysobjects) 
sysobjects表为SqlServer特有,一般返回正常
-  and (select count(*) from information_schema.TABLES)>0 
information_schema为MySQL 5.0 及以上版本特有
(2) 数据库报错
| 数据库 | 错误代码 | 错误描述 | 示例 | 
|---|---|---|---|
| MySQL | 1064 | SQL语法错误 | ERROR 1064 (42000): 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 '...' at line X | 
| Oracle | ORA-00933 | SQL命令未正确结束 | ORA-00933: SQL command not properly ended | 
| Oracle | ORA-00904 | 无效的标识符 | ORA-00904: "COLUMN_NAME": invalid identifier | 
| SQL Server | Msg 102 | 语法错误 | Msg 102, Level 15, State 1, Line X Incorrect syntax near '...'. | 
| PostgreSQL | ERROR | 语法错误 | ERROR: syntax error at or near "..." LINE X: ... | 
(3) 注释符
以下是一个表格,展示了各个数据库系统中常用的注释符:
| 数据库 | 单行注释符 | 多行注释符 | 
|---|---|---|
| MySQL | #或--(注意--后面有一个空格) | /* ... */ | 
| Oracle | -- | /* ... */ | 
| SQL Server | -- | /* ... */ | 
| PostgreSQL | -- | /* ... */ | 
| Access | 无直接注释符,通常在代码编辑器中使用注释功能 | 无直接注释符 | 
4.5 数据库特性
-  information_schema.schemata 存放所有数据库名,schema_name为数据库名字段 
-  information_schema.tables 存放所有数据库的表名,table_schema存放数据库名,table_name字段存放表名 
-  information_schema.columns 存放所有数据库表的所有列名,table_schema 库名,table_name表名,column_name列 
4.6 SQL注入常规思路(MySQL)
-  判断注入点,检测网站脆弱性 
-  判断数据库类型和版本 借助数据库特性进行判断,如数据库支持的函数、符号等 
-  order by 判断列数 
-  找到可以显示数据的位置 
-  爆库名 
-  爆表名 
-  显示admin表中的字段名 
-  显示字段内容 
4.7 sql注入靶场
-  SQL注入在线靶场:RedTiger's Hackit 
-  DVWA:https://dvwa.co.uk/ 
-  sqli-labs源码:https://github.com/Audi-1/sqli-labs 
5 SQL注入漏洞审计
执行对象是SQL的执行者。 目前常用的执行对象接口有三种: Statement、PreparedStatement和
CallableStatement 。
5.1 JDBC
5.1.1 Statement
Statement 主要用于执行静态SQL语句,即内容固定不变的SQL语句。Statement 每执行一次都要对传入的SQL语句编译一次,效率较低 。
例如:
String name = "tom";
String sqlString = "select * from student_table where student_name = '" + name + "'";
Connection conn = open();
ResultSet rs = null;
Statement stmt = conn.createStatement();
rs = stmt.executeQuery(sqlString);
System.out.println(stmt);
while (rs.next()){
System.out.println("student_id:" + rs.getInt("student_id") + "\t"
+ "student_name:" + rs.getString("student_name") + "\t"
+ "student_age:" + rs.getString("student_age") + "\t"
+ "student_tele:" + rs.getString("student_tele") + "\t");}
}
 
若 name 为用户可控参数,当用户传入 ' or 1 = 1 # 则sqlString为:select * from student_table where student_name = ' ' or 1 = 1 # ', 可以看到程序输出 student_table 表中所有条目.

5.1.2 PreparedStatement
PreparedStatement是预编译参数化查询执行SQL语句的方式。
例如
String sql = "select * from student_table where name = ?"; Connection conn = open(); PreparedStatement pstmt = (PreparedStatement) conn.prepareStatement(sql); //对占位符进行初始化 pstmt.setString(1, "tom"); pstmt.executeQuery();
看到使用PreparedStatement之后,特殊符号被转义,无法按设想的SQL语句执行了。



看到使用PreparedStatement之后,特殊符号被转义,无法按设想的SQL语句执行了。
早期开发为了满足需求,不得已的情况直接使用普通对象进行了变量的拼接执行了sql
5.1.3CallableStatement
SQL 语句
-- 创建表 CREATE TABLE student_table ( student_id INT PRIMARY KEY, student_name VARCHAR(50) NOT NULL, student_age INT NOT NULL, student_tele VARCHAR(15) NOT NULL );  -- 插入数据 INSERT INTO student_table (student_id, student_name, student_age, student_tele) VALUES (1, 'Tom', 20, '123-456-7890'); INSERT INTO student_table (student_id, student_name, student_age, student_tele) VALUES (2, 'Alice', 22, '234-567-8901'); INSERT INTO student_table (student_id, student_name, student_age, student_tele) VALUES (3, 'Bob', 23, '345-678-9012'); INSERT INTO student_table (student_id, student_name, student_age, student_tele) VALUES (4, 'Charlie', 21, '456-789-0123'); 
Java 示例代码
import java.sql.*;
import java.util.Scanner;
public class CallableStatementSQLInjectionExample {
    public static void main(String[] args) {
        search();
    }
    static void search() {
        String sql = "{CALL getStudentByName('" + getUserInput() + "')}";
        System.out.println("SQL Statement: " + sql);
        Connection conn = null;
        CallableStatement callableStmt = null;
        try {
            conn = Conn.open(); // 假设 Conn 是一个提供数据库连接的类
            callableStmt = conn.prepareCall(sql);
            // 执行存储过程
            ResultSet rs = callableStmt.executeQuery();
            // 处理结果集
            while (rs.next()) {
                System.out.println("student_id:" + rs.getInt("student_id") + "\t"
                        + "student_name:" + rs.getString("student_name") + "\t"
                        + "student_age:" + rs.getInt("student_age") + "\t"
                        + "student_tele:" + rs.getString("student_tele"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (callableStmt != null) callableStmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    static String getUserInput() {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter student name: ");
        return scanner.nextLine();
    }
}
 
5.2 MyBatis框架介绍
5.2.1 MyBatis了解
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免 了几
乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注 解来配置
和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对 象)为数据库
中的记录。
官方教程:https://mybatis.org/mybatis-3/zh/getting-started.htm
5.2.2 MyBatis基本使用--基于xml实现
实体类Student 各个参数与数据库中目标表的列名一一对应,包括参数名、参数类型

Dao接口文件:
package mybatis.dao;
import mybatis.pojo.Student;
public interface StudentDao {
public Student selectStudent(@Param("name")String name);
} 
Mapper xml SQL语句映射文件 :
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper 
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 这是一个MyBatis的Mapper文件,定义了与StudentDao接口相关的SQL映射 -->  
<mapper namespace="mybatis.dao.StudentDao">
     <!-- 定义一个名为selectStudent的查询语句,用于从student_table表中查询学生信息 -->  
    <!-- resultType指定了查询结果的Java类型,即mybatis.pojo.Student -->  
    <select id="selectStudent" resultType="mybatis.pojo.Student">
        select * from student_table where student_name = #{name}
        <!-- #{name}是一个参数占位符,用于动态传入学生名称进行查询 -->  
    </select>
    <!-- 这里可以添加更多的SQL映射语句,例如插入、更新、删除等 --> 
</mapper>
 
MyBatis xml 配置文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- <properties resource="config.properties"/>--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/Demo?useSSL=false" /> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> <mappers> <mapper resource="mybatis/StudentMapper.xml"/> </mappers> </configuration>
事务管理器(transactionManager)
• 数据源(dataSource)类型有三种:
UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。有点慢,但对那些数据库 连接可用性要求不高的简单应用程序来说,是一个很好的选择。 POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的 连接实例时所必需的初始化和认证时间。
JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在 外部配置数据源,然后放置一个 JNDI 上下文的引用。
测试类
//1. 读取配置文件
String resource = "mybatis/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//2. 创建SqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(inputStream);
//3. 使用SqlSessionFactory工厂生产SqlSession对象
SqlSession session = sqlSessionFactory.openSession();
try {
//4. 使用SqlSession创建Dao接口的代理对象
StudentDao studentMapper = session.getMapper(StudentDao.class);
//5. 使用代理对象执行方法
Student student = studentMapper.selectStudent("tom");
System.out.println(student);
} finally {
session.close();
inputStream.close();
} 
5.3 MyBatis中注入问题
5.3.1 动态SQL
动态 SQL 是 mybatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在查询之前
mybatis 会对其进行动态解析。 Mybatis框架中,接受用户参数有两种方式:
通过${param}方式
通过#{param}方式
#{ }会自动传入值加上单引号,而${ }不会。
例如:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.dao.StudentDao">
<select id="selectStudent" resultType="mybatis.pojo.Student">
select * from student_table where student_name = #{name}
</select>
</mapper> 
传入name为"tom”

实际提交的SQL为: select * from student_table where student_name = 'tom'
例如:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.dao.StudentDao">
<select id="selectStudent" resultType="mybatis.pojo.Student">
select * from student_table where student_name = ${name}
</select>
</mapper> 
此时提交name为“tom”,会直接报错, 因为SQL语句中没有给name加引号: select * from student_table where student_name = tom

5.3.2 不安全的${ }
修改mapper文件,拼接引号:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.dao.StudentDao">
<select id="selectStudent" resultType="mybatis.pojo.Student">
select * from student_table where student_name = '${name}'
</select>
</mapper> 
提交 ' or 1=1 # , SQL语句成为: select * from student_table where student_name = '' or 1=1 #'

SQL注入防御- 使用#{ }
Mybatis 会处理为: select * from student_table where student_name = ?

5.4 java常见注入场景分析
5.4.1 like 模糊匹配
例如:
<?xml version="1.0" encoding="UTF-8" ?>
2 <!DOCTYPE mapper
3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5 <mapper namespace="mybatis.dao.StudentDao">
6 <select id="selectStudent" resultType="mybatis.pojo.Student">
7 select * from student_table where student_name like '%#{name}%'
8 </select>
9 </mapper> 
Mybatis便会处理为: select * from student_table where student_name like '%?%'

常见的解决方案[反例】:
例如:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.dao.StudentDao">
<select id="selectStudent" resultType="mybatis.pojo.Student">
select * from student_table where student_name like '%${name}%'
</select>
</mapper> 
拼接为: select * from student_table where student_name like '%tom%

常见的解决方案[正解】:
#{ } 正确的写法
select * from student_table where student_name like concat('%',#{name}, '%')
最终提交的SQL语句是: select * from student_table where student_name like concat('%','tom', '%')
5.4.2 IN语句
例如: dao文件:定义id为String类型
public interface StudentDao {
public List<Student> selectStudent(@Param("id")String id);
} 
Mapper文件:对用户输入预编译:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.dao.StudentDao">
<select id="selectStudent" resultType="mybatis.pojo.Student">
select * from student_table where student_id in (#{id})
</select>
</mapper> 
提交 id=“1,2”,实际提交的SQL语句为: select * from student_table where student_id in ('1,2');

修改Mapper文件:改为拼接的方式接收用户输入(${id}) 此时实际提交的SQL语句是:
select * from student_table where student_id in (1,2)

注入测试 :
1) or 1=1 # SQL语句拼接成: select * from student_table where student_id in (1) or 1=1#)

解决方案: #{ } 正确的写法
首先要知道IN后的参数中有多少个元素,然后在语句中写入相同个数的占位符?,这一点在直接 使用
PreparedStatement时非常繁琐,需要编写额外的逻辑获取中元素个数后拼接占位符。
SELECT * FROM student_table WHERE id IN (?, ?, ?, ?)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.dao.StudentDao">
<select id="selectStudent" resultType="mybatis.pojo.Student">
select * from student_table where student_id in
<foreach collection="list" item="id" index="index" open="(" close=")" separator=",">
#{id}
</foreach>
</select></mapper> 
dao文件:
public interface StudentDao {
public List selectStudent(@Param("list")List list);
} 
传入值为1和2的list变量,实际提交的SQL语句:
select * from student_table where student_id in ('1','2'); 顺利得到id为1和2的数据行。

5.4.3 order by语句
凡是字符串但又不能加引号的位置都不能预编译参数化,这种场景不仅order by,还有group by。执行SQL语句:select * from student_table order by student_age; 数据库表按照student_age列进行默认升序排序 Ø执行SQL语句: select * from student_table order by 'student_age'; 数据库表原样输出,没有任何排序,因为 order by 后跟一个字符串,为常量列排序,而常量列 所有值都相等,所以也就不会排序 。

例如: Mapper文件中定义SQL语句为 select * from student_table order by #{name} 提 name=student_age,因为#{ }会对用户提交的数据自动添加单引号,所以这里实际提交的 SQL语句为:
select * from student_table order by 'student_age'

当我们替换为${ },即SQL语句为: select * from student_table order by ${name} 提交 name=student_age,则实际提交的SQL语句如下,是没有添加单引号的: select * from student_table order by student_age 如愿按 student_age 字段执行排序 .

注入测试
if(1=1,student_age,student_tele) 实际提交的SQL语句为:
select * from student_table order by if(1=1,student_age,student_tele)
说明: order by 中使用数字代替列名是不行的,因为if语句返回的是字符类型,不是整型 即不能替换成 if(1=1,3,4)
SQL语句为: select * from student_table order by if(1=1,3,4)
防御方案
这种场景下,通常采用白名单,只开放有限集合,使用间接对象引用,如果传来的参数不在白名单列表中,直接返回错误即可。 例如: 设置一个字段/表名数组,仅允许用户传入索引值。这样保证传入的字段或者表名都在白名单里面。
5.4.4 group by语句
例如: mapper文件中SQL语句:
select student_id,student_age,max(student_tele) from student_table group by #{name}
dao文件:
public interface StudentDao {
public List<Student> selectStudent(@Param("name")String name);
} 
传入name= “student_age”,提交的SQL语句为:
select student_id,student_age,max(student_tele) from student_table group by 'student_age' 相当于常
量列,无法按指定列聚合并输出。
当我们替换为${ },即mapper文件中SQL语句为:
select student_id,student_age,max(student_tele) from student_table group by ${name}
传入name=“student_age”,提交的SQL语句为:
select student_id,student_age,max(student_tele) from student_table group by student_age。
这种场景下解决方案与order by场景一样。

6.4.5 总结
易产生SQL注入漏洞的场景有:
-  Like 模糊查询: Select * from student where name like ‘%${name}%’ 
-  IN 查询: Select * from news where id in (${id}) 
-  order by、group by 查询: select * from studentwhere id order by ${id} 
6.5 漏洞审计技巧和思路
1、需要关注的点:来自于前端的参数且用户可控且未安全处理后拼接到SQL中执行
2、发生场景:(1)在jdbc技术中直接使用“+”拼接 (2)在mybatis中使用$符号拼接 (3) 在hibernate中使用+
需要特别注意一下将从前端获取到的值作为数据sql语句执行的字段名称,而不仅仅是字段的value
String id = requrst.getParmtmter("id")
(1)不常见的sql注入表现形式id = " id = 1",select from user where + id 拼接之后 select * from user where id = 1;
假如id被黑客篡改,id = ‘ ,会导致注入风险(*)
(2)常见的sql注入表现形式:select * from user where + id1 = $id;
解决方案:(1)对于必须从前端获取的,梳理一份白名单【key,value】,从前端获得key,通过key从白名单中找到对应的value,将value拼接到sql中。
map = {id:id,id2:id,id3:id,id4:id,id5:id,id6:id}
Strig id = requrst.getParmtmter("id")
假如用户在此位置上,注入了id="id ’",发现id‘不在白名单的范围内,导致无法继续执行sql语句。
(4)因程序员有一定的安全意识,项目中直接拼接比较少,但是在in、like、order by、gruop by等关键字如何直接使用#等预编译处理会报错,所以才不得以使用了拼接,成为了注入的高发点。
3、通过前端页面,代码生成(SQL注入高频爆发点)功能疑似存在SQL注入
4、定位到后端分析相关SQL的执行过程
关于白洁提出的针对order by、group by 白名单修复意见的说明:
1、正常其后应该加的是字段、是因为第一这个字段来自于前端,第二使用了拼接、第三参数可控
2、如果使用#满足不了功能或者是报错,所以程序员不得以使用了拼接
3、允许使用了拼接,但是需要通过白名单修复
(1)一般情况下,传的字段并且固定,允许前来的参数,但是不能直接拼接。
(2)需要在后端匹配与其对应的value,用前端的参数作为key匹配value,最后将value拼接到参数中
Strting name = request.getParemeter("name");
String value = map.getKey("name");
String sql = select * from user order by value;
假如,黑客在前端注入了sql脚本,会导致该Key找不到value,那就是说没办法发生注入了,直接返回给前端输入不合法。


















![[YOLOv10涨点改进:注意力魔改 | 轻量级的 Mixed Local Channel Attention (MLCA),加强通道信息和空间信息提取能力]](https://img-blog.csdnimg.cn/direct/2d20cc2b10ac43bfb0a506d6089ffdfa.png)

