SQL注入攻击的核心原理是攻击者将恶意SQL代码片段拼接进正常的查询语句中,绕过身份验证或者直接查询全量数据,最终导致数据库脱裤。要彻底防范这类风险,不能只做单点防护,需要结合注入阻断、数据加密、动态脱敏三层防护体系来落地。
一、从根源阻断SQL注入攻击
1. 使用参数化查询替代字符串拼接
字符串拼接是产生SQL注入的最主要原因,开发过程中要完全避免直接拼接用户输入到SQL语句中,优先使用参数化查询。以下是Java中使用PreparedStatement的示例:
// 错误写法:直接拼接用户输入,存在SQL注入风险
String username = request.getParameter("username");
String sql = "SELECT * FROM user WHERE username = '" + username + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// 正确写法:使用参数化查询,用户输入会被当作参数处理,不会拼接进SQL结构
String username = request.getParameter("username");
String sql = "SELECT * FROM user WHERE username = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
2. 输入校验与过滤
对用户输入的内容做严格校验,比如用户名只允许字母数字,手机号必须符合手机号格式,同时过滤掉常见的SQL注入关键字,比如单引号、分号、union、select等。以下是Python的输入校验示例:
import re
def validate_input(user_input):
# 只允许字母、数字、下划线,长度不超过20
pattern = r'^[a-zA-Z0-9_]{1,20}$'
if re.match(pattern, user_input):
return True
return False
user_input = "admin' OR 1=1 --"
if not validate_input(user_input):
print("输入内容不合法")
3. 最小权限原则
部署应用时使用的数据库账号不要赋予过高权限,比如只给查询权限的账号就不要授予增删改权限,即使发生注入攻击,攻击者也无法执行删表、修改数据等高危操作。
二、敏感数据加密存储
即使SQL注入防护被绕过,加密后的敏感数据也无法被攻击者直接使用,常用的加密方式分为不可逆加密和可逆加密两类。
1. 不可逆加密(哈希)
对于密码这类不需要还原的敏感数据,使用带盐值的哈希算法存储,比如SHA-256、bcrypt等,不要使用MD5等已经被破解的算法。以下是Node.js中使用bcrypt的示例:
const bcrypt = require('bcrypt');
// 生成盐值并哈希密码
async function hashPassword(password) {
const saltRounds = 10;
const hash = await bcrypt.hash(password, saltRounds);
return hash;
}
// 验证密码
async function verifyPassword(password, hash) {
const match = await bcrypt.compare(password, hash);
return match;
}
// 使用示例
hashPassword('user_password_123').then(hash => {
console.log('存储到数据库的哈希值:', hash);
});
2. 可逆加密(对称/非对称加密)
对于身份证号、手机号、银行卡号这类需要还原使用的敏感数据,使用AES等对称加密算法存储,密钥要单独存储,不要和数据库放在同一台服务器。以下是Go语言中使用AES加密的示例:
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
)
// AES加密
func encrypt(plaintext string, key []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return "", err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(plaintext))
return base64.URLEncoding.EncodeToString(ciphertext), nil
}
// AES解密
func decrypt(ciphertext string, key []byte) (string, error) {
data, err := base64.URLEncoding.DecodeString(ciphertext)
if err != nil {
return "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
iv := data[:aes.BlockSize]
data = data[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(data, data)
return string(data), nil
}
func main() {
key := []byte("example_key_16b") // 16、24、32字节分别对应AES-128、AES-192、AES-256
plaintext := "110101199001011234"
encrypted, _ := encrypt(plaintext, key)
fmt.Println("加密后存储的值:", encrypted)
decrypted, _ := decrypt(encrypted, key)
fmt.Println("解密后的值:", decrypted)
}
三、动态脱敏实施
动态脱敏是指在数据查询返回时,根据访问角色和场景自动对敏感字段做脱敏处理,比如普通运营人员查询用户列表时,身份证号只显示前6位和后4位,中间用星号替换,而风控人员可以查看完整数据。
1. 基于角色的脱敏规则配置
可以建立脱敏规则表,配置不同角色对应不同字段的脱敏方式,常见脱敏方式如下:
| 敏感字段类型 | 脱敏规则 | 脱敏后示例 |
|---|---|---|
| 手机号 | 保留前3位和后4位,中间替换为4个星号 | 138****1234 |
| 身份证号 | 保留前6位和后4位,中间替换为8个星号 | 110101********1234 |
| 银行卡号 | 保留后4位,前面替换为星号 | **** **** **** 1234 |
| 邮箱 | 保留@前第一个字符和域名,中间替换为星号 | a****@ipipp.com |
2. 应用层动态脱敏实现
在查询数据返回前,根据当前登录用户的角色,对敏感字段做脱敏处理,以下是Java中的脱敏工具类示例:
public class DesensitizedUtil {
// 手机号脱敏
public static String desensitizePhone(String phone) {
if (phone == null || phone.length() != 11) {
return phone;
}
return phone.substring(0, 3) + "****" + phone.substring(7);
}
// 身份证号脱敏
public static String desensitizeIdCard(String idCard) {
if (idCard == null || idCard.length() != 18) {
return idCard;
}
return idCard.substring(0, 6) + "********" + idCard.substring(14);
}
// 根据角色判断是否脱敏
public static String processSensitiveData(String data, String fieldType, String userRole) {
// 管理员角色返回原始数据
if ("admin".equals(userRole)) {
return data;
}
switch (fieldType) {
case "phone":
return desensitizePhone(data);
case "idCard":
return desensitizeIdCard(data);
default:
return data;
}
}
}
四、防护体系总结
防止SQL注入导致的数据库脱裤风险是一个系统工程,首先要通过参数化查询、输入校验、最小权限等措施从根源阻断注入攻击,其次要对敏感数据做加密存储,即使数据被窃取也无法直接使用,最后结合动态脱敏技术,在非必要场景下返回脱敏数据,进一步降低数据泄露的影响。三层防护体系配合使用,才能最大程度保障数据库敏感数据的安全。