什么是 SQL 注入?
SQL 注入就是在用户输入的字符串中加入 SQL 语句,如果在设计不良的程序中忽略了检查,那么这些注入进去的 SQL 语句就会被数据库服务器误认为是正常的 SQL 语句而运行,攻击者就可以执行计划外的命令或访问未被授权的数据。
SQL注入的原理
SQL 注入的原理主要有以下 4 点:
1.恶意拼接查询
我们知道,SQL 语句可以查询、插入、更新和删除数据,且使用分号来分隔不同的命令。例如:
SELECT * FROM users WHERE user_id = $user_id
其中,user_id 是传入的参数,如果传入的参数值为"1234; DELETE FROM users",那么最终的查询语句会变为:
SELECT * FROM users WHERE user_id = 1234; DELETE FROM users
如果以上语句执行,则会删除 users 表中的所有数据。
2.利用注释执行非法命令
SQL 语句中可以插入注释。例如:
SELECT COUNT(*) AS 'num' FROM game_score WHERE game_id=24411 AND version=$version
如果 version 包含了恶意的字符串'-1' OR 1 AND SLEEP(500)--
,那么最终查询语句会变为:
SELECT COUNT(*) AS 'num' FROM game_score WHERE game_id=24411 AND version='-1' OR 1 AND SLEEP(500)--
攻击者意图:
检测 SQL 注入漏洞 :攻击者通过构造
OR 3 AND SLEEP(500)
这样的条件,试图让数据库执行SLEEP(500)
函数。如果数据库执行了SLEEP(500)
,说明目标存在 SQL 注入漏洞。延迟响应 :
SLEEP(500)
会让数据库休眠 500 秒,导致应用程序的响应时间显著增加。攻击者可以通过观察响应时间来判断是否存在漏洞。
3.传入非法参数
SQL 语句中传入的字符串参数是用单引号引起来的,如果字符串本身包含单引号而没有被处理,那么可能会篡改原本 SQL 语句的作用。 例如:
SELECT * FROM user_name WHERE user_name = $user_name
如果 user_name 传入参数值为 G'chen,那么最终的查询语句会变为:
SELECT * FROM user_name WHERE user_name ='G'chen'
字符串 'G'chen'
中的单引号没有正确转义,导致 SQL 解析器认为字符串在 'G'
处结束,而 chen'
是多余的语法。如果用户输入 ' OR '1'='1
,则 SQL 语句变为:
SELECT * FROM user_name WHERE user_name = '' OR '1'='1'
WHERE
条件变为 user_name = '' OR '1'='1'
,这是一个恒真条件。数据库会返回 user_name
表中的所有数据,导致信息泄露。
4.添加额外条件
在 SQL 语句中添加一些额外条件,以此来改变执行行为。条件一般为真值表达式。例如:
UPDATE users SET userpass='userpass' WHERE user_id=user_id;
如果 user_id 被传入恶意的字符串"1234 OR TRUE",那么最终的 SQL 语句会变为:
UPDATE users SET userpass= '123456' WHERE user_id=1234 OR TRUE;
这将更改所有用户的密码。
避免SQL注入
1. 过滤输入内容,校验字符串
过滤输入内容就是在数据提交到数据库之前,就把用户输入中的不合法字符剔除掉。可以使用编程语言提供的处理函数或自己的处理函数来进行过滤,还可以使用正则表达式匹配安全的字符串。
如果值属于特定的类型或有具体的格式,那么在拼接 SQL 语句之前就要进行校验,验证其有效性。比如对于某个传入的值,如果可以确定是整型,则要判断它是否为整型,在浏览器端(客户端)和服务器端都需要进行验证。
2. 参数化查询
参数化查询目前被视作是预防 SQL 注入攻击最有效的方法。参数化查询是指在设计与数据库连接并访问数据时,在需要填入数值或数据的地方,使用参数来给值。
MySQL 的参数格式是以"?"字符加上参数名称而成,如下所示:
UPDATE myTable SET c1 = ?c1, c2 = ?c2, c3 = ?c3 WHERE c4 = ?c4
在使用参数化查询的情况下,数据库服务器不会将参数的内容视为 SQL 语句的一部分来进行处理,而是在数据库完成 SQL 语句的编译之后,才套用参数运行。因此就算参数中含有破坏性的指令,也不会被数据库所运行。
使用预编译的SQL语句语义不会发生改变,在SQL语句中,变量用问号?表示,黑客即使本事再大,也无法改变SQL语句的结构。简单总结,参数化能防注入的原因在于,语句是语句,参数是参数,参数的值并不是语句的一部分,数据库只按语句的语义跑,至于跑的时候是带一个普通背包还是一个怪物,不会影响行进路线。
3. 安全测试、安全审计
除了开发规范,还需要合适的工具来确保代码的安全。我们应该在开发过程中应对代码进行审查,在测试环节使用工具进行扫描,上线后定期扫描安全漏洞。通过多个环节的检查,一般是可以避免 SQL 注入的。
下面是在开发过程中可以避免 SQL 注入的一些方法。
1. 避免使用动态SQL
避免将用户的输入数据直接放入 SQL 语句中,最好使用准备好的语句和参数化查询,这样更安全。
在 MySQL 中,可以使用 PREPARE
、EXECUTE
和 DEALLOCATE PREPARE
语句实现参数化查询。
sql
-- 准备 SQL 语句
PREPARE stmt FROM 'SELECT * FROM users WHERE username = ? AND password = ?';
-- 绑定参数
SET @username = 'admin';
SET @password = 'password123';
-- 执行 SQL 语句
EXECUTE stmt USING @username, @password;
-- 释放预处理语句
DEALLOCATE PREPARE stmt;
2. 不要将敏感数据保留在纯文本中
加密存储在数据库中的私有/机密数据,这样可以提供了另一级保护,以防攻击者成功地排出敏感数据。
3. 限制数据库权限和特权
将数据库用户的功能设置为最低要求;这将限制攻击者在设法获取访问权限时可以执行的操作。
4. 避免直接向用户显示数据库错误
攻击者可以使用这些错误消息来获取有关数据库的信息。
SQL 预编译?
1.预编译语句是什么?
通常我们的一条sql在db接受到最终执行完毕返回可以分为三个过程:词法和语法解析;优化sql语句,制定执行计划;执行并返回结果。
但是很多情况,我们的一条sql语句可能会反复执行,或者每次执行的时候只有个别的值不同(比如query的where子句值不同,update的set子句值不同,insert的values值不同)。如果每次都需要经过上面的词法和语法解析、制定执行计划等,则效率就明显不行了。
所谓预编译语句就是将这类语句中的值用占位符替代,可以视为将sql语句模板化或者参数化,一般称这类语句叫 Prepared Statements 或者 Parameterized Statements。预编译语句的优势归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止 sql 注入。
2.预编译语句
(1)建一张测试表 t
sql
Create Table: CREATE TABLE `t` (
`a` int(11) DEFAULT NULL,
`b` varchar(20) DEFAULT NULL,
UNIQUE KEY `ab` (`a`,`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
(2)编译
通过 PREPARE stmt_name FROM perpare_stm 的语法来预编译一条 sql 语句
sql
PREPARE ins FROM 'INSERT INTO t VALUES (?,?)';
(3)执行
通过 EXECUTE stmt_name [USING @var_name [,@var_name]...]的语法来执行预编译语句
sql
SET @a=999,@b='hello';
EXECUTE ins USING @a,@b;
此时数据已经插入。
MySQL中的预编译语句作用域是会话级,但可以通过 max_prepared_stmt_count 变量来控制全局最大的存储的预编译语句数量:
sql
SET @@global.max_prepared_stmt_count=1;
/*此时设置预编译最大条数为1,如果继续使用预编译,就会报错*/
(4)释放
如果我们想要释放一条预编译语句,则可以使用{DEALLOCATE | DROP} PREPARE stmt_name的语法进行操作:
sql
DEALLOCATE prepare ins;
拷贝表的几种方式?
方式一:拷贝表结构,但是没有数据
create table 表1 like 表2;
方式二:拷贝数据,但是不会有主键和索引
create table 表1 as(select * from 表2);
方式三:又要有表结构,又要有数据
create table 表1 like 表2; insert into 表1 select * from 表2;
方式四:假如你想要一部分数据
insert into 表1 (select * from 表2 where 条件);
什么是水平分表与垂直分表?
水平分表 和 垂直分表 是数据库分表的两种常见策略,用于解决单表数据量过大导致的性能问题。它们的主要区别在于数据的分割方式:
水平分表
水平分表是指将一张表中的数据 按行 分割到多个结构相同的表中。每个表存储一部分数据,所有表的表结构完全相同。
特点:
按行分割:将表中的数据按某种规则(如范围、哈希、列表等)分配到多个表中。
表结构相同:所有分表的表结构完全相同
适用场景:
单表数据量过大,导致查询、插入、更新等操作变慢。
例如,按时间范围(如按月、按年)或用户 ID 范围分表。
示例 :假设有一张用户表
user
,数据量过大,可以按用户 ID 的范围进行水平分表:
user_0
:存储user_id
在 1-10000 的用户。
user_1
:存储user_id
在 10001-20000 的用户。
user_2
:存储user_id
在 20001-30000 的用户。优点:
减少单表数据量,提高查询性能。
易于扩展,可以通过增加分表来支持更多数据。
缺点:
需要维护分表规则,查询时需要确定数据位于哪个分表。
跨分表的查询和聚合操作较复杂。
垂直分表
垂直分表是指将一张表中的数据 按列 分割到多个表中。每个表存储一部分列,所有表共享相同的主键。
特点:
按列分割:将表中的列按业务逻辑或访问频率分配到多个表中。
共享主键:所有分表共享相同的主键,用于关联数据。
适用场景:
表中包含大量列,但每次查询只访问部分列。
例如,将常用列和不常用列分开存储。
示例:
假设有一张用户表
user
,包含以下列:
user_id
、username
、password
、address
、phone
、created_at
。可以将表垂直分表为:
user_basic
:存储user_id
、username
、password
。
user_profile
:存储user_id
、address
、phone
、created_at
。优点:
减少单表的列数,提高查询性能。
按需加载数据,减少 I/O 开销。
缺点:
查询时需要关联多个表,增加查询复杂度。
需要维护表之间的关系。
场景题
mysql 一张表可以有多少行数据?
SQL 语句的执行顺序?
- **FROM 子句:**首先,SQL 查询会从 FROM 子句中指定的表中获取数据。如果涉及多个表,会计算这些表的笛卡尔积。
- **ON 筛选器:**如果查询中包含 JOIN 操作,ON 子句会用于筛选 JOIN 条件匹配的行。
- **JOIN 操作:**根据 JOIN 类型(如 INNER JOIN、LEFT JOIN 等),将符合条件的行组合在一起。
- **WHERE 子句:**对 FROM 和 JOIN 后的结果集进行筛选,只保留满足 WHERE 条件的行。
- **GROUP BY 子句:**将数据按指定的列进行分组,通常用于聚合操作。
- 聚合函数计算:对每个分组应用聚合函数(如 COUNT、SUM、AVG 等)进行计算。
- **HAVING 子句:**对分组后的结果进行二次筛选,只保留满足 HAVING 条件的分组。
- **SELECT 子句:**选择要返回的列或表达式。
- **DISTINCT 关键字:**去除结果集中的重复行。
- **ORDER BY 子句:**对最终的结果集进行排序。
- **LIMIT:**查询指定范围
MySQL 字符集?
1.MySQL 默认字符集
-
MySQL 5.7 及之前版本 :默认字符集是
latin1
,这是一种单字节字符集,主要支持西欧语言。 -
MySQL 8.0 及之后版本 :默认字符集是
utf8mb4
,这是一种多字节字符集,支持更广泛的字符范围,包括表情符号(Emoji)。
2.MySQL常用字符集及特点
以下是 MySQL 中常用的字符集及其特点:
1. latin1
特点:
单字节字符集,支持西欧语言。
存储空间小,但不支持多语言。
使用场景:
- 仅需支持西欧语言的应用程序。
2. utf8
特点:
多字节字符集,支持大部分 Unicode 字符。
不支持 4 字节的 Unicode 字符(如表情符号)。
使用场景:
- 需要支持多语言的应用程序,但不包括表情符号。
3. utf8mb4
特点:
多字节字符集,支持完整的 Unicode 字符,包括表情符号。
是 MySQL 8.0 及之后版本的默认字符集。
使用场景:
- 需要支持多语言和表情符号的应用程序。
4. gbk
特点:
双字节字符集,支持简体中文。
存储空间较小,但不支持其他语言的字符。
使用场景:
- 仅需支持简体中文的应用程序。
5. big5
特点:
双字节字符集,支持繁体中文。
存储空间较小,但不支持其他语言的字符。
使用场景:
- 仅需支持繁体中文的应用程序。
3.什么是 Unicode 编码?
Unicode 编码 是一种字符编码标准,旨在支持全球所有语言的字符。Unicode 编码的字节数取决于具体的编码方式(如 UTF-8、UTF-16、UTF-32)。以下是常见的 Unicode 编码方式及其字节数的详细说明:
- UTF-8
特点:
可变长度编码,每个字符占用 1 到 4 个字节。
兼容 ASCII,ASCII 字符(0-127)仍占用 1 个字节。
字节数:
1 字节:ASCII 字符(0-127)。
2 字节:大部分拉丁字母、希腊字母、西里尔字母等。
3 字节:大部分常用汉字、日文、韩文等。
4 字节:不常用的字符、表情符号(Emoji)等。
示例:
字符
A
(ASCII):1 字节。字符
ä
(拉丁字母):2 字节。字符
中
(汉字):3 字节。字符
😊
(表情符号):4 字节。
- UTF-16
特点:
可变长度编码,每个字符占用 2 或 4 个字节。
基本多语言平面(BMP)中的字符占用 2 个字节,其他字符占用 4 个字节。
字节数:
2 字节:基本多语言平面(BMP)中的字符(如大部分常用汉字、拉丁字母等)。
4 字节:辅助平面中的字符(如不常用的汉字、表情符号等)。
示例:
字符
A
(ASCII):2 字节。字符
中
(汉字):2 字节。字符
😊
(表情符号):4 字节。
- UTF-32
特点:
固定长度编码,每个字符占用 4 个字节。
简单但存储空间较大。
字节数:
- 4 字节:所有字符。
示例:
字符
A
(ASCII):4 字节。字符
中
(汉字):4 字节。字符
😊
(表情符号):4 字节。
总结
编码方式 | 字节数 | 特点 |
---|---|---|
UTF-8 | 1 到 4 字节 | 可变长度,兼容 ASCII,存储空间较小,适合网络传输和存储。 |
UTF-16 | 2 或 4 字节 | 可变长度,适合需要处理大量非 ASCII 字符的场景。 |
UTF-32 | 4 字节 | 固定长度,简单但存储空间较大,适合需要快速随机访问字符的场景。 |
4.MySQL 中的 Unicode 编码
-
utf8
:- 支持 1 到 3 字节的 Unicode 字符,不支持 4 字节的字符(如表情符号)。
-
utf8mb4
:-
支持 1 到 4 字节的 Unicode 字符,包括表情符号。
-
是 MySQL 8.0 及之后版本的默认字符集。
-