Golang MySQL 驱动中的 Prepare 语句(防 SQL 注入)

go-sql-driver/mysql 有两个函数 Query() 和 Exec().
我想看看 Query() 是如何工作的.
两种模式
Query(query string, args ...interface{}) 函数根据是否存在 args 参数有两种模式.
纯文本模式
如果在没有 args 的情况下调用 Query(query), 我将其称为 纯文本模式.
在此模式中, 驱动程序不对查询字符串进行任何操作, 而是直接将其发送到 MySQL 服务器.
插值模式
如果查询字符串中有一些占位符 (例如 MySQL 中的 ?) 并传入了一些 args 进行插值, 我会调用 '插值模式'.
在此模式中, 驱动程序实际执行 3 个动作
- 准备一个语句.
- 使用给定的
args执行准备好的语句. - 关闭准备好的语句.
正如预备语句的口号: Prepare Once, Execute Many.
区别
SQL 注入
假设你有一张叫做 prepare 的表
| id | name |
|---|---|
| 1 | name1 |
| 2 | name2 |
| 3 | name3 |
可以在该表上运行这样一个 SQL select id, name from prepare where id = 1;.
其返回
| id | name |
|---|---|
| 1 | name1 |
还不错. 好的, 接下来我们看看如何在前面说的两种模式下实现它.
- 纯文本模式
func plaintextQuery(db *sql.DB, id string) *sql.Row {
return db.Query("select id, name from prepare where id = " + id + ";")
}
- 插值模式
func interpolationQuery(db *sql.DB, id string) *sql.Row {
return db.Query("select id, name from prepare where id = ?;", id)
}
当你将 "1" 作为 id 传递时, 一切都会正常. 但是, 真的没问题嘛? 我们来尝试一个 SQL 注入案例, 将 "1 or 1 = 1" 作为 id 传递.
糟糕, interpolationQuery() 仍然返回了一样的内容, 但是 plaintextQuery() 返回了表中的所有数据, 这意味着被污染的 SQL 已经被注入了.
性能
我分别用两种模式做了一个简单的插入 SQL.
Inserts Number: 100000
Plaintext Mode
Duration 16.058662357s
Interpolation Mode
Duration 24.076297264s
这意味着 纯文本模式 比 插值模式 有着更好的性能.
这是合理的, 因为 插值模式 下的每个 Query() 或 Exec() 必须执行 3 次网络通信.
结论
插值模式可用于规避大多数 SQL 注入, 这是非常重要的一点. 因此, 强烈建议使用它来规避用户输入参数中可能的 SQL 注入.纯文本模式在某种程序上具备更好的性能. 但是, 也有一些方法可以加快插值模式, 我将在后续讨论.
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
关于 LearnKu
推荐文章: