golang mysql 防注入_Go,Gorm 和 Mysql 是如何防止 SQL 注入的
Go,Gorm 和 Mysql 是如何防止 SQL 注入的
SQL 注入和 SQL 預(yù)編譯技術(shù)
什么是 SQL 注入
所謂SQL注入(sql inject),就是通過(guò)把SQL命令插入到Web表單提交或輸入域名或頁(yè)面請(qǐng)求的查詢字符串,最終達(dá)到欺騙服務(wù)器執(zhí)行惡意的SQL命令。具體來(lái)說(shuō),它是利用現(xiàn)有應(yīng)用程序,將(惡意的)SQL命令注入到后臺(tái)數(shù)據(jù)庫(kù)引擎執(zhí)行的能力,它可以通過(guò)在Web表單中輸入(惡意)SQL語(yǔ)句得到一個(gè)存在安全漏洞的網(wǎng)站上的數(shù)據(jù)庫(kù),而不是按照設(shè)計(jì)者意圖去執(zhí)行SQL語(yǔ)句。
SQL 注入例子
如下所示,是一個(gè)用戶進(jìn)行登錄時(shí),輸入用戶名和密碼,再將數(shù)據(jù)通過(guò)表單傳送到后端進(jìn)行查詢的 SQL 語(yǔ)句。
sql = "SELECT USERNAME,PASSWORD FROM USER WHERE USERNAME='" + username + "' AND PASSWORD='" + password + "'";
上面這個(gè) SQL 語(yǔ)句就存在 SQL 注入的安全漏洞。
假如 user 表中有用戶名為 123456 ,密碼為 123456 的記錄,而在前臺(tái)頁(yè)面提交表單的時(shí)候用戶輸入的用戶名和密碼是隨便輸入的,這樣當(dāng)然是不能登錄成功的。
但是如果后臺(tái)處理的 SQL 語(yǔ)句是如上所寫(xiě),前臺(tái)頁(yè)面用戶名也是隨便輸入,而用戶輸入的密碼是這樣的 aaa' or '1'='1 ,處理登錄的 SQL 語(yǔ)句就相當(dāng)于是這樣的:
SELECT USERNAME,PASSWORD FROM USER WHERE USERNAME='123456' AND PASSWORD='aaa' or '1'='1';
我們知道,1=1 是 true,所以上面這個(gè) SQL 語(yǔ)句是可以執(zhí)行成功的,這是一個(gè) SQL 注入問(wèn)題。
SQL 注入的解決
上述 SQL 注入問(wèn)題產(chǎn)生的原因就是用戶的輸入是包含 SQL 語(yǔ)句的,而且后端執(zhí)行 SQL 語(yǔ)句時(shí)直接將用戶的輸入和查詢的 SQL 語(yǔ)句進(jìn)行了拼接。
因此,簡(jiǎn)單的拼接用戶輸入的數(shù)據(jù)和后端的查詢 SQL 語(yǔ)句,是不可行的,我們需要將用戶的輸入作為一個(gè)完整的字符串,而忽略內(nèi)部的 SQL 語(yǔ)句。當(dāng)用戶輸入的密碼是這樣的 aaa’ or ‘1’='1 ,處理登錄的 SQL 語(yǔ)句實(shí)際應(yīng)該執(zhí)行的是:
SELECT USERNAME,PASSWORD FROM USER WHERE USERNAME='123456' AND PASSWORD="aaa' or '1'='1";
這樣就可以避免 SQL 注入導(dǎo)致的安全漏洞。
SQL 預(yù)編譯技術(shù)
解決 SQL 注入問(wèn)題的這個(gè)方案的關(guān)鍵要點(diǎn)實(shí)際上是將 SQL 語(yǔ)句和用戶輸入的查詢數(shù)據(jù)分別進(jìn)行處理,而不是一視同仁的作為 SQL 語(yǔ)句的不同部分進(jìn)行拼接處理。在這個(gè)基礎(chǔ)上,就產(chǎn)生了 SQL 預(yù)編譯技術(shù)。
通常我們的一條 SQL 在 DB 接收到最終執(zhí)行完畢返回可以分為下面三個(gè)過(guò)程:
詞法和語(yǔ)義解析
優(yōu)化 SQL 語(yǔ)句,制定執(zhí)行計(jì)劃
執(zhí)行并返回結(jié)果
但是我們可以將其中需要用戶輸入的值用占位符替代,可以視為將 SQL 語(yǔ)句模板化或者說(shuō)參數(shù)化,再將這樣的 SQL 語(yǔ)句進(jìn)行預(yù)編譯的處理,在實(shí)際運(yùn)行的時(shí)候,再傳入用戶輸入的數(shù)據(jù)。
使用這樣的 SQL 預(yù)編譯技術(shù),除了可以防止 SQL 注入外,還可以對(duì)預(yù)編譯的 SQL 語(yǔ)句進(jìn)行緩存,之后的運(yùn)行就省去了解析優(yōu)化 SQL 語(yǔ)句的過(guò)程,可以加速 SQL 的查詢。
Gorm 和 Go 端的 SQL 預(yù)編譯
在 Gorm 中,就為我們封裝了 SQL 預(yù)編譯技術(shù),可以供我們使用。
db = db.Where("merchant_id = ?", merchantId)
在執(zhí)行這樣的語(yǔ)句的時(shí)候?qū)嶋H上我們就用到了 SQL 預(yù)編譯技術(shù),其中預(yù)編譯的 SQL 語(yǔ)句merchant_id = ?和 SQL 查詢的數(shù)據(jù)merchantId將被分開(kāi)傳輸至 DB 后端進(jìn)行處理。
db = db.Where(fmt.Sprintf("merchant_id = %s", merchantId))
而當(dāng)你使用這種寫(xiě)法時(shí),即表示 SQL 由用戶來(lái)進(jìn)行拼裝,而不使用預(yù)編譯技術(shù),隨之可能帶來(lái)的,就是 SQL 注入的風(fēng)險(xiǎn)。
Gorm 端的 SQL 預(yù)編譯
// SQLCommon is the minimal database connection functionality gorm requires. Implemented by *sql.DB.
type SQLCommon interface {
Exec(query string, args ...interface{}) (sql.Result, error)
......
}
Go 端的 SQL 預(yù)編譯
// src/database/sql/sql.go
func (db *DB) execDC(ctx context.Context, dc *driverConn, release func(error), query string, args []interface{}) (res Result, err error) {
......
resi, err = ctxDriverExec(ctx, execerCtx, execer, query, nvdargs)
......
if err != driver.ErrSkip {
......
return driverResult{dc, resi}, nil
}
......
si, err = ctxDriverPrepare(ctx, dc.ci, query)
......
ds := &driverStmt{Locker: dc, si: si}
......
return resultFromStatement(ctx, dc.ci, ds, args...)
}
實(shí)際的實(shí)現(xiàn)最終還是落到了go-sql-driver上,如下面代碼所示go-sql-driver支持開(kāi)啟預(yù)編譯和關(guān)閉預(yù)編譯,由mc.cfg.InterpolateParams = false、true決定,可以看出gorm中mc.cfg.InterpolateParams = true,即開(kāi)啟了預(yù)編譯
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
......
if len(args) != 0 {
if !mc.cfg.InterpolateParams {
return nil, driver.ErrSkip
}
prepared, err := mc.interpolateParams(query, args)
if err != nil {
return nil, err
}
query = prepared
}
......
err := mc.exec(query)
......
return nil, mc.markBadConn(err)
}
Mysql 端的SQL 預(yù)編譯
在MySQL中是如何實(shí)現(xiàn)預(yù)編譯的,MySQL在4.1后支持了預(yù)編譯,其中涉及預(yù)編譯的指令實(shí)例如下
可以通過(guò)PREPARE預(yù)編譯指令,SET傳入數(shù)據(jù),通過(guò)EXECUTE執(zhí)行命令
mysql> PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql> SET @a = 3;
Query OK, 0 rows affected (0.00 sec)
mysql> SET @b = 4;
Query OK, 0 rows affected (0.00 sec)
mysql> EXECUTE stmt1 USING @a, @b;
+------------+
| hypotenuse |
+------------+
| 5 |
+------------+
1 row in set (0.00 sec)
mysql> DEALLOCATE PREPARE stmt1;
Query OK, 0 rows affected (0.00 sec)
首先我們先簡(jiǎn)單回顧下客戶端使用 Prepare 請(qǐng)求過(guò)程:
客戶端發(fā)起 Prepare 命令將帶 “?” 參數(shù)占位符的 SQL 語(yǔ)句發(fā)送到數(shù)據(jù)庫(kù),成功后返回 stmtID。
具體執(zhí)行 SQL 時(shí),客戶端使用之前返回的 stmtID,并帶上請(qǐng)求參數(shù)發(fā)起 Execute 命令來(lái)執(zhí)行 SQL。
不再需要 Prepare 的語(yǔ)句時(shí),關(guān)閉 stmtID 對(duì)應(yīng)的 Prepare 語(yǔ)句。
這里展示不使用 sql 預(yù)編譯和使用 sql 預(yù)編譯時(shí)的 Mysql 的日志。
2020-06-30T08:14:02.430089Z 10 Query COMMIT
2020-06-30T08:14:02.432995Z 10 Query select * from user where merchant_id='123456'
2020-06-30T08:15:10.581287Z 12 Query COMMIT
2020-06-30T08:15:10.584109Z 12 Prepare select * from user where merchant_id =?
2020-06-30T08:15:10.584725Z 12 Execute select * from user where merchant_id ='123456'
總結(jié)
以上是生活随笔為你收集整理的golang mysql 防注入_Go,Gorm 和 Mysql 是如何防止 SQL 注入的的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: sql查询索引语句_sql优化总结--基
- 下一篇: hibernate mysql 主从_M