背景
朋友说他们公司近期发现一些SQL注入问题,究其原因还是因为代码中使用了拼接查询,没有使用参数化查询,而且这种历史遗留问题较难梳理,可能很多都是3-5年前的代码,于是和我了解一种批量白盒审计SQL注入的方法。
方案
通过gosec扫描代码中的注入问题:
gosec 是一个静态分析工具,用于扫描 Go 代码以查找潜在的安全问题。它可以识别常见的代码漏洞、敏感信息泄露和其他安全问题,帮助开发人员提前发现并修复潜在的安全隐患。
检测效果
问题代码1(通过%s进行拼接查询) - 可检测
package mainimport ("database/sql""fmt""net/http""os"_ "github.com/go-sql-driver/mysql"
)var db *sql.DBfunc main() {db, err := sql.Open("mysql", "username:password@tcp(localhost:3306)/mydb")if err != nil {fmt.Println("Failed to connect to the database:", err)os.Exit(1)}defer db.Close()http.HandleFunc("/search", searchHandler)http.ListenAndServe(":8080", nil)
}func searchHandler(w http.ResponseWriter, r *http.Request) {query := r.URL.Query().Get("query")if query == "" {fmt.Fprint(w, "Please provide a search query.")return}// Potential SQL injection vulnerability using fmt.Sprintf with %ssqlQuery := fmt.Sprintf("SELECT name, age FROM users WHERE name = '%s'", query)rows, err := db.Query(sqlQuery)if err != nil {fmt.Fprint(w, "An error occurred.")return}defer rows.Close()for rows.Next() {var name stringvar age intif err := rows.Scan(&name, &age); err != nil {fmt.Fprint(w, "An error occurred.")return}fmt.Fprintf(w, "Name: %s, Age: %d\n", name, age)}
}
扫描结果:
osec ./...
[gosec] 2023/10/30 10:06:45 Including rules: default
[gosec] 2023/10/30 10:06:45 Excluding rules: default
[gosec] 2023/10/30 10:06:45 Import directory: /xxx/tools/test
[gosec] 2023/10/30 10:06:45 Checking package: main
[gosec] 2023/10/30 10:06:45 Checking file: /xxx/tools/test/main.go
Results:Golang errors in file: [/xxx/tools/test/main.go]:> [line 9 : column 4] - could not import github.com/go-sql-driver/mysql (invalid package name: "")[/xxx/tools/test/main.go:23] - G114 (CWE-676): Use of net/http serve function that has no support for setting timeouts (Confidence: HIGH, Severity: MEDIUM)22: http.HandleFunc("/search", searchHandler)> 23: http.ListenAndServe(":8080", nil)24: }[/xxx/tools/test/main.go:34] - G201 (CWE-89): SQL string formatting (Confidence: HIGH, Severity: MEDIUM)33: // Potential SQL injection vulnerability using fmt.Sprintf with %s> 34: sqlQuery := fmt.Sprintf("SELECT name, age FROM users WHERE name = '%s'", query)35: rows, err := db.Query(sqlQuery)[/xxx/tools/test/main.go:23] - G104 (CWE-703): Errors unhandled. (Confidence: HIGH, Severity: LOW)22: http.HandleFunc("/search", searchHandler)> 23: http.ListenAndServe(":8080", nil)24: }Summary:Gosec : devFiles : 1Lines : 51Nosec : 0Issues : 3
包导入错误:[line 9 : column 4] - 无法导入 github.com/go-sql-driver/mysql,因为它的包名为空。这个问题是由于在代码中试图导入一个没有包名的包而引发的。你需要检查你的导入语句并确保包名正确。HTTP服务器超时设置问题:[/xxx/tools/test/main.go:23] - G114 (CWE-676): 使用net/http的serve函数,它不支持设置超时时间。这表示你的HTTP服务器在处理请求时没有设置超时,这可能导致潜在的性能和安全问题。SQL字符串格式化问题:[/xxx/tools/test/main.go:34] - G201 (CWE-89): SQL字符串格式化。你使用了fmt.Sprintf来构建SQL查询字符串,这会导致潜在的SQL注入漏洞。最好使用参数化查询或者正确转义输入来避免这种问题。未处理错误:[/xxx/tools/test/main.go:23] - G104 (CWE-703): 未处理错误。在HTTP服务器的启动代码中,错误没有被处理。你应该考虑捕获错误并采取适当的措施来处理它们,以提高代码的健壮性。
问题代码2(通过“ +”进行拼接查询) - 可检测
package mainimport ("database/sql""fmt""log""net/http""strings"_ "github.com/go-sql-driver/mysql"
)var db *sql.DBfunc main() {// 连接到数据库var err errordb, err = sql.Open("mysql", "username:password@tcp(localhost:3306)/mydatabase")if err != nil {log.Fatal(err)}defer db.Close()http.HandleFunc("/search", searchHandler)http.ListenAndServe(":8080", nil)
}func searchHandler(w http.ResponseWriter, r *http.Request) {query := r.URL.Query().Get("query")// 构建SQL查询字符串(易受SQL注入攻击)sqlQuery := "SELECT name, age FROM users WHERE name = '" + query + "'"rows, err := db.Query(sqlQuery)if err != nil {log.Println("Error executing query:", err)return}defer rows.Close()var name stringvar age intfor rows.Next() {err := rows.Scan(&name, &age)if err != nil {log.Println("Error scanning row:", err)continue}fmt.Fprintf(w, "Name: %s, Age: %d\n", name, age)}
}
扫描结果:
gosec ./... 1 ↵
[gosec] 2023/10/30 10:26:49 Including rules: default
[gosec] 2023/10/30 10:26:49 Excluding rules: default
[gosec] 2023/10/30 10:26:49 Import directory: /xxx/tools/test
[gosec] 2023/10/30 10:26:50 Checking package: main
[gosec] 2023/10/30 10:26:50 Checking file: /xxx/tools/test/main.go
Results:Golang errors in file: [/xxx/tools/test/main.go]:> [line 9 : column 4] - could not import github.com/go-sql-driver/mysql (invalid package name: "")[/xxx/tools/test/main.go:24] - G114 (CWE-676): Use of net/http serve function that has no support for setting timeouts (Confidence: HIGH, Severity: MEDIUM)23: http.HandleFunc("/search", searchHandler)> 24: http.ListenAndServe(":8080", nil)25: }[/xxx/tools/test/main.go:31] - G202 (CWE-89): SQL string concatenation (Confidence: HIGH, Severity: MEDIUM)30: // 构建SQL查询字符串(易受SQL注入攻击)> 31: sqlQuery := "SELECT name, age FROM users WHERE name = '" + query + "'"32:[/xxx/tools/test/main.go:24] - G104 (CWE-703): Errors unhandled. (Confidence: HIGH, Severity: LOW)23: http.HandleFunc("/search", searchHandler)> 24: http.ListenAndServe(":8080", nil)25: }Summary:Gosec : devFiles : 1Lines : 50Nosec : 0Issues : 3
包导入错误:[line 9 : column 4] - 无法导入 github.com/go-sql-driver/mysql,因为它的包名为空。这个问题是由于在代码中试图导入一个没有包名的包而引发的。你需要检查你的导入语句并确保包名正确。HTTP服务器超时设置问题:[/xxx/tools/test/main.go:24] - G114 (CWE-676): 使用net/http的serve函数,它不支持设置超时时间。这表示你的HTTP服务器在处理请求时没有设置超时,这可能导致潜在的性能和安全问题。SQL字符串拼接问题:[/xxx/tools/test/main.go:31] - G202 (CWE-89): SQL字符串拼接。在构建SQL查询字符串时,你直接将用户输入 query 字符串与其他SQL代码拼接在一起,这容易受到SQL注入攻击。这是一个严重的安全问题,应该避免使用字符串拼接构建SQL查询。未处理错误:[/xxx/tools/test/main.go:24] - G104 (CWE-703): 未处理错误。在HTTP服务器的启动代码中,错误没有被处理。你应该考虑捕获错误并采取适当的措施来处理它们,以提高代码的健壮性。
结论
gosec目前通过静态扫描方式,基本满足现有SQL注入场景的检测需求。