> For the complete documentation index, see [llms.txt](https://willh.gitbook.io/build-web-application-with-golang-zhtw/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://willh.gitbook.io/build-web-application-with-golang-zhtw/09.0/09.4.md).

# 避免 SQL 注入

## 什麼是 SQL 注入

SQL 注入攻擊（SQL Injection），簡稱注入攻擊，是 Web 開發中最常見的一種安全漏洞。可以用它來從資料庫取得敏感資訊，或者利用資料庫的特性執行新增使用者，匯出檔案等一系列惡意操作，甚至有可能取得資料庫乃至系統使用者最高許可權。

而造成 SQL 注入的原因是因為程式沒有有效過濾使用者的輸入，使攻擊者成功的向伺服器提交惡意的 SQL 查詢程式碼，程式在接收後錯誤的將攻擊者的輸入作為查詢語句的一部分執行，導致原始的查詢邏輯被改變，額外的執行了攻擊者精心構造的惡意程式碼。

## SQL 注入範例

很多 Web 開發者沒有意識到 SQL 查詢是可以被篡改的，從而把 SQL 查詢當作可信任的命令。殊不知，SQL 查詢是可以繞開存取控制，從而繞過身份驗證和許可權檢查的。更有甚者，有可能透過 SQL 查詢去執行主機系統級的命令。

下面將透過一些真實的例子來詳細講解 SQL 注入的方式。

考慮以下簡單的登入表單：

```markup
<form action="/login" method="POST">
<p>Username: <input type="text" name="username" /></p>
<p>Password: <input type="password" name="password" /></p>
<p><input type="submit" value="登陸" /></p>
</form>
```

我們的處理裡面的 SQL 可能是這樣的：

```go
username:=r.Form.Get("username")
password:=r.Form.Get("password")
sql:="SELECT * FROM user WHERE username='"+username+"' AND password='"+password+"'"
```

如果使用者的輸入的使用者名稱如下，密碼任意

```go
myuser' or 'foo' = 'foo' --
```

那麼我們的 SQL 變成了如下所示：

```go
SELECT * FROM user WHERE username='myuser' or 'foo' = 'foo' --'' AND password='xxx'
```

在 SQL 裡面`--`是註釋標記，所以查詢語句會在此中斷。這就讓攻擊者在不知道任何合法使用者名稱和密碼的情況下成功登入了。

對於 MSSQL 還有更加危險的一種 SQL 注入，就是控制系統，下面這個可怕的例子將示範如何在某些版本的 MSSQL 資料庫上執行系統命令。

```go
sql:="SELECT * FROM products WHERE name LIKE '%"+prod+"%'"
Db.Exec(sql)
```

如果攻擊提交`a%' exec master..xp_cmdshell 'net user test testpass /ADD' --`作為變數 prod 的值，那麼 sql 將會變成

```go
sql:="SELECT * FROM products WHERE name LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'"
```

MSSQL 伺服器會執行這條 SQL 語句，包括它後面那個用於向系統新增新使用者的命令。如果這個程式是以 sa 執行而 MSSQLSERVER 服務又有足夠的許可權的話，攻擊者就可以獲得一個系統帳號來存取主機了。

> 雖然以上的例子是針對某一特定的資料庫系統的，但是這並不代表不能對其它資料庫系統實施類似的攻擊。針對這種安全漏洞，只要使用不同方法，各種資料庫都有可能遭殃。

## 如何預防 SQL 注入

也許你會說攻擊者要知道資料庫結構的資訊才能實施 SQL 注入攻擊。確實如此，但沒人能保證攻擊者一定拿不到這些資訊，一旦他們拿到了，資料庫就存在洩露的危險。如果你在用開放原始碼的軟體套件來存取資料庫，比如論壇程式，攻擊者就很容易得到相關的程式碼。如果這些程式碼設計不良的話，風險就更大了。目前 Discuz、phpwind、phpcms 等這些流行的開源程式都有被 SQL 注入攻擊的先例。

這些攻擊總是發生在安全性不高的程式碼上。所以，永遠不要信任外界輸入的資料，特別是來自於使用者的資料，包括選擇框、表單隱藏域和 cookie。就如上面的第一個例子那樣，就算是正常的查詢也有可能造成災難。

SQL 注入攻擊的危害這麼大，那麼該如何來防治呢 ? 下面這些建議或許對防治 SQL 注入有一定的幫助。

1. 嚴格限制 Web 應用的資料庫的操作許可權，給此使用者提供僅僅能夠滿足其工作的最低許可權，從而最大限度的減少注入攻擊對資料庫的危害。
2. 檢查輸入的資料是否具有所期望的資料格式，嚴格限制變數的型別，例如使用 regexp 套件進行一些匹配處理，或者使用 strconv 套件對字串轉化成其他基本型別的資料進行判斷。
3. 對進入資料庫的特殊字元（'"\尖括號&\*;等）進行轉義處理，或編碼轉換。Go 的`text/template`套件裡面的 `HTMLEscapeString` 函式可以對字串進行轉義處理。
4. 所有的查詢語句建議使用資料庫提供的參數化查詢介面，參數化的語句使用參數而不是將使用者輸入變數嵌入到 SQL 語句中，即不要直接拼接 SQL 語句。例如使用`database/sql`裡面的查詢函式 `Prepare` 和`Query`，或者`Exec(query string, args ...interface{})`。
5. 在應用釋出之前建議使用專業的 SQL 注入檢測工具進行檢測，以及時修補被發現的 SQL 注入漏洞。網上有很多這方面的開源工具，例如 sqlmap、SQLninja 等。
6. 避免網站顯示出 SQL 錯誤資訊，比如型別錯誤、欄位不匹配等，把程式碼裡的 SQL 語句暴露出來，以防止攻擊者利用這些錯誤資訊進行 SQL 注入。

## 總結

透過上面的範例我們可以知道，SQL 注入是危害相當大的安全漏洞。所以對於我們平常編寫的 Web 應用，應該對於每一個小細節都要非常重視，細節決定命運，生活如此，編寫 Web 應用也是這樣。

## links

* [目錄](https://github.com/doggy8088/build-web-application-with-golang-zhtw/tree/80abfb9f0c5f9f703922f4114d78f2e3c26a4dfb/preface.md)
* 上一節: [避免 XSS 攻擊](/build-web-application-with-golang-zhtw/09.0/09.3.md)
* 下一節: [儲存密碼](/build-web-application-with-golang-zhtw/09.0/09.5.md)
