tornado.database已弃用且不防注入,必须用pymysql等驱动配合%s占位符与元组/字典参数;动态标识符(如ORDER BY字段)须白名单校验,不可参数化。

直接用 tornado 自带的数据库模块(如 tornado.database)无法安全防注入——它早已被弃用且不支持参数化查询。必须搭配底层驱动(如 pymysql、psycopg2 或 aiomysql)手动构造参数化语句,否则任何字符串拼接都等于给攻击者留后门。
为什么不能依赖 tornado.database
该模块在 Tornado 6.0+ 中已被移除,早期版本也只做简单字符串替换,不走预编译流程。比如写 "WHERE name = '%s'" % user_input,哪怕套在 tornado.database 的 query() 里,照样被 ' OR 1=1 -- 绕过。
- 它不区分 SQL 结构与数据,所有传入值都会被当作字符串插进 SQL 文本里
- 没有类型绑定、无二进制传输、不触发数据库端的 prepared statement 编译
- 即便你加了
mysql_real_escape_string,Unicode 多字节编码仍可能绕过
pymysql + execute() 是最稳的组合
在 Tornado 的异步 handler 中,用 pymysql 的 execute() 配合占位符,能真正隔离数据与语法。注意:必须用元组或字典传参,不能解包成字符串。
import pymysql
from tornado.web import RequestHandler
<p>class UserHandler(RequestHandler):
def get(self):
uid = self.get_argument("id", "")</p><h1>✅ 正确:占位符 + 元组参数</h1><pre class="brush:php;toolbar:false;"> with self.db.cursor() as cur:
cur.execute("SELECT * FROM users WHERE id = %s AND status = %s", (uid, "active"))
result = cur.fetchone()
self.write(str(result))
- 占位符只能是
%s,不是?或:name;用错会直接抛ValueError - 参数必须是元组(单值也要写
(uid,))或字典({"id": uid}),不能是列表或字符串 - 连接对象
self.db应复用已配置好的pymysql.Connection,避免每次新建连接开销
动态字段(ORDER BY、LIMIT)必须白名单校验
execute() 只保护值(value),不保护标识符(identifier)。如果 URL 带 ?sort=name,直接拼进 SQL 就崩。
# ❌ 危险!不要这样写
order_by = self.get_argument("sort", "id")
cur.execute(f"SELECT * FROM users ORDER BY {order_by}")
<h1>✅ 正确:白名单映射</h1><p>valid_sorts = {"id": "id", "name": "name", "created_at": "created_at"}
sort_field = valid_sorts.get(self.get_argument("sort", "id"), "id")
cur.execute("SELECT * FROM users ORDER BY %s", (sort_field,)) # ← 这行仍错!见下条
</p>
-
ORDER BY后不能用%s占位——数据库不允许对标识符参数化 - 必须用白名单硬匹配:
sql = f"SELECT ... ORDER BY {valid_sorts[sort_key]}" -
LIMIT数量可转为整数并限范围:limit = max(1, min(100, int(self.get_argument("limit", "20"))))
ORM(如 SQLAlchemy Core)能减负但不免责
若项目已用 SQLAlchemy,其 text() 和 select() 默认走参数化,但两个地方极易翻车:
-
text("SELECT * FROM users WHERE name = :name").bindparams(name=user_input)✅ 安全 -
text(f"SELECT * FROM {table_name}")❌ 拼接表名,必须查白名单字典 -
query.extra(where="status = 'active'")❌ 字符串硬写,绕过所有参数机制
真正容易被忽略的是:Tornado 本身不介入 SQL 构造,所有安全责任落在你选的数据库驱动和手写语句上。哪怕用了 ORM,只要出现一次 str.format() 或 f"" 拼 SQL,整条链路就失效。











