参数化查询仅对值安全,无法防护表名、字段名等SQL结构,必须配合白名单校验;不同数据库驱动占位符语法各异,混用会导致参数绑定失败。

参数化查询本身不能“彻底杜绝”SQL注入——它只对值(value)安全,对SQL结构(如表名、排序字段、LIMIT数量)完全无效。真正能防住的,是严格区分“哪里能参数化”和“哪里必须白名单校验”。
Python中cursor.execute()传参失败的常见原因
不是参数化没用,而是占位符和驱动不匹配。比如用psycopg2却写%s占位符没问题,但若误用?(SQLite风格),或反过来在SQLite里用%(name)s,就会报TypeError: not all arguments converted或OperationalError: bind parameter。
- SQLite / PyMySQL / mysqlclient:统一用
%s,参数必须是tuple或list,哪怕只有一个值也要写(value,) - psycopg2:支持
%s(位置)和%(key)s(命名),命名方式需传dict,键名必须与SQL中一致 - 不要混用:比如
"WHERE id = %s AND name = %(n)s"在psycopg2里会失败,驱动不支持混合占位
MyBatis里#{}和${}的区别不是性能问题,是生死线
#{}走预编译,输入被当作纯数据;${}是字符串拼接,输入直接进SQL语法树——哪怕你过滤了单引号,攻击者仍可用1; DROP TABLE users--或Unicode编码绕过。
- 所有动态列名、表名、ORDER BY字段,必须用
${}+ 白名单校验,比如:ORDER BY ${sortField}前先判断sortField in ["id", "created_at", "status"] -
LIMIT ${count}必须强制转int并限定范围:max(1, min(100, int(count))),否则100 UNION SELECT ...就生效 - 框架不会帮你拦
${}——Django的extra()、SQLAlchemy的text()、MyBatis的${},全是一样的风险等级
为什么加了addslashes()或mysql_real_escape_string()反而更危险
这些函数依赖字符集上下文,而MySQL的宽字节注入(如%A1%AA)会让转义失效;更关键的是,它们只能处理字符串型字段,对数字型参数(如id=1 OR 1=1)完全无感——而参数化查询对所有类型一视同仁。
- 拼接前做
htmlentities()?那是防XSS的,跟SQL注入无关 - 用正则删掉
;--/*?攻击者改用UNION SELECT或EXECUTE IMMEDIATE就绕过 - 前端JS校验?curl绕开浏览器,直接POST恶意payload
真正难的不是写cursor.execute("SELECT * FROM user WHERE id = %s", (user_id,))这一行,而是后续所有需要拼接SQL结构的地方——那里没有占位符,只有白名单、类型强转和硬编码的允许列表。漏掉一个ORDER BY字段校验,整套参数化就形同虚设。

















