近年来,Golang越来越广泛。 诸如Docker,Kubernetes和Terraform之类的成功项目已经在这种编程语言上大举赌注。 Go已经成为创建命令行工具的事实上的标准。 而且,如果我们谈论安全性,事实证明,在Go的这一方面,一切都处于完美的状态。 即,自2002年以来,CVE注册表中仅记录了
一个 Golang漏洞。
但是,编程语言中没有漏洞这一事实并不意味着用该语言编写的任何应用程序都是完全安全的。 如果开发人员不遵守某些建议,那么即使使用这种语言,他也可能会创建不受保护的应用程序。 对于Go,可以通过参考
OWASP材料找到类似的建议。

本文的作者(我们今天将要翻译的译文)基于OWASP数据提出了6条关于在Go上开发安全应用程序的建议。
1.验证用户输入
用户输入的数据不仅需要进行验证,以确保应用程序正常运行。 它还旨在打击入侵者,这些入侵者使用以特殊方式准备的数据试图破坏系统。 此外,检查用户数据有助于保护用户免受常见错误的影响,从而使用户更自信地使用应用程序。 例如,通过分析用户命令,可以防止在可能导致系统故障的情况下尝试同时删除多个记录。
您可以使用标准的Go软件包来验证用户输入。 例如,
strconv
软件包可帮助将字符串数据转换为其他类型的数据。 由于
regexp
,Go还支持正则表达式。 它们可用于实现复杂的数据验证脚本。 尽管在Go的开发环境中通常会优先选择标准库,但还是有一些第三方程序包旨在检查数据。 例如,
验证器 。 该软件包可轻松验证复杂的数据结构或单个值。 例如,以下代码检查
User
结构中包含的电子邮件地址的正确性:
package main import ( "fmt" "gopkg.in/go-playground/validator.v9" ) type User struct { Email string `json:"email" validate:"required,email"` Name string `json:"name" validate:"required"` } func main() { v := validator.New() a := User{ Email: "a", } err := v.Struct(a) for _, e := range err.(validator.ValidationErrors) { fmt.Println(e) } }
2.使用HTML模板
XSS(跨站点脚本,跨站点脚本)是一个严重且广泛存在的漏洞。 XSS漏洞使攻击者可以将可能影响应用程序生成的数据的恶意代码注入应用程序。 例如,有人可能将JavaScript代码作为URL中查询字符串的一部分发送给应用程序。 当应用程序处理此类请求时,可以执行此JavaScript代码。 结果,事实证明应用程序开发人员应该期望这一点并清理从用户那里收到的数据。
Go具有
html / template包,可让您生成受恶意片段保护的HTML代码。 结果,显示受攻击的应用程序的浏览器将执行恶意JavaScript代码,将其视为纯文本,而不是执行
<script>alert('You've Been Hacked!');</script>
,从而通知用户已被黑客攻击。 。 使用HTML模板的HTTP服务器如下所示:
package main import ( "html/template" "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { param1 := r.URL.Query().Get("param1") tmpl := template.New("hello") tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`) tmpl.ExecuteTemplate(w, "T", param1) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) }
在Go上开发Web应用程序时,可以使用第三方库。 假设这是
Gorilla网络工具包 。 该工具包包含帮助开发人员的库,例如,在身份验证Cookie中对值进行编码。 这是另一个项目
-nosurf 。 这是有助于防止
CSRF攻击的HTTP数据包。
3.保护您的项目免受SQL注入
如果您不熟悉Web开发,那么您可能知道SQL注入攻击(使用将任意SQL代码注入查询的方法)。 相应的漏洞仍然在
OWASP Top 10排名中排名第一。 为了保护应用程序免受SQL注入,您需要考虑一些功能。 因此,首先要确保的是连接到数据库的用户将具有有限的特权。 另外,建议清除用户输入的数据(如前所述),或转义特殊字符并使用
html/template
包中的
HTMLEscapeString函数。
但是,防止SQL注入的最重要的事情是使用参数化查询(准备好的表达式)。 在Go中,表达式不是为连接准备的,而是为数据库准备的。 这是使用参数化查询的示例:
customerName := r.URL.Query().Get("name") db.Exec("UPDATE creditcards SET name=? WHERE customerId=?", customerName, 233, 90)
但是,如果数据库引擎不支持使用预先准备的表达式怎么办? 但是,如果这会影响查询性能呢? 在这种情况下,可以使用
db.Query()
函数,但是首先必须记住要清除用户输入。 为了防止使用SQL注入进行攻击,可以使用第三方库,例如
sqlmap 。
应当指出,尽管已尽一切努力保护应用程序免受SQL攻击,但有时攻击者仍能成功完成这些攻击。 说-通过外部应用程序依赖项。 为了提高项目安全性级别,可以使用适当的工具来验证应用程序安全性。 例如,
Sqreen平台的工具。
4.加密重要信息
如果某条线(例如,用BASE64编码)无法被人读取,则这并不意味着其中所隐藏的信息得到了可靠的保护。 因此,必须对重要信息进行加密,以保护其免受可能访问加密数据的入侵者的侵害。 通常加密的信息,例如数据库密码,用户密码,用户个人数据。
在OWASP项目的框架内,针对首选的加密算法
提出了一些
建议 。 例如,它是
bcrypt
,
PDKDF2
,
Argon2
,
scrypt
。 有一个Go,
crypto软件包,其中包含各种加密算法的可靠实现。 这是使用
bcrypt
算法的示例:
package main import ( "database/sql" "context" "fmt" "golang.org/x/crypto/bcrypt" ) func main() { ctx := context.Background() email := []byte("john.doe@somedomain.com") password := []byte("47;u5:B(95m72;Xq") hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) if err != nil { panic(err) } stmt, err := db.PrepareContext(ctx, "INSERT INTO accounts SET hash=?, email=?") if err != nil { panic(err) } result, err := stmt.ExecContext(ctx, hashedPassword, email) if err != nil { panic(err) } }
请注意,即使使用加密,您也需要注意服务之间信息的安全传输。 例如,即使以加密形式存储用户密码,也不应将其以纯文本形式传输到某处。 在Internet上传输数据时,值得一开始的假设是,攻击者可以从某个系统中执行的请求中收集数据,从而拦截它们。 攻击者可以将收集到的信息与从其他系统接收到的数据进行匹配,从而可以破解他感兴趣的项目。
5.强制执行HTTPS
如今,大多数浏览器都需要与之一起打开的网站来支持HTTPS。 例如,如果在不使用HTTPS的情况下与网站交换数据,Chrome浏览器将在地址栏中显示一条通知。 在支持某个项目的组织中,可以应用安全策略,以组织组成该项目的服务之间的数据安全交换。 因此,为确保连接的安全性,不仅要注意侦听端口443的应用程序。项目必须具有适当的证书,还必须组织HTTPS的强制使用,以防止攻击者切换到HTTP数据交换。
这是强制实施HTTPS的示例应用程序:
package main import ( "crypto/tls" "log" "net/http" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains") w.Write([]byte("This is an example server.\n")) }) cfg := &tls.Config{ MinVersion: tls.VersionTLS12, CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, PreferServerCipherSuites: true, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_256_CBC_SHA, }, } srv := &http.Server{ Addr: ":443", Handler: mux, TLSConfig: cfg, TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0), } log.Fatal(srv.ListenAndServeTLS("tls.crt", "tls.key")) }
请注意,该应用程序侦听端口443。这是负责强制使用HTTPS的行:
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
此外,在TLS配置中指定服务器名称可能很有意义:
config := &tls.Config{ServerName: "yourSiteOrServiceName"}
即使Web应用程序只是一家内部聊天公司,也建议您始终使用对系统各部分之间传输的数据进行加密。 想象一下攻击者可能拦截通过网络传输的应用程序数据的攻击的可能后果。 只要有可能,就应该为可能对项目的攻击做准备,以尽可能使攻击者的生活复杂化。
6.注意错误处理和记录。
该项目是我们列表中的最后一个项目,但其重要性绝对与最后一个项目相差甚远。 在这里,我们谈论的是错误处理和日志记录。
为了及时处理生产中出现的问题,您需要为应用程序配备适当的工具。 同时,您应该非常小心显示给用户的错误消息。 您不应该向用户详细说明出了什么问题。 事实是,攻击者可以使用此信息来找出项目中使用了哪些服务和技术。 此外,值得记住的是,尽管通常以积极的眼光看待原木,但这些原木存储在某个地方。 而且,如果应用程序日志落入错误的人手中,则分析其中的信息可以帮助对项目进行攻击。
这里首先要考虑的是Go没有例外。 这意味着用Go编写的应用程序中的错误不会以与其他语言编写的应用程序中的错误相同的方式处理错误。 通常看起来像这样:
if err != nil {
此外,Go还有一个用于处理日志的标准库,称为
log
。 这是其用法的一个简单示例:
package main import ( "log" ) func main() { log.Print("Logging in Go!") }
有用于组织日志记录的第三方库。 例如,这是
logrus
,
glog
,
loggo
。 这是一个使用
logrus
的小例子:
package main import ( "os" log "github.com/sirupsen/logrus" ) func main() { file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND, 0644) if err != nil { log.Fatal(err) } defer file.Close() log.SetOutput(file) log.SetFormatter(&log.JSONFormatter{}) log.SetLevel(log.WarnLevel) log.WithFields(log.Fields{ "animal": "walrus", "size": 10, }).Info("A group of walrus emerges from the ocean") }
最后,在处理落入日志中的数据时,请使用我们已经讨论过的安全建议。 特别是,清除并加密此类数据。
总结
这里给出的建议是用Go编写的项目应具备的最低要求。 但是,如果所讨论的项目是命令行实用程序,则它不需要为通过网络传输的流量实施保护。 其余技巧适用于几乎所有类型的应用程序。 如果您想深入探讨在Go上开发安全应用程序的问题,请
阅读有关该主题
的OWASP书 。
这里是一个存储库,其中包含指向各种工具的链接,这些工具旨在确保Go应用程序的安全性。
无论您在保护应用程序安全方面做什么,都请记住,安全性总是可以提高的,并且攻击者一直在寻找新的攻击方法。 因此,您需要做的是保护应用程序,而不是视情况而定,而要不断进行。
亲爱的读者们! 您如何保护用Go编写的应用程序?
