6 mejores prácticas de seguridad para Go

Se preparó una traducción del artículo específicamente para estudiantes del curso Golang Developer .





La popularidad de Golang ha aumentado significativamente en los últimos años. Proyectos exitosos como Docker , Kubernetes y Terraform han hecho una gran apuesta en este lenguaje de programación. Go se ha convertido recientemente en el estándar de facto para crear herramientas de línea de comandos. En términos de seguridad, Go se ha gestionado con éxito de acuerdo con sus informes de vulnerabilidad, teniendo solo un registro CVE desde 2002 .

Sin embargo, la ausencia de vulnerabilidades no significa que el lenguaje de programación sea altamente seguro. Los humanos podemos crear aplicaciones inseguras si no seguimos ciertas reglas. Por ejemplo, conocer las reglas para escribir código seguro desde OWASP, podemos pensar en cómo aplicar estos métodos al usar Go. Y esto es exactamente lo que haré esta vez. En esta publicación, le mostraré seis prácticas para tener en cuenta al desarrollar con Go.

1. Comprobar entrada


La verificación de la entrada del usuario es necesaria no solo para fines funcionales, sino que también ayuda a evitar ataques de ciberdelincuentes que nos envían datos intrusivos que podrían dañar el sistema. Además, ayuda a los usuarios a usar esta herramienta con más confianza, evitando que cometan errores tontos y comunes. Por ejemplo, puede evitar que un usuario intente eliminar varias entradas a la vez.

Para probar la entrada del usuario, puede usar paquetes nativos de Go como strconv para manejar cadenas de conversión a otros tipos de datos. Go también admite expresiones regulares con regexpcontroles complejos. Aunque Go prefiere usar bibliotecas nativas, hay paquetes de terceros como el validador. Con el validador, puede habilitar la validación para estructuras o campos individuales mucho más fácilmente. Por ejemplo, el siguiente código verifica que la estructura Usercontiene una dirección de correo electrónico válida:

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


Una vulnerabilidad crítica común es la secuencia de comandos entre sitios o XSS. El principal problema es que un atacante puede inyectar código malicioso en la aplicación para modificar la salida. Por ejemplo, alguien podría enviar código JavaScript como parte de una cadena de consulta en una URL. Cuando la aplicación devuelve el valor del usuario, se puede ejecutar el código JavaScript. Por lo tanto, como desarrollador, debe ser consciente de esta posibilidad y borrar los datos ingresados ​​por el usuario.

Go tiene un paquete html / template para codificar lo que la aplicación devuelve al usuario. Por lo tanto, en lugar de que el navegador haga entradas como<script>alert(‘ !’);</script>él dará una advertencia; Puede codificar la entrada y la aplicación procesará la entrada como un código HTML típico impreso en un navegador. Un servidor HTTP que devuelve una plantilla HTML se verá así:

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


Pero hay bibliotecas de terceros que puede usar al desarrollar aplicaciones web en Go. Por ejemplo, el kit de herramientas web Gorilla , que incluye bibliotecas para ayudar a los desarrolladores a hacer cosas como codificar valores de autenticación de cookies. También hay nosurf , que es un paquete HTTP que ayuda a prevenir la falsificación de solicitudes entre sitios ( CSRF ).

3. Protégete de la inyección SQL


Si ha estado desarrollando durante algún tiempo, puede saber acerca de las inyecciones SQL, que aún ocupan la primera posición en la parte superior de OWASP . Sin embargo, hay algunos puntos específicos que debe considerar al usar Go. Lo primero que debe hacer es asegurarse de que el usuario que se conecta a la base de datos tenga derechos limitados. También es una buena práctica desinfectar la entrada del usuario, como describí en la sección anterior, o escapar de caracteres especiales y usar la función HTMLEscapeString del paquete de plantillas HTML.

Pero el código más importante que debe incluir es el uso de consultas parametrizadas. En Go, no prepara una expresión (esta es una declaración preparada) en conexión; Lo preparas en la base de datos. Aquí hay un ejemplo de cómo usar consultas parametrizadas:

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


Sin embargo, ¿qué sucede si el motor de la base de datos no admite el uso de expresiones preparadas? ¿O qué pasa si afecta el rendimiento de la consulta? Bueno, puede usar la función db.Query (), pero primero asegúrese de desinfectar la entrada del usuario, como se muestra en las secciones anteriores. También hay bibliotecas de terceros, como sqlmap , para evitar la inyección de SQL.

A pesar de todos nuestros esfuerzos, a veces las vulnerabilidades se escapan o ingresan a nuestras aplicaciones a través de terceros. Para proteger sus aplicaciones web de ataques críticos, como la inyección de SQL, considere usar una plataforma de administración de seguridad de aplicaciones como Sqreen .

4. Cifrar información confidencial


El hecho de que la cadena sea difícil de leer, por ejemplo, en formato base 64, no significa que el valor oculto sea secreto. Necesita una forma de cifrar la información que los atacantes no pueden decodificar fácilmente. La información típica que le gustaría encriptar son contraseñas de bases de datos, contraseñas de usuarios o incluso números de seguridad social.

La OWASP tiene varias recomendaciones en lo que utilizan algoritmos de cifrado, por ejemplo, bcrypt, PDKDF2, Argon2o scrypt. Afortunadamente, hay un paquete Go que incluye implementaciones confiables para cifrar información: criptografía . El siguiente código es un ejemplo 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)
	}
}


Tenga en cuenta que aún debe tener cuidado al transferir información entre servicios. No querrá enviar datos de usuario en texto sin formato. No importa si la aplicación cifra los datos del usuario antes de guardarlos. Suponga que alguien en Internet puede escuchar su tráfico y mantener registros de las solicitudes de su sistema. Un atacante puede usar esta información para que coincida con otros datos de otros sistemas.

5. Proporcionar conectividad HTTPS


La mayoría de los navegadores actualmente requieren HTTPS para funcionar en todos los sitios. Chrome, por ejemplo, le mostrará una advertencia si el sitio no usa HTTPS. El departamento de seguridad de la información puede usar el cifrado de tránsito para la comunicación entre servicios como una política. Es decir, para garantizar la seguridad de la conexión de tránsito en el sistema, no se trata solo de escuchar la aplicación en el puerto 443. También debe usar los certificados apropiados y usar HTTPS para evitar que los atacantes degraden el protocolo a HTTP.

Aquí hay un fragmento de código para una aplicación web que proporciona y utiliza el 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"))
}


Tenga en cuenta que la aplicación escuchará en el puerto 443. La siguiente línea es la línea que proporciona la configuración HTTPS:

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


También puede especificar el nombre del servidor en la configuración de TLS de esta manera:
config := &tls.Config{ServerName: "yourSiteOrServiceName"}


Siempre se recomienda que use cifrado de fondo, incluso si su aplicación web es solo para comunicación interna. Imagínese si por algún motivo un atacante pudiera escuchar su tráfico interno. Siempre que sea posible, siempre es mejor elevar el listón para posibles futuros atacantes.

6. Preste atención a los errores y registros.


Por último, pero no menos importante, este es el manejo de errores y el inicio de sesión en sus aplicaciones Go.

Para solucionar con éxito los problemas de producción, debe configurar correctamente sus aplicaciones. Debe tener en cuenta los errores que muestra a los usuarios. No querrá que los usuarios sepan exactamente qué salió mal. Los atacantes pueden usar esta información para determinar qué servicios y tecnologías usa. Además, debe recordar que aunque los registros son maravillosos, se almacenan en algún lugar. Y si los registros caen en las manos equivocadas, pueden usarse para incrustar el próximo ataque en el sistema.

Entonces, lo primero que debe aprender o recordar es que no hay excepciones para Go. Esto significa que debe manejar los errores de manera diferente que en otros idiomas. El estándar se ve así:

if err != nil {
    //  
}


Go también ofrece una biblioteca incorporada para trabajar con registros. El código más simple se ve así:

package main

import (
	"log"
)

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


Pero hay bibliotecas de terceros para mantener registros. Algunos de estos son logrus, glogy loggo. Aquí hay un pequeño fragmento 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")
}


Finalmente, asegúrese de aplicar todas las recomendaciones anteriores, como el cifrado y el saneamiento de los datos que coloca en los registros.

Siempre hay espacio para crecer


Estas pautas son las mínimas que deberían ser específicas para sus aplicaciones Go. Sin embargo, si su aplicación es una herramienta de línea de comandos, no necesitará métodos de cifrado para el tránsito. Pero el resto de los consejos de seguridad se aplican a casi todos los tipos de aplicaciones. Si desea saber más, hay un libro de OWASP específicamente para Go que trata algunos temas. También hay un repositorio que contiene enlaces a marcos, bibliotecas y otras herramientas de seguridad en Go.

No importa lo que termine haciendo, recuerde que siempre puede aumentar la seguridad de su aplicación. Los atacantes siempre encontrarán nuevas formas de explotar las vulnerabilidades, así que trate de trabajar constantemente en la seguridad de su aplicación.


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

.

All Articles