6 recomendações para o desenvolvimento de aplicativos Go seguros

Nos últimos anos, Golang se espalhou cada vez mais. Projetos bem-sucedidos como Docker, Kubernetes e Terraform fizeram grandes apostas nessa linguagem de programação. O Go se tornou o padrão de fato para a criação de ferramentas de linha de comando. E se falamos de segurança, nesta área da Go tudo está em perfeita ordem. Ou seja, desde 2002, apenas uma vulnerabilidade Golang foi registrada no registro CVE.

No entanto, o fato de não haver vulnerabilidades na linguagem de programação não significa que qualquer aplicativo escrito nessa linguagem seja completamente seguro. Se o desenvolvedor não aderir a certas recomendações, ele poderá criar um aplicativo desprotegido, mesmo nesse idioma. No caso do Go, você pode encontrar recomendações semelhantes consultando os materiais da OWASP .



O autor do artigo, cuja tradução estamos publicando hoje, formulou, com base nos dados da OWASP, 6 recomendações para o desenvolvimento de aplicativos seguros no Go.

1. Verifique a entrada do usuário


A validação dos dados inseridos pelo usuário é necessária não apenas para garantir o correto funcionamento do aplicativo. Também visa combater invasores que, usando dados preparados de maneira especial, estão tentando atrapalhar o sistema. Além disso, a verificação dos dados do usuário ajuda os usuários a trabalhar com o aplicativo com mais confiança, pois os protege de erros comuns. Por exemplo, analisando comandos do usuário, você pode impedir uma tentativa de excluir vários registros ao mesmo tempo em uma situação em que essa ação possa levar ao mau funcionamento do sistema.

Você pode usar pacotes padrão Go para verificar a entrada do usuário. Por exemplo, o pacote strconv ajuda a converter dados de seqüência de caracteres em outros tipos de dados. O Go também suporta, graças ao regexp , expressões regulares. Eles podem ser usados ​​para implementar scripts de validação de dados complexos. Apesar do fato de que, no ambiente de desenvolvimento em que a preferência Go é normalmente atribuída às bibliotecas padrão, existem pacotes de terceiros destinados a verificar dados. Por exemplo, validador . Este pacote facilita a verificação de estruturas de dados complexas ou valores únicos. Por exemplo, o código a seguir verifica a estrutura do User quanto à exatidão do endereço de email contido nela:

 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


O XSS (script entre sites, script entre sites) é uma vulnerabilidade séria e generalizada. A vulnerabilidade XSS permite que um invasor injete código malicioso no aplicativo que pode afetar os dados gerados pelo aplicativo. Por exemplo, alguém pode enviar código JavaScript para o aplicativo, como parte de uma sequência de consultas em um URL. Quando o aplicativo processa essa solicitação, esse código JavaScript pode ser executado. Como resultado, o desenvolvedor do aplicativo deve esperar isso e limpar os dados recebidos do usuário.

O Go possui um pacote html / template que permite gerar código HTML protegido contra fragmentos maliciosos. Como resultado, o navegador que exibe o aplicativo atacado, em vez de executar código como <script>alert('You've Been Hacked!');</script> foi hackeado <script>alert('You've Been Hacked!');</script> , que informa ao usuário que foi hackeado, perceberá o código malicioso JavaScript como texto sem formatação . Veja como é um servidor HTTP usando modelos HTML:

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

Existem bibliotecas de terceiros que você pode usar ao desenvolver aplicativos da Web no Go. Digamos que este é o kit de ferramentas da web Gorilla . Este kit de ferramentas inclui bibliotecas que ajudam o desenvolvedor, por exemplo, a codificar valores em cookies de autenticação. E aqui está outro projeto - nosurf . Este é um pacote HTTP que ajuda a impedir ataques de CSRF .

3. Proteja seu projeto da injeção de SQL


Se você não é iniciante no desenvolvimento da Web, provavelmente conhece os ataques de injeção de SQL (usando um método de injeção de código SQL arbitrário nas consultas). A vulnerabilidade correspondente ainda está em primeiro lugar no ranking dos 10 melhores da OWASP . Para proteger aplicativos da injeção SQL, você precisa considerar alguns recursos. Portanto, a primeira coisa a garantir é que o usuário que se conecta ao banco de dados tenha privilégios limitados. Além disso, é recomendável limpar os dados inseridos pelo usuário, sobre os quais já falamos, ou escapar de caracteres especiais e usar a função HTMLEscapeString do pacote html/template .

Mas o mais importante na proteção contra injeção de SQL é o uso de consultas parametrizadas (expressões preparadas). No Go, as expressões não estão preparadas para uma conexão, mas para um banco de dados. Aqui está um exemplo do uso de consultas parametrizadas:

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

Mas e se o mecanismo de banco de dados não suportar o uso de expressões pré-preparadas? Mas e se isso afetar o desempenho da consulta? Nesses casos, você pode usar a função db.Query() , mas primeiro lembre-se de limpar a entrada do usuário. Para impedir ataques usando injeção SQL, você pode usar bibliotecas de terceiros, como o sqlmap .

Deve-se observar que, apesar de todos os esforços para proteger os aplicativos contra ataques SQL, algumas vezes os invasores ainda são bem-sucedidos. Diga - através de dependências externas de aplicativos. Para aumentar o nível de segurança do projeto, você pode usar as ferramentas apropriadas para verificar a segurança do aplicativo. Por exemplo, as ferramentas da plataforma Sqreen .

4. Criptografar informações importantes


Se uma pessoa não puder ler uma determinada sequência, digamos, na codificação BASE64, isso não significa que as informações ocultas nela sejam protegidas de forma confiável. Portanto, informações importantes devem ser criptografadas, protegendo-as de invasores que podem obter acesso a dados criptografados. Informações geralmente criptografadas, como senhas do banco de dados, senhas do usuário, dados pessoais do usuário.

Dentro da estrutura do projeto OWASP, são feitas algumas recomendações sobre os algoritmos de criptografia preferidos. Por exemplo, é bcrypt , PDKDF2 , Argon2 , scrypt . Existe um pacote Go, crypto , que contém implementações confiáveis ​​de vários algoritmos de criptografia. Aqui está um exemplo usando o algoritmo 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, mesmo usando criptografia, você precisa cuidar da transferência segura de informações entre os serviços. Por exemplo, você não deve transferir a senha do usuário em algum lugar em texto sem formatação, mesmo que esteja armazenada em forma criptografada. Ao transferir dados na Internet, vale a pena partir da suposição de que eles podem ser interceptados por um invasor que coleta dados de solicitações executadas em um determinado sistema. O invasor pode combinar as informações coletadas com os dados recebidos de outros sistemas e, como resultado, pode quebrar o projeto de seu interesse.

5. Aplicar HTTPS


Atualmente, a maioria dos navegadores exige sites que são abertos com eles para oferecer suporte a HTTPS. O Chrome, por exemplo, mostrará uma notificação na barra de endereço se os dados forem trocados com o site sem o uso de HTTPS. Em uma organização que apóia um determinado projeto, uma política de segurança pode ser aplicada com o objetivo de organizar uma troca segura de dados entre os serviços que compõem esse projeto. Como resultado, para garantir a segurança das conexões, é necessário prestar atenção não apenas ao aplicativo que atende na porta 443. O projeto deve ter os certificados apropriados, é necessário organizar o uso forçado de HTTPS para impedir que o invasor mude para a troca de dados HTTP.

Aqui está um exemplo de aplicativo que aplica 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 escuta na porta 443. E aqui está a linha responsável pelo uso forçado de HTTPS:

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

Além disso, especificar o nome do servidor na configuração do TLS pode fazer sentido:

 config := &tls.Config{ServerName: "yourSiteOrServiceName"} 

É recomendável que você sempre use criptografia de dados transmitidos entre partes do sistema, mesmo se o aplicativo da Web for um pouco de uma empresa de bate-papo interno. Imagine as possíveis consequências de um ataque no qual um invasor intercepta dados de aplicativos transmitidos por uma rede. Sempre que possível, vale a pena se preparar para possíveis ataques ao projeto, tentando complicar a vida dos invasores o máximo possível.

6. Tenha cuidado com o tratamento e registro de erros.


Este item é o último da nossa lista, mas definitivamente está longe do último em importância. Aqui estamos falando sobre tratamento e registro de erros.

Para lidar com os problemas que surgem na produção, você precisa equipar o aplicativo com as ferramentas apropriadas. Ao mesmo tempo, você deve ter muito cuidado com as mensagens de erro mostradas aos usuários. Você não deve informar ao usuário detalhes sobre o que deu errado. O fato é que um invasor pode usar essas informações para descobrir quais serviços e tecnologias são usados ​​no projeto. Além disso, vale lembrar que, embora os logs geralmente sejam percebidos de forma positiva, eles são armazenados em algum lugar. E se o log do aplicativo cair nas mãos erradas, a análise das informações dele pode ajudar na condução de um ataque ao projeto.

A primeira coisa a considerar aqui é que não há exceções ao Go. Isso significa que erros em aplicativos escritos em Go não tratam erros da mesma maneira que erros em aplicativos gravados em outros idiomas. Geralmente fica assim:

 if err != nil {    //   } 

Além disso, o Go possui uma biblioteca padrão para trabalhar com logs chamados log . Aqui está um exemplo simples de seu uso:

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

Existem bibliotecas de terceiros para organizar o log. Por exemplo, isso é logrus , glog , loggo . Aqui está um pequeno exemplo de uso do 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") } 

E, finalmente, ao trabalhar com dados que caem nos logs, use as recomendações de segurança que já discutimos. Em particular, limpe e criptografe esses dados.

Sumário


As recomendações fornecidas aqui são no mínimo que um projeto escrito em Go deve ter. Mas se o projeto em questão for um utilitário de linha de comando, ele não precisará implementar proteção para o tráfego transmitido pela rede. As dicas restantes se aplicam a quase qualquer tipo de aplicativo. Se você deseja explorar profundamente a questão do desenvolvimento de aplicativos seguros no Go, dê uma olhada no livro da OWASP sobre esse assunto. E aqui está um repositório que contém links para várias ferramentas destinadas a garantir a segurança dos aplicativos Go.

Tudo o que você faz no campo de proteger seus aplicativos, lembre-se de que a segurança sempre pode ser aprimorada e que os invasores estão constantemente encontrando novas maneiras de atacar. Portanto, a proteção de aplicativos é o que você precisa fazer, não de caso a caso, mas constantemente.

Caros leitores! Como você protege seus aplicativos escritos no Go?

Source: https://habr.com/ru/post/pt484614/


All Articles