6 recommandations pour développer des applications Go sécurisées

Ces dernières années, Golang s'est propagé de plus en plus. Des projets réussis comme Docker, Kubernetes et Terraform ont fait d'énormes paris sur ce langage de programmation. Go est devenu la norme de facto pour la création d'outils de ligne de commande. Et si nous parlons de sécurité, il s'avère que dans ce domaine chez Go, tout est en parfait état. A savoir, depuis 2002, une seule vulnérabilité Golang a été enregistrée dans le registre CVE.

Cependant, le fait qu'il n'y ait aucune vulnérabilité dans le langage de programmation ne signifie pas que toute application écrite dans ce langage sera complètement sûre. Si le développeur n'adhère pas à certaines recommandations, il peut très bien créer une application non protégée même dans un tel langage. Dans le cas de Go, vous pouvez trouver des recommandations similaires en vous référant au matériel OWASP .



L'auteur de l'article, dont nous publions aujourd'hui la traduction, a formulé, sur la base des données OWASP, 6 recommandations pour développer des applications sécurisées sur Go.

1. Vérifiez l'entrée utilisateur


La validation des données saisies par l'utilisateur est nécessaire non seulement pour assurer le bon fonctionnement de l'application. Il vise également à lutter contre les intrus qui, à l'aide de données préparées de manière particulière, tentent de perturber le système. De plus, la vérification des données utilisateur aide les utilisateurs à travailler avec l'application en toute confiance, car elle les protège des erreurs courantes. Par exemple, en analysant les commandes utilisateur, vous pouvez empêcher une tentative de suppression de plusieurs enregistrements en même temps dans une situation où une telle action peut entraîner un dysfonctionnement du système.

Vous pouvez utiliser des packages Go standard pour vérifier l'entrée utilisateur. Par exemple, le package strconv permet de convertir des données de chaîne en d'autres types de données. Go prend également en charge, grâce à regexp , les expressions régulières. Ils peuvent être utilisés pour implémenter des scripts de validation de données complexes. Malgré le fait que dans l'environnement de développement on préfère généralement Go aux bibliothèques standard, il existe des packages tiers destinés à vérifier les données. Par exemple, validateur . Ce package facilite la vérification de structures de données complexes ou de valeurs uniques. Par exemple, le code suivant vérifie la structure de l' User pour l'exactitude de l'adresse e-mail qu'elle contient:

 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. Utilisez des modèles HTML


XSS (cross-site scripting, cross-site scripting) est une vulnérabilité grave et répandue. La vulnérabilité XSS permet à un attaquant d'injecter du code malveillant dans l'application qui pourrait affecter les données générées par l'application. Par exemple, quelqu'un peut envoyer du code JavaScript à l'application, dans le cadre d'une chaîne de requête dans une URL. Lorsque l'application traite une telle demande, ce code JavaScript peut être exécuté. En conséquence, il s'avère que le développeur de l'application doit s'y attendre et nettoyer les données reçues de l'utilisateur.

Go a un package html / template qui vous permet de générer du code HTML protégé contre les fragments malveillants. En conséquence, le navigateur qui affiche l'application attaquée, au lieu d'exécuter du code comme <script>alert('You've Been Hacked!');</script> , qui informe l'utilisateur qu'il a été piraté, percevra le code JavaScript malveillant comme du texte brut . Voici à quoi ressemble un serveur HTTP utilisant des modèles 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) } 

Il existe des bibliothèques tierces que vous pouvez utiliser lors du développement d'applications Web sur Go. Disons que c'est la boîte à outils Web Gorilla . Cette boîte à outils comprend des bibliothèques qui aident le développeur, par exemple, à coder des valeurs dans les cookies d'authentification. Et voici un autre projet - nosurf . Il s'agit d'un paquet HTTP qui aide à prévenir les attaques CSRF .

3. Protégez votre projet de l'injection SQL


Si vous n'êtes pas nouveau dans le développement Web, vous connaissez probablement les attaques par injection SQL (en utilisant une méthode d'injection de code SQL arbitraire dans les requêtes). La vulnérabilité correspondante occupe toujours le premier rang du classement OWASP Top 10 . Pour protéger les applications de l'injection SQL, vous devez prendre en compte certaines fonctionnalités. Ainsi, la première chose à faire est que l'utilisateur qui se connecte à la base de données ait des privilèges limités. Il est recommandé, en outre, d'effacer les données entrées par l'utilisateur, comme nous l'avons déjà mentionné, ou d'échapper des caractères spéciaux et d'utiliser la fonction HTMLEscapeString du package html/template .

Mais la chose la plus importante dans la protection contre l'injection SQL est l'utilisation de requêtes paramétrées (expressions préparées). Dans Go, les expressions ne sont pas préparées pour une connexion, mais pour une base de données. Voici un exemple d'utilisation de requêtes paramétrées:

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

Mais que faire si le moteur de base de données ne prend pas en charge l'utilisation d'expressions pré-préparées? Mais que se passe-t-il si cela affecte les performances des requêtes? Dans de tels cas, vous pouvez utiliser la fonction db.Query() , mais vous devez d'abord vous rappeler d'effacer l'entrée utilisateur. Pour empêcher les attaques utilisant l'injection SQL, vous pouvez utiliser des bibliothèques tierces, comme sqlmap .

Il convient de noter que, malgré tous les efforts pour protéger les applications contre les attaques SQL, les attaquants réussissent parfois encore à ces attaques. Dites - à travers les dépendances des applications externes. Afin d'augmenter le niveau de sécurité du projet, vous pouvez utiliser les outils appropriés pour vérifier la sécurité des applications. Par exemple, les outils de la plateforme Sqreen .

4. Chiffrer les informations importantes


Si une certaine ligne, par exemple, dans le codage BASE64, ne peut pas être lue par une personne, cela ne signifie pas que les informations qui y sont cachées sont protégées de manière fiable. Par conséquent, les informations importantes doivent être cryptées, les protégeant des intrus qui peuvent accéder aux données cryptées. Informations généralement cryptées telles que les mots de passe de base de données, les mots de passe des utilisateurs, les données personnelles des utilisateurs.

Dans le cadre du projet OWASP, certaines recommandations sont faites concernant les algorithmes de chiffrement préférés. Par exemple, il s'agit de bcrypt , PDKDF2 , Argon2 , scrypt . Il existe un package Go, crypto , qui contient des implémentations fiables de divers algorithmes de cryptage. Voici un exemple utilisant l'algorithme 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)  } } 

Veuillez noter que même en utilisant le cryptage, vous devez prendre soin du transfert sécurisé des informations entre les services. Par exemple, vous ne devez pas transférer le mot de passe de l'utilisateur quelque part en texte brut, même s'il est stocké sous forme cryptée. Lors de la transmission de données sur Internet, il convient de partir de l'hypothèse qu'elles peuvent être interceptées par un attaquant qui collecte des données à partir de requêtes exécutées dans un certain système. L'attaquant peut faire correspondre les informations collectées avec les données reçues d'autres systèmes et, par conséquent, peut casser le projet qui l'intéresse.

5. Appliquer HTTPS


De nos jours, la plupart des navigateurs nécessitent des sites qui s'ouvrent avec eux pour prendre en charge HTTPS. Chrome, par exemple, affichera une notification dans la barre d'adresse si des données sont échangées avec le site sans utiliser HTTPS. Dans une organisation soutenant un certain projet, une politique de sécurité peut être appliquée visant à organiser un échange sécurisé de données entre les services qui composent ce projet. Par conséquent, pour assurer la sécurité des connexions, il faut faire attention non seulement à l'écoute de l'application sur le port 443. Le projet doit avoir les certificats appropriés, il est nécessaire d'organiser l'utilisation forcée de HTTPS afin d'empêcher l'attaquant de basculer vers l'échange de données HTTP.

Voici un exemple d'application qui applique 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")) } 

Veuillez noter que l'application écoute sur le port 443. Et voici la ligne responsable de l'utilisation forcée de HTTPS:

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

De plus, la spécification du nom du serveur dans la configuration TLS peut avoir un sens:

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

Il est recommandé de toujours utiliser le cryptage des données transmises entre les parties du système, même si l'application Web est un peu une société de chat interne. Imaginez les conséquences possibles d'une attaque dans laquelle un attaquant intercepte des données d'application transmises sur un réseau. Dans la mesure du possible, il vaut la peine de se préparer à d'éventuelles attaques contre le projet, en essayant de compliquer au maximum la vie des attaquants.

6. Faites attention à la gestion et à la journalisation des erreurs.


Cet article est le dernier de notre liste, mais il est certainement loin d'être le dernier en importance. Ici, nous parlons de la gestion des erreurs et de la journalisation.

Afin de faire face en temps opportun aux problèmes survenant dans la production, vous devez équiper l'application d'outils appropriés. Dans le même temps, vous devez faire très attention aux messages d'erreur affichés aux utilisateurs. Vous ne devez pas dire aux utilisateurs ce qui ne va pas. Le fait est qu'un attaquant peut utiliser ces informations afin de découvrir quels services et technologies sont utilisés dans le projet. De plus, il convient de rappeler que bien que les journaux soient généralement perçus sous un jour positif, ces journaux sont stockés quelque part. Et si le journal des applications tombe entre de mauvaises mains, l'analyse des informations qu'il contient peut aider à mener une attaque contre le projet.

La première chose à considérer ici est qu'il n'y a aucune exception à Go. Cela signifie que les erreurs dans les applications écrites en Go ne traitent pas les erreurs de la même manière que les erreurs dans les applications écrites dans d'autres langues. Habituellement, cela ressemble à ceci:

 if err != nil {    //   } 

De plus, Go possède une bibliothèque standard pour travailler avec des journaux, appelée log . Voici un exemple simple de son utilisation:

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

Il existe des bibliothèques tierces pour organiser la journalisation. Par exemple, il s'agit de logrus , glog , loggo . Voici un petit exemple d'utilisation 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") } 

Et enfin, lorsque vous travaillez avec des données qui tombent dans les journaux, utilisez les recommandations de sécurité dont nous avons déjà discuté. En particulier, effacez et cryptez ces données.

Résumé


Les recommandations données ici sont un minimum qu'un projet écrit en Go devrait avoir. Mais si le projet en question est un utilitaire de ligne de commande, il n'a pas besoin de mettre en œuvre de protection pour le trafic transmis sur le réseau. Les autres conseils s'appliquent à presque tous les types d'application. Si vous souhaitez approfondir la question du développement d'applications sécurisées sur Go, jetez un œil au livre OWASP sur ce sujet. Et voici un référentiel qui contient des liens vers divers outils visant à assurer la sécurité des applications Go.

Quoi que vous fassiez dans le domaine de la sécurisation de vos applications, n'oubliez pas que la sécurité peut toujours être améliorée et que les attaquants trouvent constamment de nouvelles façons d'attaquer. Par conséquent, la protection des applications est ce que vous devez faire, non pas au cas par cas, mais en permanence.

Chers lecteurs! Comment protégez-vous vos applications écrites en Go?

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


All Articles