一、什么是SQL注入(SQL Injection)
1. 核心定义
SQL注入是一种恶意的网络攻击手段,攻击者通过在请求参数中插入特殊的SQL语句片段(如引号、关键字OR/AND/DROP等),让数据库执行原本未预期的SQL命令,从而实现窃取数据、修改数据、删除表甚至控制数据库服务器的目的。
2. 通俗举例(结合你的插入接口)
假设你的代码没有使用参数化查询,而是用字符串拼接的方式构造SQL:
# 危险:字符串拼接构造SQL(极易被SQL注入)
insert_sql = f"INSERT INTO users (name) VALUES ('{user_name}');"
此时攻击者在请求中传入这样的name值:
李四'); DROP TABLE users; --
拼接后的SQL语句就会变成:
INSERT INTO users (name) VALUES ('李四'); DROP TABLE users; -- ');
我们来拆解这个恶意SQL的执行逻辑:
'李四');:先正常完成一条插入语句(闭合前面的单引号,结束插入语句);DROP TABLE users;:执行删除users表的危险命令;--:SQL中的注释符,将后面剩余的'注释掉,避免语法错误。
最终结果:users表被直接删除,数据全部丢失,这就是典型的SQL注入攻击(破坏性极强)。
3. SQL注入的危害等级
SQL注入属于高危安全漏洞(OWASP Top 10 常年排名前列),常见危害包括:
- 窃取敏感数据(如用户密码、数据库配置信息);
- 篡改数据库数据(如修改用户余额、伪造用户信息);
- 删除数据库表/库(如
DROP TABLE/DROP DATABASE); - 提权操作(如获取数据库管理员权限,控制整个服务器)。
二、再理解:你的参数化查询如何避免SQL注入
你的代码cursor.execute(insert_sql, (user_name,))是标准的参数化查询,它通过「先编译SQL模板,再传入参数」的机制,从根源上阻断了SQL注入,具体拆解如下:
步骤1:明确参数化查询的两个核心部分
你的代码中,参数化查询分为两个独立的部分,永远不会拼接成一个完整的字符串:
insert_sql = "INSERT INTO users (name) VALUES (%s);":SQL模板(占位符%s代替实际参数值);(user_name,):实际参数(元组格式,存放要插入的用户名)。
步骤2:参数化查询的执行流程(关键防注入原理)
数据库执行参数化查询时,会严格按照「先编译,后传参」的顺序执行,和字符串拼接有本质区别:
-
第一步:编译SQL模板
数据库先接收INSERT INTO users (name) VALUES (%s);这个SQL模板,对其进行语法解析和编译,确定SQL的执行逻辑(就是「插入一条数据到users表的name字段」),此时%s只是一个纯粹的「参数占位符」,不具备任何SQL语法含义。- 这个阶段,数据库已经明确了「要做什么」(插入数据),不会因为后续的参数而改变执行逻辑。
-
第二步:传入并转义参数值
数据库再接收(user_name,)中的实际参数值,将其作为「纯粹的字符串数据」填充到已编译好的SQL模板的%s位置,同时会自动对参数值中的特殊字符(如'、;、--等)进行转义处理。- 举例:攻击者传入
李四'); DROP TABLE users; --,数据库会将其转义为李四\'); DROP TABLE users; --(不同数据库转义方式略有差异); - 转义后,特殊字符失去了SQL语法含义,仅作为普通字符串的一部分存储到数据库中,不会被当作SQL命令执行。
- 举例:攻击者传入
步骤3:对比字符串拼接和参数化查询(核心差异)
| 对比项 | 字符串拼接(危险) | 参数化查询(安全) |
|---|---|---|
| 执行顺序 | 先拼接成完整SQL,再编译执行 | 先编译SQL模板,再传入参数 |
| 参数的角色 | 作为SQL语句的一部分,参与语法解析 | 作为纯粹的数据,不参与SQL语法解析 |
| 特殊字符处理 | 不转义,特殊字符具备SQL语法含义 | 自动转义,特殊字符仅作为普通字符串 |
| 执行逻辑是否可变 | 可被参数篡改,执行恶意SQL | 不可变,仅执行预编译的逻辑(插入/查询等) |
| 能否被SQL注入 | 极易被注入 | 从根源上阻断SQL注入 |
4. 你的代码执行结果(攻击者参数无效化)
对于攻击者传入的李四'); DROP TABLE users; --,你的参数化查询执行后,最终存入users表name字段的内容就是李四'); DROP TABLE users; --(普通字符串),而不会执行DROP TABLE命令,完美避免了SQL注入。
三、关键补充:关于参数化查询的注意点
-
占位符的格式:不同数据库/驱动的占位符格式不同,你的代码中
%s是pymysql(MySQL驱动)的占位符,其他常见格式:- PostgreSQL(
psycopg2):%s(和MySQL一致); - SQLite(
sqlite3):?; - SQL Server(
pyodbc):?或%s。
核心:不要手动替换占位符,必须通过cursor.execute()的第二个参数传入。
- PostgreSQL(
-
参数必须是元组/列表格式:你的代码中
(user_name,)是一个元组(末尾的逗号不能少,否则不是元组),也可以用列表[user_name],pymysql会自动解析其中的参数,切忌直接传入字符串user_name。 -
仅对参数有效,对SQL关键字/表名/字段名无效:参数化查询的占位符
%s只能替代「参数值」,不能替代SQL关键字(如SELECT/INSERT)、表名(如users)、字段名(如name),例如:# 无效:用%s替代表名,依然有注入风险 sql = "INSERT INTO %s (name) VALUES (%s);" cursor.execute(sql, ("users", "李四")) # 表名不能用参数化占位符核心:表名、字段名若需动态指定,需手动做白名单校验(如只允许指定的几个表名),不能直接接收用户输入。
四、总结
- SQL注入:攻击者通过在参数中插入特殊SQL片段,篡改原本的SQL执行逻辑,实现恶意操作的高危攻击;
- 参数化查询防注入核心:「先编译SQL模板,后传入并转义参数」,参数仅作为纯粹数据,不参与SQL语法解析;
- 你的代码为何安全:
%s是占位符,(user_name,)是独立参数,数据库自动转义特殊字符,阻断了SQL注入; - 开发准则:凡是涉及用户输入(或外部传入参数)的SQL操作,必须使用参数化查询,禁止字符串拼接。
26万+

被折叠的 条评论
为什么被折叠?



