6 recomendaciones para desarrollar aplicaciones Go seguras

En los últimos años, Golang se ha extendido cada vez más. Proyectos exitosos como Docker, Kubernetes y Terraform han hecho grandes apuestas en este lenguaje de programación. Go se ha convertido en el estándar de facto para crear herramientas de línea de comandos. Y si hablamos de seguridad, resulta que en esta área en Go todo está en perfecto orden. Es decir, desde 2002, solo se ha registrado una vulnerabilidad de Golang en el registro CVE.

Sin embargo, el hecho de que no haya vulnerabilidades en el lenguaje de programación no significa que ninguna aplicación escrita en este lenguaje sea completamente segura. Si el desarrollador no se adhiere a ciertas recomendaciones, puede crear una aplicación desprotegida incluso en dicho lenguaje. En el caso de Go, puede encontrar recomendaciones similares consultando los materiales de OWASP .



El autor del artículo, cuya traducción publicamos hoy, formuló, en base a los datos de OWASP, 6 recomendaciones para desarrollar aplicaciones seguras en Go.

1. Verificar la entrada del usuario


La validación de los datos ingresados ​​por el usuario es necesaria no solo para garantizar el correcto funcionamiento de la aplicación. También tiene como objetivo combatir a los intrusos que, utilizando datos preparados de una manera especial, están tratando de interrumpir el sistema. Además, la verificación de los datos del usuario ayuda a los usuarios a trabajar con la aplicación con mayor confianza, ya que los protege de errores comunes. Por ejemplo, al analizar los comandos del usuario, puede evitar un intento de eliminar varios registros al mismo tiempo en una situación en la que dicha acción puede conducir a un mal funcionamiento del sistema.

Puede usar paquetes Go estándar para verificar la entrada del usuario. Por ejemplo, el paquete strconv ayuda a convertir datos de cadena a otros tipos de datos. Go también admite, gracias a expresiones regulares, expresiones regulares. Se pueden usar para implementar scripts de validación de datos complejos. A pesar de que en el entorno de desarrollo en Go generalmente se da preferencia a las bibliotecas estándar, hay paquetes de terceros destinados a verificar los datos. Por ejemplo, validador . Este paquete facilita la verificación de estructuras de datos complejas o valores únicos. Por ejemplo, el siguiente código verifica la estructura del User para ver si la dirección de correo electrónico contenida es correcta:

 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


XSS (secuencias de comandos entre sitios, secuencias de comandos entre sitios) es una vulnerabilidad grave y generalizada. La vulnerabilidad XSS permite a un atacante inyectar código malicioso en la aplicación que podría afectar los datos generados por la aplicación. Por ejemplo, alguien puede enviar un código JavaScript a la aplicación, como parte de una cadena de consulta en una URL. Cuando la aplicación procesa dicha solicitud, se puede ejecutar este código JavaScript. Como resultado, resulta que el desarrollador de la aplicación debería esperar esto y limpiar los datos recibidos del usuario.

Go tiene un paquete html / template que le permite generar código HTML que está protegido contra fragmentos maliciosos. Como resultado, el navegador que muestra la aplicación atacada, en lugar de ejecutar código como <script>alert('You've Been Hacked!');</script> , que informa al usuario que fue pirateado, percibirá el código JavaScript malicioso como texto sin formato . Así es como se ve un servidor HTTP que usa plantillas 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) } 

Hay bibliotecas de terceros que puede usar al desarrollar aplicaciones web en Go. Digamos que este es el kit de herramientas web de Gorilla . Este kit de herramientas incluye bibliotecas que ayudan al desarrollador, por ejemplo, a codificar valores en cookies de autenticación. Y aquí hay otro proyecto: nosurf . Este es un paquete HTTP que ayuda a prevenir ataques CSRF .

3. Proteja su proyecto de la inyección SQL


Si no es nuevo en el desarrollo web, entonces probablemente sepa acerca de los ataques de inyección SQL (utilizando un método de inyección de código SQL arbitrario en consultas). La vulnerabilidad correspondiente todavía ocupa el primer lugar en el ranking de los 10 mejores de OWASP . Para proteger las aplicaciones de la inyección SQL, debe tener en cuenta algunas características. Por lo tanto, lo primero que debe asegurarse es que el usuario que se conecta a la base de datos tenga privilegios limitados. Además, se recomienda borrar los datos ingresados ​​por el usuario, como ya mencionamos, o escapar de caracteres especiales y usar la función HTMLEscapeString del paquete html/template .

Pero lo más importante en la protección contra la inyección de SQL es el uso de consultas parametrizadas (expresiones preparadas). En Go, las expresiones no están preparadas para una conexión, sino para una base de datos. Aquí hay un ejemplo del uso de consultas parametrizadas:

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

Pero, ¿qué sucede si el motor de la base de datos no admite el uso de expresiones preparadas previamente? Pero, ¿qué pasa si esto afecta el rendimiento de la consulta? En tales casos, puede usar la función db.Query() , pero primero debe recordar borrar la entrada del usuario. Para evitar ataques con inyección SQL, puede usar bibliotecas de terceros, como sqlmap .

Cabe señalar que, a pesar de todos los esfuerzos para proteger las aplicaciones de los ataques SQL, a veces los atacantes aún tienen éxito en estos ataques. Digamos, a través de dependencias de aplicaciones externas. Para aumentar el nivel de seguridad del proyecto, puede usar las herramientas apropiadas para verificar la seguridad de la aplicación. Por ejemplo, las herramientas de la plataforma Sqreen .

4. Cifrar información importante


Si una persona no puede leer una determinada cadena, por ejemplo, en la codificación BASE64, esto no significa que la información oculta en ella esté protegida de manera confiable. Por lo tanto, la información importante debe estar encriptada, protegiéndola de intrusos que pueden obtener acceso a datos encriptados. Por lo general, información cifrada, como contraseñas de bases de datos, contraseñas de usuarios, datos personales del usuario.

Dentro del marco del proyecto OWASP, se hacen algunas recomendaciones con respecto a los algoritmos de cifrado preferidos. Por ejemplo, es bcrypt , PDKDF2 , Argon2 , scrypt . Hay un paquete Go, crypto , que contiene implementaciones confiables de varios algoritmos de cifrado. Aquí hay un ejemplo usando el 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)  } } 

Tenga en cuenta que incluso utilizando el cifrado, debe encargarse de la transferencia segura de información entre los servicios. Por ejemplo, no debe transferir la contraseña del usuario a algún lugar en texto plano, incluso si está almacenada en forma cifrada. Al transmitir datos en Internet, vale la pena partir del supuesto de que pueden ser interceptados por un atacante que recopila datos de solicitudes ejecutadas dentro de un determinado sistema. El atacante puede hacer coincidir la información recopilada con los datos recibidos de otros sistemas y, como resultado, puede descifrar el proyecto que le interesa.

5. Hacer cumplir HTTPS


En la actualidad, la mayoría de los navegadores requieren sitios que se abran con ellos para admitir HTTPS. Chrome, por ejemplo, mostrará una notificación en la barra de direcciones si se intercambian datos con el sitio sin usar HTTPS. En una organización que respalda un determinado proyecto, se puede aplicar una política de seguridad dirigida a organizar un intercambio seguro de datos entre los servicios que conforman este proyecto. Como resultado, para garantizar la seguridad de las conexiones, es necesario prestar atención no solo a la aplicación que escucha en el puerto 443. El proyecto debe tener los certificados apropiados, es necesario organizar el uso forzado de HTTPS para evitar que el atacante cambie al intercambio de datos HTTP.

Aquí hay una aplicación de ejemplo 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")) } 

Tenga en cuenta que la aplicación escucha en el puerto 443. Y aquí está la línea responsable del uso forzado de HTTPS:

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

Además, puede tener sentido especificar el nombre del servidor en la configuración de TLS:

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

Se recomienda que utilice siempre el cifrado de los datos transmitidos entre partes del sistema, incluso si la aplicación web es un poco una empresa de chat interna. Imagine las posibles consecuencias de un ataque en el que un atacante intercepta los datos de la aplicación transmitidos a través de una red. Siempre que sea posible, vale la pena prepararse para posibles ataques al proyecto, tratando de complicar la vida de los atacantes tanto como sea posible.

6. Tenga cuidado con el manejo y registro de errores.


Este artículo es el último en nuestra lista, pero definitivamente está lejos de ser el último en importancia. Aquí estamos hablando sobre el manejo de errores y el registro.

Para hacer frente a los problemas que surgen en la producción, debe equipar la aplicación con las herramientas adecuadas. Al mismo tiempo, debe tener mucho cuidado con los mensajes de error que se muestran a los usuarios. No debe decirle al usuario detalles sobre lo que salió mal. El hecho es que un atacante puede usar esta información para averiguar qué servicios y tecnologías se utilizan en el proyecto. Además, vale la pena recordar que, aunque los registros generalmente se perciben de manera positiva, estos registros se almacenan en algún lugar. Y si el registro de la aplicación cae en las manos equivocadas, analizar la información que contiene puede ayudar a realizar un ataque al proyecto.

Lo primero a considerar aquí es que no hay excepciones para Go. Esto significa que los errores en las aplicaciones escritas en Go no manejan los errores de la misma manera que los errores en las aplicaciones escritas en otros idiomas. Por lo general, se ve así:

 if err != nil {    //   } 

Además, Go tiene una biblioteca estándar para trabajar con registros llamada log . Aquí hay un ejemplo simple de su uso:

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

Hay bibliotecas de terceros para organizar el registro. Por ejemplo, esto es logrus , glog , loggo . Aquí hay un pequeño ejemplo del uso de 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") } 

Y finalmente, cuando trabaje con datos que caen en los registros, use las recomendaciones de seguridad que ya hemos discutido. En particular, borre y cifre dichos datos.

Resumen


Las recomendaciones dadas aquí son un mínimo que debe tener un proyecto escrito en Go. Pero si el proyecto en cuestión es una utilidad de línea de comandos, entonces no necesita implementar protección para el tráfico transmitido a través de la red. Los consejos restantes se aplican a casi cualquier tipo de aplicación. Si desea explorar profundamente el tema del desarrollo de aplicaciones seguras en Go, eche un vistazo al libro de OWASP sobre este tema. Y aquí hay un repositorio que contiene enlaces a varias herramientas destinadas a garantizar la seguridad de las aplicaciones Go.

Independientemente de lo que haga en el campo de proteger sus aplicaciones, recuerde que la seguridad siempre se puede mejorar y que los atacantes constantemente encuentran nuevas formas de atacar. Por lo tanto, la protección de la aplicación es lo que debe hacer, no de un caso a otro, sino constantemente.

Estimados lectores! ¿Cómo protege sus aplicaciones escritas en Go?

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


All Articles