6 Melhores Práticas de Segurança para Go

Uma tradução do artigo foi preparada especificamente para os alunos do curso Golang Developer .





A popularidade de Golang aumentou significativamente nos últimos anos. Projetos bem-sucedidos como Docker , Kubernetes e Terraform fizeram uma grande aposta nessa linguagem de programação. O Go tornou-se recentemente o padrão de fato para a criação de ferramentas de linha de comando. Em termos de segurança, o Go foi gerenciado com sucesso de acordo com seus relatórios de vulnerabilidade, possuindo apenas um registro CVE desde 2002 .

No entanto, a ausência de vulnerabilidades não significa que a linguagem de programação seja altamente segura. Nós, humanos, podemos criar aplicativos inseguros se não seguirmos certas regras. Por exemplo, conhecendo as regras para escrever código seguro do OWASP, podemos pensar em como aplicar esses métodos ao usar o Go. E é exatamente isso que farei desta vez. Neste post, mostrarei seis práticas a serem consideradas ao desenvolver com o Go.

1. Verifique a entrada


A verificação da entrada do usuário é necessária não apenas para fins funcionais, mas também ajuda a evitar ataques de criminosos cibernéticos que nos enviam dados intrusivos que podem danificar o sistema. Além disso, você ajuda os usuários a usar essa ferramenta com mais confiança, impedindo-os de cometer erros tolos e comuns. Por exemplo, você pode impedir que um usuário tente excluir várias entradas de uma só vez.

Para testar a entrada do usuário, você pode usar pacotes Go nativos, como strconv, para manipular a conversão de cadeias para outros tipos de dados. O Go também suporta expressões regulares com regexppara verificações complexas. Embora o Go prefira usar bibliotecas nativas, existem pacotes de terceiros, como validador. Com o validador, você pode habilitar a validação para estruturas ou campos individuais com muito mais facilidade. Por exemplo, o código a seguir verifica se a estrutura Usercontém um endereço de email válido:

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 modelos HTML


Uma vulnerabilidade crítica comum é o script entre sites ou XSS. A principal exploração é que um invasor pode injetar código malicioso no aplicativo para modificar a saída. Por exemplo, alguém pode enviar código JavaScript como parte de uma string de consulta em um URL. Quando o aplicativo retorna o valor do usuário, o código JavaScript pode ser executado. Portanto, como desenvolvedor, você deve estar ciente dessa possibilidade e limpar os dados inseridos pelo usuário.

O Go possui um pacote html / template para codificar o que o aplicativo retorna ao usuário. Assim, em vez do navegador fazer entradas como<script>alert(‘ !’);</script>ele dará um aviso; Você pode codificar a entrada e o aplicativo processará a entrada como código HTML típico impresso em um navegador. Um servidor HTTP que retorna um modelo HTML terá a seguinte aparência:

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


Mas existem bibliotecas de terceiros que você pode usar ao desenvolver aplicativos da Web no Go. Por exemplo, o kit de ferramentas da web Gorilla , que inclui bibliotecas para ajudar os desenvolvedores a codificar valores de autenticação de cookies. Há também o nosurf , que é um pacote HTTP que ajuda a impedir a falsificação de solicitações entre sites ( CSRF ).

3. Proteja-se da injeção de SQL


Se você está desenvolvendo há algum tempo, pode conhecer as injeções de SQL, que ainda ocupam a primeira posição no topo da OWASP . No entanto, existem alguns pontos específicos que você deve considerar ao usar o Go. A primeira coisa que você precisa fazer é garantir que o usuário que se conecta ao banco de dados tenha direitos limitados. Também é uma boa prática desinfetar a entrada do usuário, conforme descrito na seção anterior, ou escapar de caracteres especiais e usar a função HTMLEscapeString do pacote de modelos HTML.

Mas o trecho de código mais importante que você precisa incluir é o uso de consultas parametrizadas. No Go, você não prepara uma expressão (esta é uma Declaração Preparada) em conexão; Você o prepara no banco de dados. Aqui está um exemplo de como usar consultas parametrizadas:

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


No entanto, e se o mecanismo de banco de dados não suportar o uso de expressões preparadas? Ou, se isso afeta o desempenho da consulta? Bem, você pode usar a função db.Query (), mas primeiro certifique-se de limpar a entrada do usuário, conforme mostrado nas seções anteriores. Também existem bibliotecas de terceiros, como o sqlmap , para impedir a injeção de SQL.

Apesar de todos os nossos esforços, algumas vezes as vulnerabilidades deslizam ou entram em nossos aplicativos por meio de terceiros. Para proteger seus aplicativos da Web contra ataques críticos, como a injeção de SQL, considere o uso de uma plataforma de gerenciamento de segurança de aplicativos, como o Sqreen .

4. Criptografar informações confidenciais


O fato de ser difícil ler a string, por exemplo, no formato base 64, não significa que o valor oculto seja secreto. Você precisa de uma maneira de criptografar informações que os invasores não podem decodificar facilmente. As informações típicas que você deseja criptografar são senhas de bancos de dados, senhas de usuários ou até números de segurança social.

A OWASP tem várias recomendações sobre o que algoritmos de criptografia usado, por exemplo, bcrypt, PDKDF2, Argon2ou scrypt. Felizmente, existe um pacote Go que inclui implementações confiáveis ​​para criptografar informações - criptografia . O código a seguir é um exemplo de uso 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)
	}
}


Observe que você ainda precisa ter cuidado ao transferir informações entre serviços. Você não gostaria de enviar dados do usuário em texto sem formatação. Não importa se o aplicativo criptografa os dados do usuário antes de salvar. Suponha que alguém na Internet possa ouvir seu tráfego e manter registros das solicitações do seu sistema. Um invasor pode usar essas informações para combiná-las com outros dados de outros sistemas.

5. Forneça conectividade HTTPS


Atualmente, a maioria dos navegadores exige que o HTTPS funcione em todos os sites. O Chrome, por exemplo, exibirá um aviso se o site não usar HTTPS. O departamento de segurança da informação pode usar a criptografia de trânsito para comunicação entre serviços como uma política. Ou seja, para garantir a segurança da conexão de trânsito no sistema, não se trata apenas de ouvir o aplicativo na porta 443. Você também precisa usar os certificados apropriados e o HTTPS para impedir que os invasores façam o downgrade do protocolo para HTTP.

Aqui está um trecho de código para um aplicativo Web que fornece e usa o protocolo 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"))
}


Observe que o aplicativo escutará na porta 443. A próxima linha é a linha que fornece a configuração HTTPS:

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


Você também pode especificar o nome do servidor na configuração do TLS desta maneira:
config := &tls.Config{ServerName: "yourSiteOrServiceName"}


É sempre recomendável que você use criptografia de backend, mesmo que seu aplicativo da Web seja apenas para comunicação interna. Imagine se, por algum motivo, um invasor pudesse ouvir seu tráfego interno. Sempre que possível, é sempre melhor elevar a fasquia para possíveis futuros atacantes.

6. Preste atenção aos erros e logs.


Por último, mas não menos importante, tratamento de erros e login em seus aplicativos Go.

Para solucionar problemas de produção com sucesso, você precisa configurar corretamente seus aplicativos. Você deve levar em consideração os erros que você mostra aos usuários. Você não gostaria que os usuários soubessem o que exatamente deu errado. Os invasores podem usar essas informações para determinar quais serviços e tecnologias você usa. Além disso, lembre-se de que, embora os logs sejam maravilhosos, eles são armazenados em algum lugar. E se os logs caírem nas mãos erradas, eles poderão ser usados ​​para incorporar o próximo ataque no sistema.

Portanto, a primeira coisa que você precisa aprender ou lembrar é que não há exceções no Go. Isso significa que você precisa lidar com os erros de maneira diferente do que em outros idiomas. O padrão é assim:

if err != nil {
    //  
}


O Go também oferece uma biblioteca interna para trabalhar com logs. O código mais simples é assim:

package main

import (
	"log"
)

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


Mas existem bibliotecas de terceiros para manter logs. Alguns destes são logrus, gloge loggo. Aqui está um pequeno trecho de código usando 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")
}


Por fim, certifique-se de aplicar todas as recomendações anteriores, como criptografia e limpeza dos dados que você coloca nos logs.

Sempre há espaço para crescer


Essas diretrizes são o mínimo que deve ser específico para seus aplicativos Go. No entanto, se o seu aplicativo for uma ferramenta de linha de comando, você não precisará de métodos de criptografia para o trânsito. Mas o restante das dicas de segurança se aplica a quase todos os tipos de aplicativos. Se você quiser saber mais, há um livro da OWASP especificamente para Go que aborda alguns tópicos. Há também um repositório contendo links para estruturas, bibliotecas e outras ferramentas de segurança no Go.

Não importa o que você acaba fazendo, lembre-se de que você sempre pode aumentar a segurança do seu aplicativo. Os invasores sempre encontrarão novas maneiras de explorar vulnerabilidades; portanto, tente trabalhar constantemente com a segurança do seu aplicativo.


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

.

All Articles