6 Best Security Practices for Go

A translation of the article was prepared specifically for students of the Golang Developer course .





The popularity of Golang has increased significantly in recent years. Successful projects such as Docker , Kubernetes and Terraform have made a big bet on this programming language. Go has recently become the de facto standard for creating command-line tools. In terms of security, Go has successfully managed according to its vulnerability reports, having only one CVE registry since 2002 .

However, the absence of vulnerabilities does not mean that the programming language is highly secure. We humans can create unsafe applications if we do not follow certain rules. For example, knowing the rules for writing safe code from OWASP, we can think about how to apply these methods when using Go. And this is exactly what I will do this time. In this post, I will show you six practices to consider when developing with Go.

1. Check input


Checking user input is necessary not only for functional purposes, it also helps to avoid attacks by cybercriminals who send us intrusive data that could damage the system. Moreover, you help users to use this tool more confidently, preventing them from making silly and common mistakes. For example, you can prevent a user from trying to delete multiple entries at once.

To test user input, you can use native Go packages such as strconv to handle converting strings to other data types. Go also supports regular expressions with regexpfor complex checks. Although Go prefers to use native libraries, there are third-party packages such as validator. With validator, you can enable validation for structures or individual fields much more easily. For example, the following code verifies that the structure Usercontains a valid email address:

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. Use HTML templates


One common critical vulnerability is cross-site scripting or XSS. The main exploit is that an attacker can inject malicious code into the application to modify the output. For example, someone might send JavaScript code as part of a query string in a URL. When the application returns the value of the user, JavaScript code can be executed. Therefore, as a developer, you should be aware of this possibility and clear the data entered by the user.

Go has an html / template package to encode what the application returns to the user. Thus, instead of the browser doing input like<script>alert(โ€˜ !โ€™);</script>he will give a warning; You can encode the input, and the application will process the input as typical HTML code printed in a browser. An HTTP server that returns an HTML template will look like this:

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)
}


But there are third-party libraries that you can use when developing web applications on Go. For example, the Gorilla web toolkit , which includes libraries to help developers do things like encode cookie authentication values. There is also nosurf , which is an HTTP package that helps prevent cross-site request forgery ( CSRF ).

3. Protect yourself from SQL injection


If you have been developing for some time, you may know about SQL injections, which still occupy the first position in the OWASP top . However, there are some specific points you should consider when using Go. The first thing you need to do is make sure that the user who connects to the database has limited rights. It is also good practice to sanitize user input, as I described in the previous section, or to escape special characters and use the HTMLEscapeString function from the HTML template package.

But the most important piece of code that you need to include is the use of parameterized queries. In Go, you do not prepare an expression (this is a Prepared Statement) in connection; You prepare it in the database. Here is an example of how to use parameterized queries:

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


However, what if the database engine does not support the use of prepared expressions? Or what if it affects query performance? Well, you can use the db.Query () function, but first make sure you sanitize user input, as shown in the previous sections. There are also third-party libraries, such as sqlmap , to prevent SQL injection.

Despite all our efforts, sometimes vulnerabilities slip or enter our applications through third parties. To protect your web applications from critical attacks such as SQL injection, consider using an application security management platform such as Sqreen .

4. Encrypt sensitive information


The fact that the string is difficult to read, for example, in base-64 format, does not mean that the hidden value is secret. You need a way to encrypt information that attackers cannot easily decode. Typical information that you would like to encrypt is database passwords, user passwords, or even social security numbers.

The OWASP has several recommendations on what encryption algorithms used, for example, bcrypt, PDKDF2, Argon2or scrypt. Fortunately, there is a Go package that includes reliable implementations for encrypting information - crypto . The following code is an example use 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)
	}
}


Please note that you still need to exercise caution when transferring information between services. You would not want to send user data in plain text. It doesnโ€™t matter if the application encrypts user data before saving. Suppose someone on the Internet can listen to your traffic and keep logs of your systemโ€™s requests. An attacker can use this information to match it with other data from other systems.

5. Provide HTTPS connectivity


Most browsers currently require HTTPS to work on every site. Chrome, for example, will show you a warning if the site does not use HTTPS. The information security department can use transit encryption for communication between services as a policy. That is, to ensure the security of the transit connection in the system, it is not only about listening to the application on port 443. You also need to use the appropriate certificates and use HTTPS to prevent attackers from downgrading the protocol to HTTP.

Here is a snippet of code for a web application that provides and uses the HTTPS protocol:

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"))
}


Note that the application will listen on port 443. The next line is the line that provides HTTPS configuration:

w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")


You can also specify the server name in the TLS configuration in this way:
config := &tls.Config{ServerName: "yourSiteOrServiceName"}


It is always recommended that you use back-end encryption, even if your web application is for internal communication only. Imagine if for some reason an attacker could listen to your internal traffic. Whenever possible, it is always best to raise the bar for possible future attackers.

6. Pay attention to errors and logs.


Last, but not least, error handling and logging in your Go applications.

To successfully troubleshoot production issues, you need to properly configure your applications. You must take into account the errors you show to users. You would not want users to know what exactly went wrong. Attackers can use this information to determine what services and technologies you use. In addition, you should remember that although the logs are wonderful, they are stored somewhere. And if the logs fall into the wrong hands, they can be used to embed the upcoming attack in the system.

So, the first thing you need to learn or remember is that there are no exceptions to Go. This means that you need to handle errors differently than in other languages. The standard looks like this:

if err != nil {
    //  
}


Go also offers a built-in library for working with logs. The simplest code looks like this:

package main

import (
	"log"
)

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


But there are third-party libraries for maintaining logs. Some of these are logrus, glogand loggo. Here is a small snippet of code using 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")
}


Finally, make sure that you apply all the previous recommendations, such as encryption and sanation of the data that you put in the logs.

There is always room to grow


These guidelines are the minimum that should be specific to your Go applications. However, if your application is a command line tool, you will not need encryption methods for transit. But the rest of the security tips apply to almost all types of applications. If you want to know more, there is a book from OWASP specifically for Go that goes into some topics. There is also a repository containing links to frameworks, libraries, and other security tools in Go.

No matter what you end up doing, remember that you can always increase the security of your application. Attackers will always find new ways to exploit vulnerabilities, so try to constantly work on the security of your application.


. โ€” , , , .NET, Node.js Java, Docker.

.

All Articles