SQL注入防御必须从参数化查询开始,使用?或%s占位符并单独传参,禁用字符串拼接;需关闭错误堆栈泄露,预编译语句防语法注入但逻辑注入仍需白名单校验。

SQL注入防御必须从参数化查询开始
不拼接 SQL 字符串,是唯一靠谱的起点。哪怕你加了转义、过滤、正则匹配,只要用了 string.format、+ 拼接或 f-string 插入用户输入,就等于给攻击者留了后门。
常见错误现象:cursor.execute("SELECT * FROM users WHERE name = '" + name + "'") —— 这种写法哪怕加了 name.replace("'", "''") 也防不住 Unicode 绕过、宽字节注入、多语句执行(如 name = "admin'; DROP TABLE users-- ")。
- Python 用
sqlite3或psycopg2时,只允许用?(SQLite)或%s(PostgreSQL)占位符,值单独传入execute(sql, params) - MySQL 的
pymysql同理,用%s占位,绝不用%格式化字符串 - ORM 如 Django 或 SQLAlchemy 默认启用参数化,但手动写
raw()或text()时,仍要自己确保参数分离
数据库报错堆栈泄露是默认开启的安全漏洞
开发环境看到 psycopg2.errors.UndefinedTable: relation "users_x" does not exist 很方便调试,但上线后等于告诉攻击者表名、字段名、甚至数据库类型和版本。
使用场景:Web 接口返回 500 错误时,后端未捕获异常,或日志配置不当导致错误直接透出到响应体中。
- Django 要确认
DEBUG = False,且ALLOWED_HOSTS非空,否则即使DEBUG=False也可能泄露部分信息 - Flask 中禁用
debug=True,并用@app.errorhandler(Exception)捕获所有未处理异常,统一返回{"error": "Internal server error"} - Node.js + Express 下,避免在
res.send(err.stack)或res.json(err)中直接返回原始错误对象 - Nginx 层可加
fastcgi_intercept_errors on;+ 自定义 500 页面,防止 CGI/PHP 堆栈穿透
预编译语句(Prepared Statement)不是银弹,但能堵住多数盲注入口
参数化查询解决的是「语法注入」,但无法防御「逻辑注入」——比如用户控制 ORDER BY 字段名、LIMIT 偏移量、或 IN 子句长度,这些仍需白名单校验。
性能影响极小,现代数据库(PostgreSQL、MySQL 8.0+)对预编译语句有缓存优化;兼容性上,SQLite 默认支持,旧版 MySQL(mysql_real_escape_string 已淘汰,别再用。
-
ORDER BY动态字段:只允许从["created_at", "score"]这类固定列表中选,拒绝任何外部输入 -
LIMIT ? OFFSET ?中的数值必须是整型且有合理上限(如max_limit=100),不能直接传request.args.get("limit") - 多值
IN查询(如WHERE id IN (?, ?, ?))需动态生成占位符数量,不能靠字符串拼接问号
错误日志里藏着比接口更危险的信息泄漏点
很多团队关掉了接口报错,却忘了日志文件里还记着完整的 Traceback、SQL 语句、甚至 repr(user_input)。攻击者若通过其他漏洞拿到服务器文件读取权限,这些日志就是密码本。
容易被忽略的地方:本地开发用 print(e) 调试,上线后没删;或用 logging.exception() 记录所有异常,却没过滤敏感字段。
- 日志中禁止记录原始 SQL(尤其含参数值),可用
logging.info("DB query: SELECT * FROM users WHERE id = ?")替代 - 敏感字段如
password、token、api_key必须在日志前脱敏,例如user.email.replace("@", "[at]") - 生产环境日志级别设为
WARNING或更高,避免INFO级别输出调试信息 - 定期轮转并压缩日志,限制保留天数,避免磁盘打满或历史数据暴露
真正难的不是写对一条 execute(),而是确保每条路径、每个日志点、每次异常分支都不把结构或内容当成“只是内部看看”。细节松动一次,就可能让整个防护形同虚设。

















