你的网站还在裸奔?SQL注入可能已经盯上你了

别等被黑了才翻日志。上周有个做电商小程序的朋友,后台订单页一个没过滤的ID参数,被人拖走了3000多条用户手机号——就因为登录框旁边那个“查订单”的小输入框,连个单引号 ' 都没拦住。

你的网站存在SQL注入漏洞吗?5个自查方法

别信“我用的是ThinkPHP/Express/Django,肯定安全”。框架再厚,也挡不住手抖写错的一行拼接。

1. 手动输入测试法
打开你网站所有带输入的地方:搜索框、登录页、个人资料编辑、订单查询、评论框……挨个试。在框里敲一个英文单引号 ',回车。
→ 页面直接崩出 MySQL errorsyntax near '...'ORA-00936 这类报错?立刻停手,这就是明晃晃的漏洞入口。
→ 没报错?别松气。有些站点会静默失败或跳转,接着试 1' OR '1'='11' UNION SELECT 1,2,3--,看返回内容有没有异常增多或字段错乱。

2. 使用自动化扫描工具
人工漏得太多。如果你用 Chrome 浏览器,今天就能装上 Burp Suite Community Edition(免费版):

  • 开启 Proxy,用浏览器正常访问你的网站;
  • 在 Proxy → HTTP history 里,把所有带参数的请求(尤其是 GET /search?q=xxxPOST /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)
    找到就标红。重点盯 WHEREORDER BYLIMIT 后面是否直接拼了变量——这些地方最容易翻车。

4. 检查数据库日志
登录你的 MySQL/PostgreSQL 控制台(或者用 phpMyAdmin / DBeaver / Navicat),翻最近24小时的慢查询日志或通用日志:

  • UNION SELECTOR 1=1SLEEP(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 / 云厂商控制台),找到你网站用的那个数据库账号,把它权限砍到只剩 SELECTINSERTUPDATE(删库、建表、读系统表权限全关掉);
  • 在代码里关掉所有数据库错误回显:
    • PHP:ini_set('display_errors', 0) + error_reporting(0)
    • Python Flask:app.debug = False
    • Node.js:别把 err.stack 发给前端,只记日志,返回 {"code": 500, "msg": "服务器忙,请稍后再试"}

修复后如何验证效果?

别修完就跑。回到第一步,重新走一遍:

  • 用之前那个 'OR 1=1 再试一遍所有接口;
  • 看 Burp 的 Intruder 结果里,是否还有 500 错误或异常响应;
  • 查数据库日志,确认没有新的可疑 payload 出现;
  • 如果某个接口修完后功能异常(比如搜不到东西),大概率是你参数化写错了——检查占位符数量和传参顺序是否对得上。

如何建立长期的安全习惯?

  • 每次 Code Review,把 SQLqueryexecute 当成敏感词高亮。看到字符串拼接,直接打问号;
  • 新成员入职,第一周就让他读团队 Wiki 里那页《数据库操作安全红线》,里面贴着3个真实翻车案例;
  • 每月最后一个周五下午,留30分钟,所有人一起用 Burp 扫一遍自己负责的接口,把结果发到技术群——不问责,只补漏。

今天下班前就能做的1个具体操作

现在就打开你的 VS Code / WebStorm / 记事本,按 Ctrl+Shift+F(Windows)或 Cmd+Shift+F(Mac),全局搜索:

"SELECT  
"INSERT  
"UPDATE  
"DELETE  

找到第一个匹配项,点进去,看 WHEREVALUESSET 后面是不是直接拼了变量(比如 + id. $name{user_id})。
如果是——
✅ 打开对应语言的官方文档(比如 Python DB-API 文档、PHP PDO 文档),抄一段参数化示例;
✅ 把那行危险代码替换成参数化写法;
✅ 用 Postman 或浏览器手动测一遍这个接口,确认功能正常。
修完一个,你就少一个被拖库的风险。现在,就打开编辑器。