SQL注入攻击与防御
约 1740 字大约 6 分钟
sql-injectionsecurity
2025-08-13
概述
SQL 注入(SQL Injection, SQLi)是最经典也最危险的 Web 安全漏洞之一,攻击者通过在用户输入中插入恶意 SQL 代码,从而操控后端数据库执行未授权的操作。OWASP 将其列为 Web 应用安全风险第一名长达数十年。
SQL 注入原理
注入类型详解
1. 经典注入(Union-Based)
-- 原始查询
SELECT id, name, email FROM users WHERE id = {user_input}
-- 攻击者输入: 1 UNION SELECT username, password, credit_card FROM admin_users--
-- 最终执行:
SELECT id, name, email FROM users WHERE id = 1
UNION SELECT username, password, credit_card FROM admin_users--2. 报错注入(Error-Based)
-- 利用数据库错误消息泄露信息
-- MySQL 示例
' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version()), 0x7e))--
-- 错误信息: XPATH syntax error: '~5.7.33~'
-- PostgreSQL 示例
' AND 1=CAST((SELECT version()) AS int)--
-- 错误信息: invalid input syntax for integer: "PostgreSQL 14.1"3. 盲注(Blind Injection)
当页面不显示错误信息时,通过布尔条件或时间延迟推断数据。
-- 布尔盲注:观察页面响应差异
' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a'--
-- 页面正常 → 第一个字符是 'a'
-- 页面异常 → 第一个字符不是 'a'
-- 时间盲注:通过响应时间推断
' AND IF(SUBSTRING(password,1,1)='a', SLEEP(5), 0)--
-- 响应延迟 5 秒 → 猜测正确
-- 立即响应 → 猜测错误4. 堆叠查询(Stacked Queries)
-- 某些数据库/驱动支持分号分隔多条语句
'; DROP TABLE users;--
'; INSERT INTO admin_users VALUES('hacker','password');--防御措施
1. 参数化查询(最重要的防御)
参数化查询将 SQL 结构和数据分离,数据库驱动会自动处理转义。
# Python - 使用参数化查询
import psycopg2
conn = psycopg2.connect(database="mydb")
cursor = conn.cursor()
# 正确:参数化查询(占位符由驱动处理)
username = request.form['username']
cursor.execute(
"SELECT id, name, email FROM users WHERE username = %s AND active = %s",
(username, True)
)
# 错误:字符串拼接(SQL 注入漏洞)
# cursor.execute(f"SELECT * FROM users WHERE username = '{username}'")// Java - PreparedStatement
String sql = "SELECT id, name, email FROM users WHERE username = ? AND role = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, username); // 自动处理转义
stmt.setString(2, role);
ResultSet rs = stmt.executeQuery();// Node.js - 使用参数化查询
const { Pool } = require('pg');
const pool = new Pool();
// 正确
const result = await pool.query(
'SELECT id, name FROM users WHERE email = $1 AND status = $2',
[email, 'active']
);
// 使用 mysql2
const [rows] = await connection.execute(
'SELECT * FROM users WHERE id = ? AND name = ?',
[userId, userName]
);2. ORM 的安全使用
# SQLAlchemy - ORM 查询天然使用参数化
from sqlalchemy import select
from models import User
# 安全:ORM 查询
stmt = select(User).where(User.username == username)
result = session.execute(stmt).scalars().first()
# 安全:ORM 的 filter
users = session.query(User).filter(User.email == email).all()
# 危险:ORM 中使用 raw SQL 拼接
# session.execute(f"SELECT * FROM users WHERE name = '{name}'")
# 安全:ORM 中使用参数化 raw SQL
from sqlalchemy import text
stmt = text("SELECT * FROM users WHERE name = :name AND role = :role")
result = session.execute(stmt, {"name": name, "role": role})# Django ORM
from django.db.models import Q
# 安全:ORM 查询
users = User.objects.filter(username=username, is_active=True)
users = User.objects.filter(Q(role='admin') | Q(role='superadmin'))
# 安全:参数化 raw SQL
users = User.objects.raw(
"SELECT * FROM auth_user WHERE username = %s", [username]
)
# 危险:raw SQL 拼接
# User.objects.raw(f"SELECT * FROM auth_user WHERE username = '{username}'")
# 危险:extra() 中的不安全使用
# User.objects.extra(where=[f"username = '{username}'"])3. 存储过程
-- 存储过程可以限制 SQL 操作范围
CREATE PROCEDURE GetUserByName(IN p_username VARCHAR(50))
BEGIN
SELECT id, name, email
FROM users
WHERE username = p_username;
-- 参数被作为数据处理,而非 SQL 代码
END;
-- 但存储过程中的动态 SQL 同样存在注入风险
-- 危险示例
CREATE PROCEDURE SearchUsers(IN p_search VARCHAR(100))
BEGIN
SET @sql = CONCAT('SELECT * FROM users WHERE name LIKE ''%', p_search, '%''');
PREPARE stmt FROM @sql; -- 动态 SQL 拼接 = 注入风险
EXECUTE stmt;
END;4. 输入验证与白名单
# 对于不能使用参数化的场景(如表名、列名排序)
# 使用严格的白名单验证
ALLOWED_SORT_COLUMNS = {'name', 'email', 'created_at', 'id'}
ALLOWED_SORT_ORDERS = {'ASC', 'DESC'}
def safe_query_with_sort(sort_column: str, sort_order: str):
if sort_column not in ALLOWED_SORT_COLUMNS:
raise ValueError(f"Invalid sort column: {sort_column}")
if sort_order.upper() not in ALLOWED_SORT_ORDERS:
raise ValueError(f"Invalid sort order: {sort_order}")
# 白名单验证后,拼接是安全的
query = f"SELECT id, name FROM users ORDER BY {sort_column} {sort_order}"
cursor.execute(query)5. 最小权限原则
-- 应用数据库账户不应有管理员权限
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'strong_password';
-- 只授予必要的权限
GRANT SELECT, INSERT, UPDATE ON mydb.users TO 'app_user'@'localhost';
GRANT SELECT ON mydb.products TO 'app_user'@'localhost';
-- 禁止危险权限
-- GRANT ALL PRIVILEGES ON *.* TO 'app_user' -- 绝不要这样做
-- GRANT FILE, PROCESS, SUPER ON *.* TO 'app_user' -- 危险6. WAF 规则
# ModSecurity (Nginx/Apache WAF) 基础规则
# 检测常见的 SQL 注入模式
SecRule ARGS "@detectSQLi" \
"id:1,\
phase:2,\
deny,\
status:403,\
msg:'SQL Injection Detected',\
log,\
tag:'attack-sqli'"
# OWASP Core Rule Set (CRS) 提供了全面的 SQL 注入检测规则SQLMap 检测
# sqlmap 是最流行的 SQL 注入自动化检测工具
# 基础检测
sqlmap -u "https://example.com/api/user?id=1" --batch
# 指定参数检测
sqlmap -u "https://example.com/api/search" --data="q=test&page=1" -p q
# POST JSON 检测
sqlmap -u "https://example.com/api/login" \
--data='{"username":"test","password":"test"}' \
--headers="Content-Type: application/json"
# 列出数据库
sqlmap -u "https://example.com/api/user?id=1" --dbs
# 使用 tamper 脚本绕过 WAF
sqlmap -u "https://example.com/api/user?id=1" \
--tamper=space2comment,between,randomcase防御层次
最佳实践总结
- 始终使用参数化查询,这是防御 SQL 注入的第一道也是最重要的防线
- 使用 ORM 查询,避免手写 SQL,ORM 的查询构建器默认使用参数化
- 对动态表名/列名使用严格白名单,不允许用户控制 SQL 结构
- 数据库连接使用最小权限账户,应用账户不应拥有 DROP/ALTER 权限
- 生产环境关闭详细错误信息,防止错误注入泄露数据库结构
- 部署 WAF 作为额外防线,使用 OWASP Core Rule Set
- 定期使用 sqlmap 等工具进行安全测试
- 使用预编译的存储过程,但注意存储过程内部也要避免动态 SQL 拼接
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于