Go的6种最佳安全做法

本文的翻译是专门为Golang Developer课程的学生准备的





近年来,Golang的知名度显着增加。诸如DockerKubernetes和Terraform之类的成功项目在这种编程语言上大举赌注。 Go最近已成为创建命令行工具的事实上的标准。在安全性方面,Go已根据其漏洞报告成功进行了管理,自2002年以来仅拥有一个CVE注册中心

但是,没有漏洞并不意味着编程语言是高度安全的。如果不遵守某些规则,我们人类可能会创建不安全的应用程序。例如,了解从OWASP编写安全代码规则,我们可以考虑使用Go时如何应用这些方法。而这正是我这次要做的。在本文中,我将向您展示在使用Go开发时要考虑的六种实践。

1.检查输入


检查用户输入不仅是出于功能目的,而且还有助于避免入侵者向我们发送可能损坏系统的侵入性数据的攻击。此外,您还可以帮助用户更放心地使用此工具,防止他们犯下愚蠢的常见错误。例如,您可以防止用户尝试一次删除多个条目。

要测试用户输入,可以使用本地的Go包(例如strconv)来处理将字符串转换为其他数据类型的过程。 Go还支持带有regexp用于复杂检查的正则表达式。尽管Go倾向于使用本机库,但是还是有第三方程序包,例如Validator使用验证器,可以更轻松地对结构或单个字段启用验证。例如,以下代码验证该结构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。主要漏洞是攻击者可以将恶意代码注入应用程序以修改输出。例如,有人可能将JavaScript代码作为URL中查询字符串的一部分发送。当应用程序返回用户值时,可以执行JavaScript代码。因此,作为开发人员,您应该意识到这种可能性,并清除用户输入的数据。

Go有一个html / template包,用于对应用程序返回给用户的内容进行编码。因此,代替浏览器进行类似<script>alert(‘ !’);</script>他会发出警告;您可以对输入进行编码,应用程序会将输入作为在浏览器中打印的典型HTML代码进行处理。返回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 Web工具箱,其中包括可帮助开发人员执行诸如编码Cookie身份验证值之类的库的库。还有nosurf,这是一个HTTP软件包,可帮助防止跨站点请求伪造(CSRF)。

3.保护自己免受SQL注入


如果您已经进行了一段时间的开发,那么您可能会了解有关SQL注入的知识,它们仍然占据OWASP top中的第一位。但是,在使用Go时,您应考虑一些特定点。您需要做的第一件事是确保连接到数据库的用户具有有限的权限。如上一节所述,清理用户输入,或者转义特殊字符并使用HTML模板包中HTMLEscapeString函数也是一种好习惯

但是,您需要包括的最重要的代码是使用参数化查询。在Go中,您无需准备表达式(这是一条准备语句) 在连接;您在数据库中准备它。这是如何使用参数化查询的示例:

customerName := r.URL.Query().Get("name")
db.Exec("UPDATE creditcards SET name=? WHERE customerId=?", customerName, 233, 90)


但是,如果数据库引擎不支持使用准备好的表达式怎么办?还是会影响查询性能呢?好了,您可以使用db.Query()函数,但首先请确保清理用户输入,如前几节所示。也有第三方库,例如sqlmap,以防止SQL注入。

尽管我们竭尽全力,但有时漏洞会通过第三方泄漏或进入我们的应用程序。为了保护Web应用程序免受诸如SQL注入之类的严重攻击,请考虑使用诸如Sqreen之类的应用程序安全管理平台

4.加密敏感信息


字符串难以读取(例如,以base-64格式显示)的事实并不意味着隐藏值是秘密的。您需要一种对攻击者无法轻松解码的信息进行加密的方法。您想要加密的典型信息是数据库密码,用户密码,甚至是社会保险号。

OWASP的有几个建议,在什么使用的加密算法,例如bcryptPDKDF2Argon2scrypt。幸运的是,有一个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协议的Web应用程序的代码片段:

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还提供了用于处理日志的内置库。最简单的代码如下所示:

package main

import (
	"log"
)

func main() {
	log.Print("Logging in Go!")
}


但是,还有用于维护日志的第三方库。其中一些是logrusglogloggo这是使用的一小段代码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应用程序的最低要求。但是,如果您的应用程序是命令行工具,则不需要加密方法来进行传输。但是其余的安全提示几乎适用于所有类型的应用程序。如果您想了解更多,OWASP上有专门针对Go书,其中涉及一些主题。还有一个存储库,其中包含指向Go中的框架,库和其他安全工具的链接。

无论最终要做什么,请记住,您可以始终提高应用程序的安全性。攻击者将始终找到利用漏洞的新方法,因此请尝试不断致力于应用程序的安全性。


. — , , , .NET, Node.js Java, Docker.

.

All Articles