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 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: