SQL注入攻击全场景防御指南:从代码到运维的12个实操方法

先搞懂SQL注入的底层逻辑,才能针对性防御

要防SQL注入,得先明白它是怎么回事——本质是恶意用户将SQL指令伪装成输入参数,让数据库引擎误将其当作合法SQL执行。比如一个登录接口的查询语句:

SELECT * FROM users WHERE username = '$username' AND password = '$password'

如果用户输入username' OR '1'='1,密码随便填,语句就会变成:

SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'xxx'

因为'1'='1'永远为真,这会直接返回所有用户数据,甚至绕开密码验证。

SQL注入攻击全场景防御指南:从代码到运维的12个实操方法

再比如获取用户信息的接口:

SELECT * FROM users WHERE id = $_GET['id']

用户输入1 OR 1=1,语句变成SELECT * FROM users WHERE id = 1 OR 1=1,会返回所有用户的信息——这就是最典型的“Union注入”或“Boolean盲注”。

记住:SQL注入的核心是“输入数据被当作SQL指令执行”,所有防御方法都围绕“让输入数据只作为数据,不参与SQL逻辑”展开。

写代码时就把漏洞堵死:最有效的前置防御

90%的SQL注入漏洞都是代码不规范导致的,解决办法就藏在写代码的细节里——

1. 用预处理语句+参数化查询,彻底杜绝拼接

预处理语句(Prepared Statement)是数据库引擎提供的“安全执行模板”:先把SQL结构传给数据库编译,再把参数传进去。参数会被自动转义,不会被当作SQL指令解析。

举个PHP的正确例子(PDO):
错误写法(字符串拼接,危险!):

$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$stmt = $pdo->query($sql); // 直接拼接,容易被注入

正确写法(预处理+参数化,安全!):

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
// 用数组传参数,PDO会自动处理转义
$stmt->execute([$username, $password]); 
$user = $stmt->fetch();

Python的例子(SQLAlchemy):

from sqlalchemy import create_engine, text

engine = create_engine('mysql+pymysql://user:pass@localhost/db')
with engine.connect() as conn:
    # 用:text()标记参数,参数用字典传入
    result = conn.execute(
        text("SELECT * FROM users WHERE username = :username AND password = :password"),
        {"username": username, "password": password}
    )

关键结论: 不管用什么语言,拒绝任何形式的字符串拼接,必须用预处理+参数化查询——这是防御SQL注入的“银弹”。

2. 用ORM框架,但别滥用Raw SQL

很多开发者以为“用了ORM就安全”,其实不然——ORM的安全前提是不手动写Raw SQL。比如Django的ORM:
安全写法(用QuerySet,自动参数化):

User.objects.filter(username=username, password=password)

危险写法(用Raw SQL,没参数化):

User.objects.raw(f"SELECT * FROM users WHERE username = '{username}'") # 还是会被注入!

提醒: 如果必须用Raw SQL,一定要用ORM提供的参数化方法,比如Django的params参数:

User.objects.raw("SELECT * FROM users WHERE username = %s", [username]) # 安全

3. 对输入做“严格类型校验”,把坏数据挡在门外

比如“用户ID”应该是整数,“手机号”是11位数字,“邮箱”要符合格式——用正则或类型转换把不符合要求的输入直接拒绝

举个Python的例子:

id = request.args.get('id')
if not id.isdigit():
    return jsonify({"error": "无效的ID"}), 400
# 或者直接转成整数
id = int(id) # 非整数会抛异常,直接拦截

补充: 对字符串输入,比如用户名、邮箱,可以限制长度(比如用户名最多20位),避免超长输入绕过滤规则。

数据库本身的防御设置:给数据加一层“金钟罩”

代码没错了,数据库本身的配置也不能马虎——最小权限+危险功能禁用,能把注入的破坏降到最低。

1. 给应用账号设“最小权限”,别用root账号

永远别给应用程序用“root”或“admin”这样的超级账号!正确的做法是:
– 为每个应用创建独立的数据库用户;
– 只授予该应用必须的权限(比如SELECT、INSERT、UPDATE,绝对不给DROP、ALTER、CREATE权限);
– 禁止用户访问其他数据库(比如电商应用的用户,不能访问日志数据库)。

MySQL的具体操作步骤:

-- 1. 创建应用用户(仅限本地访问)
CREATE USER 'shop_app'@'localhost' IDENTIFIED BY 'your_strong_password';
-- 2. 授予仅有的权限(比如只给shop_db数据库的SELECT/INSERT/UPDATE)
GRANT SELECT, INSERT, UPDATE ON shop_db.* TO 'shop_app'@'localhost';
-- 3. 刷新权限
FLUSH PRIVILEGES;

效果: 即使应用被注入,攻击者也没法删除表、修改结构,最多篡改数据——损失可控。

2. 禁用数据库的“危险函数”,切断攻击路径

MySQL、PostgreSQL等数据库有很多“危险函数”,比如:
LOAD_FILE():读取服务器上的文件(比如/etc/passwd);
INTO OUTFILE:写入文件(比如写入webshell);
SELECT ... INTO DUMPFILE:导出文件;
EXECUTE():执行动态SQL(PostgreSQL)。

禁用方法(MySQL):
修改my.cnf(或my.ini)文件,添加以下配置:

[mysqld]
# 禁止读取/写入文件(彻底禁用LOAD_FILE、INTO OUTFILE)
secure_file_priv = NULL
# 禁用二进制日志(避免攻击者通过日志获取数据)
log_bin = OFF
# 禁用查询缓存(避免缓存恶意查询)
query_cache_type = 0

重启MySQL后生效:

systemctl restart mysqld # Linux
net stop mysql && net start mysql # Windows

3. 开启数据库的“严格模式”,拒绝非法数据

严格模式会阻止插入“不符合字段类型”的数据,比如给INT字段插入字符串,会直接报错——这能拦截一部分“盲注”攻击。

MySQL开启严格模式:

-- 临时开启(重启后失效)
SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
-- 永久开启(修改my.cnf)
[mysqld]
sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"

运维环节的最后一道锁:拦截已发生的攻击

即使代码和数据库都没问题,运维环节的防御也能“查漏补缺”——

1. 用Web应用防火墙(WAF),拦截恶意请求

WAF是“网站的门神”,能基于规则拦截带SQL注入特征的请求(比如OR 1=1UNION SELECT'--等)。

推荐的WAF工具:
– 云WAF:Cloudflare(免费版够用)、阿里云WAF、腾讯云WAF;
– 开源WAF:ModSecurity(Apache/Nginx插件)、OpenWAF。

Cloudflare的配置步骤:
1. 登录Cloudflare后台,进入网站的“Security”→“WAF”;
2. 点击“Create Rule”,设置规则名称(比如“Block SQL Injection”);
3. 添加条件:Request URL包含' OR 1=1UNION SELECT--
4. 动作选“Block”,点击“Deploy”。

效果: 所有带恶意字符的请求都会被直接拦截,不会到达应用服务器。

2. 监控数据库日志,及时发现异常

SQL注入的攻击请求,一定会在数据库日志里留下痕迹——比如:
– 大量包含OR 1=1的查询;
– 访问不存在的表(比如SELECT * FROM non_exist_table);
– 频繁查询userpassword等敏感表。

MySQL的日志配置:
修改my.cnf,开启慢查询日志和错误日志:

[mysqld]
# 开启慢查询日志(记录超过1秒的查询)
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
# 开启错误日志
log_error = /var/log/mysql/error.log
# 开启通用查询日志(记录所有查询,生产环境慎用,会影响性能)
general_log = 1
general_log_file = /var/log/mysql/general.log

分析日志的工具: 用ELK Stack(Elasticsearch+Logstash+Kibana)或Grafana,把日志可视化——比如设置“每分钟超过10次带OR 1=1的查询”就报警,能在攻击刚开始时就发现。

3. 定期做“漏洞扫描”,主动找问题

别等被攻击了才发现漏洞——定期用工具扫描,比如:
OWASP ZAP:免费开源的Web漏洞扫描器,能模拟SQL注入攻击;
Acunetix:商业工具,扫描更全面;
Nessus:综合漏洞扫描器,能扫数据库和服务器。

操作步骤(OWASP ZAP):
1. 下载安装OWASP ZAP(https://www.zaproxy.org/);
2. 输入你的网站URL,点击“Attack”;
3. 扫描完成后,查看“Alerts”标签,里面会列出SQL注入漏洞的位置(比如哪个接口的哪个参数有问题)。

常见误区避坑:别用“假防御”骗自己

最后提醒几个最容易踩的坑,别让之前的努力白费——

1. “过滤特殊字符就够了?”——错!

很多人以为“把单引号换成双引号”“过滤掉OR、AND”就安全,其实绕过方法太多
– 编码绕过:比如%BF%27(UTF-8编码,解码后是');
– 大小写绕过:比如Or 1=1UNion SeLeCt
– 注释绕过:比如'--'/*(注释掉后面的SQL)。

结论: 过滤特殊字符只能作为“辅助防御”,不能当主力——主力还是预处理+参数化。

2. “用了WAF就不用管代码了?”——错!

WAF是“兜底”,不是“万能药”:
– 定制化的SQL注入(比如针对你网站的特有逻辑)可能绕开WAF规则;
– WAF会有“误杀”(比如正常请求带'),需要不断调优规则;
– 内网应用(比如后台管理系统)可能没开WAF,还是得靠代码防御。

3. “加密密码就不会被注入?”——错!

加密密码是为了保护密码本身,但SQL注入是获取数据的方式——比如即使密码是MD5加密的,攻击者还是能通过OR 1=1获取所有用户的加密密码,再去撞库解密。

最后:防御SQL注入的“ Checklist”

写完代码、配置完数据库,用这个清单检查一遍:
✅ 所有SQL查询都用了预处理+参数化?
✅ 应用账号只用了最小权限?
✅ 数据库的危险函数都禁用了?
✅ 开了WAF并配置了SQL注入规则?
✅ 定期扫描漏洞、监控日志?

记住:SQL注入防御不是“做一件事”,而是代码→数据库→运维的全流程闭环——每一步都做到位,才能真正防住。

原创文章,作者:,如若转载,请注明出处:https://zube.cn/archives/304

(0)

相关推荐