你的网站还在裸奔?SQL注入可能已经盯上你了
别等被黑了才翻日志。上周有个做电商小程序的朋友,后台订单页一个没过滤的ID参数,被人拖走了3000多条用户手机号——就因为登录框旁边那个“查订单”的小输入框,连个单引号 ' 都没拦住。
你的网站存在SQL注入漏洞吗?5个自查方法
别信“我用的是ThinkPHP/Express/Django,肯定安全”。框架再厚,也挡不住手抖写错的一行拼接。
1. 手动输入测试法
打开你网站所有带输入的地方:搜索框、登录页、个人资料编辑、订单查询、评论框……挨个试。在框里敲一个英文单引号 ',回车。
→ 页面直接崩出 MySQL error、syntax near '...'、ORA-00936 这类报错?立刻停手,这就是明晃晃的漏洞入口。
→ 没报错?别松气。有些站点会静默失败或跳转,接着试 1' OR '1'='1 或 1' UNION SELECT 1,2,3--,看返回内容有没有异常增多或字段错乱。
2. 使用自动化扫描工具
人工漏得太多。如果你用 Chrome 浏览器,今天就能装上 Burp Suite Community Edition(免费版):
- 开启 Proxy,用浏览器正常访问你的网站;
- 在 Proxy → HTTP history 里,把所有带参数的请求(尤其是
GET /search?q=xxx、POST /login这类)右键 → “Send to Intruder”; - 在 Payloads 里选 “Payload type: Simple list”,填入几个基础 payload:
',' OR '1'='1,") OR "1"="1; - 点击 Attack,看哪些响应状态码突变、响应体长度暴增、或出现
error/mysql/postgresql字样。
别拿 SQLMap 直扫线上环境——它太猛,容易把数据库打挂。本地或测试服试试就行。
3. 全面代码审计
打开你项目的代码编辑器(VS Code / WebStorm / PHPStorm),全局搜索这几个危险组合:
+ userInput(Java/JS).$userInput(PHP)f"SELECT * FROM ... WHERE id = {user_input}"(Python f-string)"SELECT * FROM users WHERE name = '" + req.query.name + "'"(Node.js)
找到就标红。重点盯WHERE、ORDER BY、LIMIT后面是否直接拼了变量——这些地方最容易翻车。
4. 检查数据库日志
登录你的 MySQL/PostgreSQL 控制台(或者用 phpMyAdmin / DBeaver / Navicat),翻最近24小时的慢查询日志或通用日志:
- 搜
UNION SELECT、OR 1=1、SLEEP(、BENCHMARK(; - 看有没有一串长得几乎一样、只改了末尾数字的查询(比如
id=1,id=2,id=3…连续几十条); - 某个运营同事曾从日志里发现
SELECT * FROM users WHERE email = 'xxx' OR '1'='1',顺藤摸瓜揪出注册页邮箱校验漏掉了引号转义。
5. 模拟攻击者思维
关掉文档,打开你自己的网站,当一回坏人:
- 不登录,能通过修改 URL 里的
?id=123看到别人订单吗? - 注册时填
admin'--当用户名,能绕过重名检测直接变成管理员账号吗? - 上传头像的文件名字段,如果输
avatar.jpg' AND SLEEP(5)--,页面会卡5秒吗?
只要有一个“能”,这个点就必须马上修。
SQL注入的根本原因是什么?
就两句话:
你把用户输的东西,当成了SQL语句的一部分。
你把数据库的错误提示,原封不动扔给了用户看。
比如一个商品详情页,后端写了:
$id = $_GET['id'];
$sql = "SELECT * FROM products WHERE id = $id";
用户在地址栏改成 ?id=1 OR 1=1,SQL 就变成:
SELECT * FROM products WHERE id = 1 OR 1=1
结果——全表数据都吐出来了。
再比如,用户输了个非法ID,你直接把 MySQL Error: Unknown column 'xxx' in 'where clause' 显示在网页上。攻击者一眼就知道你用的是 MySQL,表名可能是 products,字段名可能叫 xxx……等于递给他一把万能钥匙。
修复SQL注入的3个核心方法
1. 无条件用参数化查询
不是“尽量用”,是“只要连数据库,必须用”。
- Java:
PreparedStatement(别再用Statement); - Python:用
cursor.execute("SELECT * FROM users WHERE id = %s", [user_id]),别用f"WHERE id = {user_id}"; - PHP:PDO 的
prepare()+execute(),或者 MySQLi 的prepare(); - Node.js:
mysql2库的?占位符,connection.execute("SELECT * FROM users WHERE id = ?", [id])。
记住:参数化不是加个引号,是让数据库驱动帮你把值当纯数据处理,彻底隔开代码和数据。
2. 输入校验只认“白名单”
动态表名、列名、排序字段(比如 ?sort=price)没法参数化?那就死守白名单:
# ✅ 安全:只允许预设的几个值
valid_sorts = ["price", "name", "created_at"]
if sort not in valid_sorts:
raise ValueError("Invalid sort field")
sql = f"SELECT * FROM products ORDER BY {sort} DESC"
# ❌ 危险:黑名单永远防不住新招式
if "union" in user_input.lower(): # 攻击者写 uNiOn、%75%6e%69%6f%6e 就绕过了
return
3. 数据库账号要“抠门”,错误提示要“装傻”
- 登录你的数据库管理后台(phpMyAdmin / pgAdmin / 云厂商控制台),找到你网站用的那个数据库账号,把它权限砍到只剩
SELECT、INSERT、UPDATE(删库、建表、读系统表权限全关掉); - 在代码里关掉所有数据库错误回显:
- PHP:
ini_set('display_errors', 0)+error_reporting(0); - Python Flask:
app.debug = False; - Node.js:别把
err.stack发给前端,只记日志,返回{"code": 500, "msg": "服务器忙,请稍后再试"}。
- PHP:
修复后如何验证效果?
别修完就跑。回到第一步,重新走一遍:
- 用之前那个
'和OR 1=1再试一遍所有接口; - 看 Burp 的 Intruder 结果里,是否还有 500 错误或异常响应;
- 查数据库日志,确认没有新的可疑 payload 出现;
- 如果某个接口修完后功能异常(比如搜不到东西),大概率是你参数化写错了——检查占位符数量和传参顺序是否对得上。
如何建立长期的安全习惯?
- 每次 Code Review,把
SQL、query、execute当成敏感词高亮。看到字符串拼接,直接打问号; - 新成员入职,第一周就让他读团队 Wiki 里那页《数据库操作安全红线》,里面贴着3个真实翻车案例;
- 每月最后一个周五下午,留30分钟,所有人一起用 Burp 扫一遍自己负责的接口,把结果发到技术群——不问责,只补漏。
今天下班前就能做的1个具体操作
现在就打开你的 VS Code / WebStorm / 记事本,按 Ctrl+Shift+F(Windows)或 Cmd+Shift+F(Mac),全局搜索:
"SELECT
"INSERT
"UPDATE
"DELETE
找到第一个匹配项,点进去,看 WHERE、VALUES、SET 后面是不是直接拼了变量(比如 + id、. $name、{user_id})。
如果是——
✅ 打开对应语言的官方文档(比如 Python DB-API 文档、PHP PDO 文档),抄一段参数化示例;
✅ 把那行危险代码替换成参数化写法;
✅ 用 Postman 或浏览器手动测一遍这个接口,确认功能正常。
修完一个,你就少一个被拖库的风险。现在,就打开编辑器。