拼接SQL字符串必然导致SQL注入风险,因数据库无法区分代码与数据;必须使用参数化查询(如?占位符)并严格避免字符串拼接,字段名等结构元素需白名单校验。

为什么拼接 SQL 字符串一定会出事
因为数据库不会区分“代码”和“数据”——你用 + 或 f-string 把用户输入塞进 SQL 里,等于亲手把刀递过去。' OR '1'='1 这种输入能直接绕过登录验证,不是理论风险,是每天都在发生的事实。
常见错误现象:sqlite3.OperationalError: near "admin": syntax error(其实是注入失败的副产品);更危险的是悄无声息地删库、拖走用户表。
关键判断:只要 SQL 字符串里出现 user_input、request.args.get('id')、name 这类变量名,且没经过参数化处理,就已处于高危状态。
Python 中用 execute() 传参才是正路
所有主流 DB API(sqlite3、psycopg2、pymysql)都支持占位符传参,这是唯一被设计用来隔离数据与语句的机制。
实操建议:
- 用
?占位符(SQLite)或%s(MySQL/PostgreSQL),绝不用f"SELECT * FROM users WHERE name = '{name}'" - 参数必须以元组或字典形式传给
execute()第二个参数,不能 unpack 成位置参数 - 字段名、表名、排序方向(
ASC/DESC)不能参数化——它们属于 SQL 结构,需白名单校验或硬编码
示例(安全):
cursor.execute("SELECT * FROM users WHERE age > ? AND status = ?", (18, 'active'))
ORM 框架里别手痒写原生 SQL
Django 的 filter()、SQLAlchemy 的 query.filter() 默认就是参数化的。但一旦你调用 extra()、text() 或 raw(),就立刻退出安全区。
容易踩的坑:
-
session.execute(text("SELECT * FROM users WHERE name = :name"), {"name": request.args['name']})—— 看似用了命名参数,但如果text()里混入了字符串拼接,照样完蛋 - Django
extra(where=["name = '%s'" % user_input])—— 经典翻车现场,%格式化不等于参数化 - 用
__in查多个 ID 时,传入[1,2,3]是安全的;但若手动拼成"1,2,3"再塞进 SQL,就失效了
Node.js / PHP / Java 怎么做才对
核心原则不变:让驱动层接管变量插值,而不是自己拼字符串。
对应写法:
- Node.js(
mysql2):conn.execute("SELECT * FROM posts WHERE id = ?", [id]) - PHP(PDO):
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?"); $stmt->execute([$email]); - Java(JDBC):
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?"); stmt.setInt(1, userId);
注意:PHP 的 mysqli::query() 不接受参数,必须用 prepare() + bind_param();Java 的 Statement 同理,必须用 PreparedStatement。
真正麻烦的从来不是语法,而是有人觉得“就一个搜索框,哪至于”,然后在 WHERE title LIKE '%"+keyword+"%' 里埋下整站沦陷的引信。

















