【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 注入的三大致命风险

  1. 数据泄露:绕过登录验证,窃取用户密码、手机号等敏感信息
  2. 数据篡改:注入 UPDATE/DELETE 语句,恶意修改或删除数据库数据
  3. 服务中断:注入 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 全方位防御策略

  1. 输入校验与过滤:过滤特殊字符(如 '--UNION 等),验证输入格式
  2. 权限最小化:数据库账号只分配必要权限(如查询账号无增删改权限)
  3. 框架选型:使用 ORM 框架(如 GORM、MyBatis)自动处理 SQL 拼接
  4. 漏洞监控:开启数据库审计,监控包含 1=1-- 等异常 SQL
  5. 定期升级:及时更新数据库、框架版本,修复已知漏洞

总结

SQL 注入的本质是信任了用户输入,防御的核心是never trust user input!通过预编译语句 + 输入校验 + 权限控制的组合拳,就能有效抵御绝大多数 SQL 注入攻击。

建议大家回头检查自己的项目,看看是否存在直接拼接 SQL 的情况,赶紧修复~ 如果你有遇到过 SQL 注入相关的坑,或者有更好的防御方案,欢迎在评论区交流!

Tags:

发表回复

Your email address will not be published. Required fields are marked *.

*
*