PreparedStatement是Trino防御SQL注入的唯一可靠手段,因其参数值不参与SQL解析、完全脱离语法树,而字符串拼接即使转义也无法覆盖Unicode绕过、嵌套注释等边界场景。

PreparedStatement 是 Trino 防御 SQL 注入的唯一可靠手段,硬拼接字符串(哪怕加了引号或转义)在任何场景下都不安全。Trino 本身不支持服务端动态拼接 SQL,所有“动态”都必须由客户端完成,且仅限于参数化占位符 ? 的位置。
为什么不能用字符串拼接 + 手动转义
Trino 的 SQL 解析器(SqlParser)在语法层就拒绝非法结构,但转义逻辑(如单引号替换成两个单引号)无法覆盖所有边界情况:嵌套注释、Unicode 控制字符、多字节编码绕过、JSON 字段内注入等。实测中,' OR 1=1 -- 或 ' || (SELECT password FROM system.metadata.users) || ' 在拼接场景下仍可能穿透。
关键点在于:Trino 不解析参数值,只解析 SQL 模板。只有 PreparedStatement 能让参数值完全脱离语法树。
- 拼接生成的 SQL 会完整送入
SqlParser.createStatement(),值和结构混在一起校验 -
PreparedStatement的参数值由 JDBC 驱动序列化为二进制协议字段,不参与 SQL 解析 - Trino 协调器收到的是已预编译的语句 ID + 参数值映射表,不是原始 SQL 字符串
JDBC 中正确使用 PreparedStatement
必须用 Connection.prepareStatement() 创建语句,再用 setString()、setLong() 等方法绑定参数——不能用 Statement.executeQuery("SELECT * FROM t WHERE id = '" + userInput + "'")。
- 占位符只支持
?,不支持命名参数(如:name或${col}) - 模板中不能把表名、列名、
ORDER BY字段、LIMIT值作为参数——这些属于 SQL 结构,必须静态写死或通过白名单校验后拼接 - 批量插入时,用
addBatch()+executeBatch(),避免循环调用prepareStatement()
示例:
String sql = "SELECT name, email FROM users WHERE tenant_id = ? AND status = ? AND created_at > ?"; PreparedStatement ps = connection.prepareStatement(sql); ps.setString(1, "acme-inc"); // ✅ 安全 ps.setString(2, "active"); // ✅ 安全 ps.setTimestamp(3, since); // ✅ 安全(注意是 setTimestamp,不是 setString) ResultSet rs = ps.executeQuery();
动态表名 / 列名等结构化参数怎么办
这类内容无法参数化,必须走白名单校验 + 严格格式限制。
- 提取变量后,只允许匹配正则
[a-zA-Z_][a-zA-Z0-9_]*(即标准标识符规则) - 查
system.metadata.table_comments或information_schema.tables确认表存在且属于授权 catalog/schema - 禁止任何用户输入进入
FROM、JOIN、GROUP BY、ORDER BY子句,除非经过双校验(白名单 + 元数据比对) - Trino 的
ResourceGroupNameTemplate支持${user}这类模板,但仅用于资源组路由,**不用于生成 SQL 文本**
错误示例(危险):
// ❌ 绝对禁止
String table = request.getParameter("table");
statement.executeQuery("SELECT * FROM " + table);REST API 场景下如何防注入
Trino REST API(/v1/statement)不接受预编译语句,只接收原始 SQL 字符串。此时必须在业务层做前置净化:
- 禁用所有非必要 SQL 关键字:
UNION、WITH、EXECUTE、CREATE、DROP、ALTER等(只允许SELECT开头的只读查询) - 用
SqlParser类本地解析(不提交),捕获ParsingException并拒绝含子查询、CTE、函数调用的语句(除非白名单) - 强制添加
LIMIT(如... LIMIT 10000),防止全表扫描滥用 - 所有参数值必须从请求体 JSON 中提取,并用 JDBC
PreparedStatement执行,而非拼进 REST body
本质:REST 接口只是通道,真正的参数化必须落在 JDBC 层,不能寄希望于服务端“自动识别并过滤”。
Trino 的防护链条很清晰:客户端负责结构安全(白名单)、值安全(PreparedStatement)、协议安全(SSL + 认证),服务端只做语法合规与权限校验。最容易被忽略的是——把“用了 PreparedStatement”当成万能解药,却放任表名、排序字段、分页参数等结构化内容直通 SQL 模板。


















