重构大版本更新
一口气全面统一博客样式,移除部分冗余导航页面,更专注内容展示。
从一条拼接 SQL 语句出发,理解注入原理、UNION 拖库、盲注技巧,以及为什么参数化查询能一劳永逸。
Web 安全入门系列第二篇。上一篇讲 XSS 时说过:所有注入类漏洞的本质都一样——不该信的数据被当成了代码执行。SQL 注入是这句话最经典的注脚,三十年过去,它每年仍稳居 OWASP Top 10。
看一段”能跑就行”的登录代码:
// Node.js + 字符串拼接(有漏洞)app.post("/login", (req, res) => { const sql = `SELECT * FROM users WHERE name='${req.body.user}' AND pass='${req.body.pass}'`; db.query(sql);});正常用户输入 admin / 123456,SQL 是:
SELECT * FROM users WHERE name='admin' AND pass='123456'但攻击者输入的用户名是 admin'--(注意结尾两个短横线)。拼出来的 SQL 变成:
SELECT * FROM users WHERE name='admin'--' AND pass='...'-- 后面全部被注释掉,密码检查直接消失,以 admin 身份登录成功。这就是注入:输入中的引号改变了 SQL 的结构,数据变成了代码。
按危害递增:
UNION SELECT 把其他表的数据拼进查询结果:-- 猜列数 → 拼接 UNION → 拖出用户表' UNION SELECT username, password FROM users--AND SUBSTRING(pass,1,1)='a' 页面正常、错误则说明猜错。慢,但自动化工具(sqlmap)让它变得可怕;SELECT ... INTO OUTFILE 写 Webshell,直接控制服务器。核心原则:SQL 结构与数据分离。预编译语句把 SQL 模板先发给数据库解析,数据永远只当数据:
// 正确:? 占位符,数据由驱动安全处理await db.query( "SELECT * FROM users WHERE name=? AND pass=?", [req.body.user, req.body.pass]);用户再输入 admin'--,它就是一个叫 admin'-- 的用户名字符串,查不到任何东西。二十年前就有的技术,至今仍是唯一根治方案。
其他辅助手段:
| 手段 | 作用 | 局限 |
|---|---|---|
| 参数化查询 | 根治注入 | ORDER BY / 表名无法参数化,需白名单 |
| 最小权限数据库账号 | 缩小拖库、写文件面 | 不阻止读取本表 |
| ORM(Prisma/Sequelize) | 默认安全 | raw() 原生查询仍需注意占位符 |
| WAF | 拦截明显 payload | 可被编码绕过,只作辅助 |
在真实网站上测试注入是违法行为。想动手,用本地靶场:
用一个下午把 sqli-labs 前 10 关打通,你对注入的理解会超过大多数只看过文章的人。
SQL 注入的防御技术二十年没变过:能用参数化的地方全部参数化,不能参数化的地方用白名单。它至今仍大面积存在的原因不是技术难题,而是老旧代码和侥幸心理。下一篇讲密码哈希——为什么数据库里存的密码必须是哈希,明文和可逆加密都不合格。