SQL注入防御必须从参数化查询开始,所有外部输入的数据库操作须用占位符而非字符串拼接;CSP与SQL注入无关,仅防XSS;参数化需配合输入校验、类型约束和白名单。

SQL注入防御必须从参数化查询开始
不处理用户输入就拼接 SQL,等于给攻击者递钥匙。所有数据库操作,只要涉及外部输入,query、execute、cursor.execute 这类调用必须用参数占位符,不能靠字符串格式化或 + 拼接。
常见错误现象:sql = "SELECT * FROM users WHERE name = '" + request.args.get('name') + "'" —— 这种写法哪怕加了 strip() 或正则过滤也拦不住绕过;mysql.connector.connect() 启用了 autocommit=False 也不影响注入发生。
- Python(sqlite3 / psycopg2)用
%s(PostgreSQL)或?(SQLite)占位,传参走第二个参数:cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,)) - Node.js(pg)必须用
$1,$2占位,禁用pg.escape()或模板字符串 - PHP(PDO)只认
:name或?占位,PDO::ATTR_EMULATE_PREPARES必须设为false,否则预编译被绕过 - ORM 如 Django ORM、SQLAlchemy 默认安全,但一旦调用
raw()或text(),就得立刻切回参数化
CSP 与 SQL 注入没有直接关系
内容安全策略(CSP)是浏览器端机制,只控制脚本、样式、iframe 等资源加载行为,对后端数据库查询完全无感。Content-Security-Policy 响应头再严格,也防不住 id=1%20OR%201=1 这种发往 API 的恶意参数。
容易踩的坑:script-src 'self' 设得再紧,如果后端接口把 request.query.id 直接塞进 SQL 字符串,攻击照样成功;有人误以为 CSP 能防 XSS 就能顺带防注入,这是混淆了客户端渲染漏洞和服务器数据访问漏洞。
- CSP 只在
response headers中生效,且只影响浏览器解析 HTML/JS 的行为 - SQL 注入发生在数据库驱动层,早于任何 HTTP 响应头生成
- 真正需要 CSP 的场景是:防止攻击者通过 XSS 窃取 token 后伪造请求,间接扩大注入影响面 —— 这是第二道防线,不是替代方案
参数化不够时,还得加输入校验和类型约束
参数化解决的是语法层面的注入,但挡不住语义层面的滥用。比如 user_id 是数字,却传了 "1; DROP TABLE users;" —— 参数化下它会被当字符串字面量处理,不会执行,但若字段本该是整数,却接受超长字符串或非法字符,说明校验缺失。
使用场景:API 接口接收分页参数 limit 和 offset,它们必须是正整数,且有合理上限。
- 用
int()强转前先str.isdigit()或正则^\d+$,避免int("1e5")报错或被绕过 - 对枚举类参数(如
sort=created_at),白名单比正则更可靠:if sort_field not in ['created_at', 'updated_at']:直接拒绝 - 长度限制要落在数据库字段定义(
VARCHAR(32))和应用层校验双重位置,单靠 DB 约束不够及时 - PostgreSQL 的
pg_typeof()或 MySQL 的CAST()不该用于运行时“修复”输入,而是用来做调试验证
ORM 自动化防护有盲区,别盲目信任
Django 的 filter(id__exact=xxx) 或 SQLAlchemy 的 session.query(User).filter(User.id == xxx) 确实默认参数化,但一旦混入 extra()、raw()、text()、func.concat() 这类接口,就可能滑出安全区。
典型错误:User.objects.extra(where=["name = '%s'" % request.GET.get('name')]) —— 这里 %s 是 Python 字符串插值,不是 SQL 占位符;又比如 SQLAlchemy 中写 text("SELECT * FROM users WHERE name = :name") 却没传 params 字典,或者传了 params={'name': user_input} 却忘了 bindparam 类型声明。
- Django
extra()已被标记为 deprecated,优先用annotate()+F()表达式 - SQLAlchemy 的
text()必须配bindparam()或明确传params,且不能在text()内部做字符串拼接 - 使用
QuerySet.raw()时,连params都不支持,只能靠外部预处理,这种场景建议直接换原生驱动+参数化 - ORM 日志里如果出现
WHERE name = 'admin' --这类带注释的语句,基本可判定参数化失效或被绕过
事情说清了就结束。最危险的不是不会写参数化,而是写了参数化却在某个 raw() 调用、某个 os.system() 拼接、某处日志打印里漏掉校验——这些点往往藏在业务边缘逻辑里,review 时最容易被跳过。










