【MySQL】深入浅出SQL注入:原理、演示与防御实战
作为后端开发者,数据安全永远是绕不开的话题。最近在做项目审计时,发现很多新手开发者容易忽略 SQL 注入风险,今天就带大家从零开始认识 SQL 注入,通过实战演示理解其危害,最后给出可落地的防御方案。文章包含完整代码示例,新手也能跟着操作~
一、什么是 SQL 注入?
SQL 注入(SQL Injection)是 web 应用中最常见的安全漏洞之一,本质是攻击者通过构造恶意 SQL 语句,注入到用户输入字段中,欺骗数据库执行非预期操作。
1.1 先搭个测试环境
为了方便演示,我们先创建测试表并插入测试数据:
use maria;
CREATE TABLE users_info (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
password VARCHAR(50) NOT NULL,
contact VARCHAR(100) NOT NULL
);
INSERT INTO users_info (username, password, contact)
VALUES ('user1', 'pass1', '123456789'),
('user2', 'pass2', '987654321'),
('user3', 'pass3', '121212123'),
('user4', 'pass4', '989898987');
1.2 两种典型注入方式
(1)OR 逻辑注入
正常查询 SQL(验证用户名密码):
select * from users_info where username='user1' and password='pass1';

攻击者将密码改为 ' or 'a'='a,构造出恶意 SQL:
select * from users_info where username='user1' and password='' or 'a'='a';

由于 'a'='a 恒为真,条件整体成立,将返回全表数据!
(2)UNION 联合查询注入
通过 UNION 关键字拼接查询语句,获取其他数据:
-- 原始查询
select * from users_info where username='user1' and password='pass1';
-- 注入后
select * from users_info where username='' UNION SELECT * FROM users_info ; -- ' and password='pass1';

-- 是 SQL 注释符,后面的内容会被忽略,最终执行两个查询并合并结果。
二、SQL 注入的三大致命风险
- 数据泄露:绕过登录验证,窃取用户密码、手机号等敏感信息
- 数据篡改:注入 UPDATE/DELETE 语句,恶意修改或删除数据库数据
- 服务中断:注入 DROP TABLE 等破坏性语句,导致系统瘫痪
三、实战演示:用 Go 写一个易受攻击的登录程序
3.1 演示代码(存在注入漏洞)
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
_ "github.com/go-sql-driver/mysql" // MySQL驱动
)
func main() {
// 注册登录路由,处理GET(显示页面)和POST(验证登录)请求
http.HandleFunc("/login", loginHandler)
// 启动HTTP服务,监听8080端口
log.Println("服务启动:http://localhost:8081/login")
if err := http.ListenAndServe(":8081", nil); err != nil {
log.Fatal("服务启动失败:", err)
}
}
// loginHandler:处理登录逻辑
func loginHandler(w http.ResponseWriter, r *http.Request) {
// 1. GET请求:返回登录表单页面
if r.Method == "GET" {
html := `
<!DOCTYPE html>
<html>
<head><title>用户登录</title></head>
<body>
<h2>请登录</h2>
<form method="POST" action="/login">
用户名:<input type="text" name="username" required><br><br>
密码:<input type="password" name="password" required><br><br>
<input type="submit" value="登录">
</form>
</body>
</html>`
fmt.Fprint(w, html)
return
}
// 2. POST请求:处理登录验证
if r.Method == "POST" {
// 获取用户输入的用户名和密码(未做任何过滤)
username := r.FormValue("username")
password := r.FormValue("password")
log.Printf("接收请求:username=%s, password=%s", username, password)
// 连接数据库(实际项目需用配置文件存储账号密码)
db, err := sql.Open("mysql", "go_rw:Ud7q_dac8@tcp(192.168.184.151:3306)/maria")
if err != nil {
log.Fatal("数据库连接失败:", err)
}
defer db.Close() // 函数结束前关闭数据库连接
// 【漏洞核心】直接拼接用户输入到SQL语句中
query := fmt.Sprintf(
"SELECT COUNT(*) FROM users_info WHERE username = '%s' AND password = '%s'",
username, password,
)
log.Println("执行SQL:", query)
// 执行查询并判断登录结果
var count int
if err := db.QueryRow(query).Scan(&count); err != nil {
log.Fatal("查询失败:", err)
}
if count > 0 {
fmt.Fprint(w, "登录成功!")
} else {
fmt.Fprint(w, "用户名或密码错误!")
}
}
}
3.2 注入测试效果
- 正常登录:输入
user1/pass1→ 登录成功 - 故意输错:输入
user1/123→ 登录失败 - SQL 注入攻击:用户名随便填,密码输入
' or 'a'='a→ 直接登录成功!
执行的恶意 SQL:
SELECT COUNT(*) FROM users_info WHERE username = 'test' AND password = '' or 'a'='a'
四、如何有效预防 SQL 注入?
4.1 核心修复:使用预编译语句
最有效的防御手段是参数化查询(预编译语句),避免直接拼接用户输入。修改上述代码的查询部分:
// 危险写法(已废弃)
// query := fmt.Sprintf("SELECT COUNT(*) FROM users_info WHERE username = '%s' AND password = '%s'", name, password)
// 安全写法:使用占位符?
query := "SELECT COUNT(*) FROM users_info WHERE username = ? AND password = ?"
var count int
// 把参数单独传入,数据库会自动处理转义
err := db.QueryRow(query, name, password).Scan(&count)
修改后再测试注入:输入 ' or 'a'='a 会被当作普通字符串处理,无法触发注入。
4.2 全方位防御策略
- 输入校验与过滤:过滤特殊字符(如
'、--、UNION等),验证输入格式 - 权限最小化:数据库账号只分配必要权限(如查询账号无增删改权限)
- 框架选型:使用 ORM 框架(如 GORM、MyBatis)自动处理 SQL 拼接
- 漏洞监控:开启数据库审计,监控包含
1=1、--等异常 SQL - 定期升级:及时更新数据库、框架版本,修复已知漏洞
总结
SQL 注入的本质是信任了用户输入,防御的核心是never trust user input!通过预编译语句 + 输入校验 + 权限控制的组合拳,就能有效抵御绝大多数 SQL 注入攻击。
建议大家回头检查自己的项目,看看是否存在直接拼接 SQL 的情况,赶紧修复~ 如果你有遇到过 SQL 注入相关的坑,或者有更好的防御方案,欢迎在评论区交流!



