SQL注入本质是字符串拼接而非语法问题,根本原因是用户输入被直接拼入SQL语句;必须对所有用户可控输入使用参数化查询,禁用+、format、f-string等拼接方式,动态结构(如表名、列名)需白名单校验,ORM非绝对安全,存储过程等内部SQL同样需防护。

SQL注入本质是拼接,不是语法问题
SQL注入能生效,根本原因不是用户输入了特殊字符,而是程序把用户输入直接拼进了SQL字符串里。哪怕只用SELECT,只要用了字符串拼接,照样中招。
常见错误现象:WHERE name = ' + user_input + ' 这种写法,不管后端用Python、Java还是PHP,只要没走参数化,就等于给攻击者开了门。
- 永远别用
string.format()、+、f-string拼SQL查询条件 - 所有用户可控的值——包括URL参数、表单字段、请求头、Cookie——都必须进参数占位符
- 数据库驱动层(如
psycopg2、mysql-connector-python、sqlite3)原生支持%s或?占位符,就用它,别自己“转义”
预编译语句 ≠ 自动防注入
很多开发者以为用了PreparedStatement(Java)或cursor.execute(sql, params)(Python)就万事大吉,其实不然:参数只能替代**值**,不能替代**结构**。
使用场景:动态表名、列名、ORDER BY字段、LIMIT偏移量——这些都不能用参数化,必须白名单校验。
-
SELECT * FROM ?是非法的,?不能代入表名 -
ORDER BY ?在多数驱动里也不被允许,得靠if/else或查配置映射到固定字段名 - 如果真要动态列名,先查白名单:
if column_name not in ['created_at', 'status', 'score']:就直接拒掉
ORM不是免死金牌,filter()也能翻车
Django的filter(name__icontains=user_input)、SQLAlchemy的.filter(User.name.contains(user_input)) 是安全的;但一旦混进extra()、raw()、text(),防线立刻崩塌。
错误示例:User.objects.extra(where=["name LIKE '%" + keyword + "%'"]) —— 这和手拼SQL毫无区别。
- Django
raw()和extra()必须确保所有变量都来自可信上下文,否则一律禁用 - SQLAlchemy 的
text("SELECT * FROM users WHERE name = :name")是安全的,但text(f"SELECT * FROM {table_name}")不行 - 任何ORM都挡不住
os.system()或subprocess.run()调用psql命令这种操作——这是另一条攻击路径
LIKE查询的通配符要单独处理
LIKE本身不导致注入,但用户输入里的%、_会被当通配符用,造成逻辑绕过或误匹配;更麻烦的是,有人会故意输%admin%来模糊搜管理员账号。
这不是SQL注入,但属于输入滥用,需要和注入一起防御。
- 若业务允许模糊搜索,对用户输入做
escape并声明转义符:WHERE name LIKE %s ESCAPE '\',然后把%替换成\% - 不要用
replace('%', '\%')这种粗暴方式,得用驱动提供的escape机制,否则可能漏掉[\]本身 - 如果只是精确匹配,就别用
LIKE,改用=,避免引入额外复杂度
最易被忽略的一点:存储过程、触发器、视图里如果拼了用户输入,哪怕调用方用了参数化,也照旧中招。SQL注入的战场不在应用层入口,而在每一个执行SQL的地方。











