SQLAlchemy的execute()不能直接拼接字符串,因会绕过参数化导致SQL注入;须用:name或%s占位符并传params;Django的extra()和raw()若未用params则危险;text()需手动参数化;不同驱动占位符不同;标识符须白名单校验。

SQLAlchemy的execute()为什么不能直接拼接字符串
因为execute()底层仍走数据库驱动,如果传入的是拼接后的str(比如"SELECT * FROM users WHERE id = " + user_id),数据库会原样解析执行,恶意输入如"1; DROP TABLE users;"就直接生效。
正确做法是始终用参数占位符,让驱动做类型绑定和转义:
- 使用
:name命名参数(推荐):conn.execute("SELECT * FROM users WHERE status = :status", {"status": "active"}) - 或
%s位置参数(注意:仅限MySQLdb/PyMySQL;SQLite用?):conn.execute("INSERT INTO log (msg) VALUES (%s)", ("login failed",)) - 绝不要用
f-string、.format()或%格式化拼SQL
Django ORM的filter()和extra()哪个更危险
filter()默认安全——所有字段名和值都经QuerySet编译器转义,哪怕传name__contains="'; DROP--"也只当普通字符串查。
但extra()、raw()、extra(tables=[...])会绕过ORM校验,直接进SQL生成链:
立即学习“Python免费学习笔记(深入)”;
-
User.objects.extra(where=["name = '%s'" % user_input])→ 危险! -
User.objects.extra(where=["name = %s"], params=[user_input])→ 安全,params走参数化 -
User.objects.raw("SELECT * FROM auth_user WHERE email = %s", [email])→ 安全 - 只要出现字符串拼接、未加
params、或tables/joins里含用户输入,立刻视为高危
Flask-SQLAlchemy里text()怎么写才不漏掉转义
text()本身只是包装SQL字符串,不自动参数化。它是否安全,完全取决于你是否显式传params并用占位符。
常见错误是以为text()自带防护:
- 错:
db.session.execute(text(f"SELECT * FROM posts WHERE tag = '{tag}'"))→ 拼接即沦陷 - 对:
db.session.execute(text("SELECT * FROM posts WHERE tag = :tag"), {"tag": tag}) - 注意
text()中占位符必须和params键名一致,且不能是Python变量名(比如:tag≠tag变量) - 若需动态表名或字段名(极少见),必须白名单校验,不能参数化 —— 因为SQL语法层不允许参数化标识符
原生sqlite3和psycopg2的参数写法差异
不同驱动的占位符不兼容,混用会导致ProgrammingError或静默失败(比如把%s当字面量执行)。
必须按驱动约定写:
- SQLite(
sqlite3):只认?(问号)或:name:cur.execute("SELECT * FROM t WHERE id = ?", (123,)) - PostgreSQL(
psycopg2):支持%s(位置)和%(name)s(命名):cur.execute("SELECT * FROM t WHERE id = %(id)s", {"id": 123}) - MySQL(
pymysql):用%s,不支持命名参数(除非用DictCursor+额外封装) - 切勿在
psycopg2里写?,也别在sqlite3里写%s—— 不报错但可能查不到数据
最易被忽略的是动态SQL中的标识符(表名、字段名、ORDER BY字段),它们无法参数化,必须用白名单或正则严格过滤,比如re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", table_name)。没这步,再严的参数化也拦不住UNION SELECT攻击。










