python防止sql注入的核心方法是使用参数化查询,原理是将sql语句结构与数据严格分离,数据库驱动把参数作为独立数据单元传递并由引擎安全转义或绑定,避免用户输入被解析为sql代码。

直接用 sqlite3_bind_* 替换字符串拼接
SQLite本身不解析参数值,但前提是别把用户输入塞进SQL字符串里。只要看到 db.execSQL("SELECT * FROM users WHERE name = '" + input + "'") 这类写法,就等于给攻击者开了后门。
正确做法是用预编译语句配合绑定函数:
-
sqlite3_prepare_v2()先编译带?或:name占位符的SQL - 再用
sqlite3_bind_text()、sqlite3_bind_int()等函数传入实际值 - 最后调用
sqlite3_step()执行
注意:sqlite3_bind_* 系列函数对输入不做任何解释,哪怕传入 "admin' OR 1=1--",它也只当普通文本存进字段值,不会触发语法解析。
Python里别用 f-string 或 % 拼接SQL
常见错误是写成 cursor.execute(f"SELECT * FROM logs WHERE level = '{level}'") —— 这种写法在任何输入含单引号、分号或注释符时都会崩。
必须改用问号占位符:
- 查询单个值:
cursor.execute("SELECT id FROM config WHERE key = ?", (key,)) - 多个参数用元组:
cursor.execute("INSERT INTO events VALUES (?, ?, ?)", (ts, evt, data)) - 字典绑定(仅支持命名占位符):
cursor.execute("UPDATE users SET email = :email WHERE id = :id", {"email": e, "id": i})
特别注意:元组即使只有一个元素,也必须加逗号,写成 (value,),否则会被当成括号包裹的表达式,导致 sqlite3.ProgrammingError。
动态表名/列名无法参数化,只能白名单校验
SELECT * FROM ? 是非法的——SQLite不允许参数化标识符。如果业务真需要切换表(比如按月份分表),就得手动控制范围。
安全做法是提前定义允许的表名集合,再做严格比对:
- 建立硬编码白名单:
ALLOWED_TABLES = {"logs_2024", "logs_2025", "logs_2026"} - 校验输入是否在其中:
if table_name not in ALLOWED_TABLES: raise ValueError("Invalid table") - 拒绝任何含点号、空格、斜杠、控制字符的输入
千万别用正则替换或“去掉单引号”这种弱过滤——攻击者能绕过的方式太多,比如用 sqlite_master 表名触发元数据泄露,或用 Unicode 变体混淆检测。
Android里慎用 rawQuery() 的第二个参数
Android SDK 的 SQLiteDatabase.rawQuery() 看似支持参数化,但第二个参数(selectionArgs)只作用于 WHERE 子句中的 ? 占位符,且仅限值部分。
容易踩的坑:
- 误以为
db.rawQuery("SELECT * FROM " + table, null)安全——其实table是拼进去的,完全不设防 - 在
ORDER BY后直接插变量:"ORDER BY " + sortCol—— 这里无法用?占位 - 把
selectionArgs当万能解药,忽略其只处理 WHERE 条件的局限性
对应方案:对 sortCol 做白名单检查;对表名、列名、GROUP BY 字段等所有非值内容,一律走字符串白名单或枚举校验。
参数化查询不是银弹,它只保值不保结构。真正危险的从来不是“用户输错”,而是“用户输得刚好能骗过你的校验逻辑”。动态SQL部分永远要靠设计约束来兜底,而不是指望某行代码能自动免疫。











