In this article
Éviter les vulnérabilités SSRF dans les applications Go
Go continue sans conteste de gagner en popularité pour la création d’applications Web évolutives et performantes. Il est donc essentiel de traiter les vulnérabilités de sécurité telles que les SSRF. Le modèle de concurrence et les performances de Go en font un excellent choix pour les applications côté serveur, mais les développeurs doivent rester vigilants face aux menaces de sécurité potentielles.
Dans cet article, nous allons découvrir comment les vulnérabilités SSRF se manifestent dans les applications Go et comment les développeurs peuvent mettre en place des mesures de sécurité efficaces pour protéger leurs applications et leurs données.
Nous analyserons le code étape par étape pour identifier la vulnérabilité SSRF et examiner les risques de sécurité associés. Nous présenterons également des outils comme Snyk Code, qui peuvent aider les développeurs à détecter et à corriger les vulnérabilités SSRF avant de déployer leurs applications en production.
Qu’est-ce qu’une vulnérabilité SSRF ?
La falsification de requêtes côté serveur (SSRF) est une vulnérabilité critique qui permet à un attaquant d’envoyer, depuis une application côté serveur, des requêtes vers des destinations non prévues. Cela peut entraîner un accès non autorisé à des services internes, l’exposition de données sensibles, voire l’exécution de code à distance. Les attaques SSRF exploitent la confiance accordée par les serveurs aux communications sur le réseau interne : les attaquants peuvent ainsi manipuler des URL et rediriger les requêtes vers des points de terminaison malveillants.
Vecteurs d’attaque courants et conséquences des SSRF
Les vulnérabilités SSRF surviennent souvent lorsqu’une application accepte des données saisies par l’utilisateur pour construire des URL destinées à des requêtes côté serveur, sans validation ni assainissement adéquats. Les attaquants exploitent cette faille en créant des données malveillantes qui redirigent la requête du serveur vers une destination non prévue, par exemple :
Des services internes (par exemple, des services de métadonnées dans des environnements cloud)
Des interfaces localhost
D’autres ressources du réseau interne
L’exploitation par des attaquants d’une vulnérabilité SSRF dans des applications Go peut avoir de graves conséquences, notamment :
Exfiltration de données : accès à des données sensibles depuis des services internes, comme d’autres microservices accessibles à l’application Go vulnérable.
Interruption de service : envoi de requêtes qui surchargent ou perturbent les services internes.
Une application Go vulnérable
L’application Go suivante est un serveur HTTP simple créé avec le framework Web Gin. Elle expose un point de terminaison POST /cloudpawnery/image qui permet aux utilisateurs de télécharger et de redimensionner un fichier image. Voici les principaux composants :
Configuration et structures Go :
À partir du code Go suivant de notre programme Web, nous allons définir la configuration initiale pour récupérer des fichiers à distance ainsi que le schéma de données attendu dans les requêtes entrantes sur la route de l’API :
const baseHost = "localtest.me:8080"
type FileInfo struct {
Filename string `json:"filename"`
Download string `json:"download"`
}Dans l’extrait de code ci-dessus :
baseHost est une constante qui définit le domaine de base utilisé pour construire l’URL de téléchargement des fichiers.
FileInfo est une structure qui représente la structure JSON attendue du serveur de stockage des fichiers.
Une fonction pour télécharger des ressources via HTTP et les redimensionner
La fonction downloadAndResize prend tenantID, fileID et fileSize en paramètres. Elle construit une URL pour télécharger un fichier JSON contenant des informations sur le fichier à télécharger et à redimensionner.
func downloadAndResize(tenantID, fileID, fileSize string) error {
// Example input based on the mocked storage server in fixtures/http directory of the project:
log.Printf("Processing request for tenantID: %s, fileID: %s", tenantID, fileID)
urlStr := fmt.Sprintf("http://%s.%s/storage/%s.json", tenantID, baseHost, fileID)
fmt.Println("Resolved URL: ", urlStr)
// Parse the URL to extract the hostname
parsedURL, err := url.Parse(urlStr)
if (err != nil) {
panic(err)
}
fmt.Println("Resolved Hostname: ", parsedURL.Hostname())
// Make HTTP request
resp, err := http.Get(urlStr)
if (err != nil) {
panic(err)
}
defer resp.Body.Close()
// Read response body
body, err := ioutil.ReadAll(resp.Body)
if (err != nil) {
panic(err)
}
// Decode JSON data
var info FileInfo
err = json.Unmarshal(body, &info)
if (err != nil) {
panic(err)
}
// Download file
downloadResp, err := http.Get(info.Download)
if (err != nil) {
panic(err)
}
defer downloadResp.Body.Close()
// Create target filename
targetFilename := fmt.Sprintf("uploads/%s", info.Filename)
// read the downloaded file into memory
fileBytes, err := ioutil.ReadAll(downloadResp.Body)
if (err != nil) {
panic(err)
}
// Save downloaded file
err = ioutil.WriteFile(targetFilename, fileBytes, 0644)
if (err != nil) {
panic(err)
}
convertCmd := fmt.Sprintf("convert %s -resize %sx%s %s", targetFilename, fileSize, fileSize, targetFilename)
fmt.Println("Running command:", convertCmd)
_, err = exec.Command("sh", "-c", convertCmd).Output()
if (err != nil) {
fmt.Println("Error resizing image:", err)
} else {
fmt.Println("Downloaded and resized image:", targetFilename)
}
return nil
}La fonction effectue les étapes suivantes :
Construit l’URL à l’aide de
tenantIDetfileID.Effectue une requête HTTP GET pour récupérer les données JSON.
Analyse le JSON pour extraire l’URL de téléchargement et le nom du fichier.
Télécharge le fichier et l’enregistre localement.
Redimensionne l’image à l’aide de la commande
convert.
La fonction principale de notre programme Go
C’est au cœur du programme Go que nous instancions un serveur Web, définissons une route de point de terminaison POST et appelons la fonction downloadAndResize :
func main() {
// Create a Gin router
router := gin.Default()
// Define a POST endpoint
router.POST("/cloudpawnery/image", func(c *gin.Context) {
// If data lives on the query string we can use this:
tenantID := c.Query("tenantID")
fileID := c.Query("fileID")
fileSize := c.Query("fileSize")
if (fileSize == "") {
fileSize = "200"
}
// Validate tenantID and fileID
if (tenantID == "" || fileID == "") {
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing tenantID or fileID"})
return
}
// Call the download and resize function
err := downloadAndResize(tenantID, fileID, fileSize)
if (err != nil) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Return a success response
c.JSON(http.StatusOK, gin.H{"message": "File downloaded and resized successfully"})
})
// Start the HTTP server
router.Run(":7000")
}Le point de terminaison POST est défini par la route HTTP /cloudpawnery/image, qui extrait tenantID, fileID et fileSize des paramètres de requête. Il effectue également les tâches suivantes :
- Vérifie la présence de tenantID et de fileID.
- Appelle downloadAndResize pour traiter l’image.
- Renvoie une réponse JSON indiquant la réussite ou l’échec.
Identifier la vulnérabilité SSRF dans le code
La vulnérabilité SSRF de cette application Go vient de la manière dont tenantID est utilisé pour construire l’URL de la requête HTTP.
Examinons en détail cette vulnérabilité de sécurité :
Donnée contrôlée par l’utilisateur :
tenantIDest extrait directement de la chaîne de requête HTTP, sans validation ni assainissement. Un attaquant peut ainsi modifiertenantIDpour qu’il pointe vers un domaine arbitraire.
Construction de l’URL : la fonction downloadAndResize construit une URL à l’aide de tenantID et de fileID. C’est le moment de vous demander si cette concaténation de chaînes est une méthode sûre pour construire une URL. Que pourrait-il mal se passer ?
urlStr := fmt.Sprintf("http://%s.%s/storage/%s.json", tenantID, baseHost, fileID)Si un attaquant fournit un tenantID malveillant, il peut contrôler l’ensemble du sous-domaine et potentiellement rediriger la requête vers un serveur malveillant.
Requête HTTP : l’application effectue une requête HTTP GET vers l’URL construite :
resp, err := http.Get(urlStr)Cette requête peut être redirigée vers un serveur contrôlé par un attaquant, qui peut alors accéder à des services internes ou mener d’autres actions malveillantes.
Le code Go vulnérable à l’origine de la SSRF
Dans l’application Go fournie, tenantID est extrait de la chaîne de requête HTTP et directement utilisé pour construire une URL. Cette URL est ensuite transmise à la fonction de requête GET http.Get, sans validation ni assainissement.
Cette pratique ouvre la voie à une vulnérabilité de type Server-Side Request Forgery (SSRF). En modifiant tenantID, un attaquant peut créer une URL qui pointe vers un hôte non prévu et ainsi potentiellement accéder à des services ou à des données internes sensibles.
Exploiter la vulnérabilité SSRF avec un tenantID malveillant
Un attaquant peut exploiter cette vulnérabilité en fournissant un tenantID malveillant pour rediriger la requête vers un serveur non prévu.
Par exemple, si l’attaquant envoie une requête avec un tenantID tel que e:@evil.com/url=, l’URL construite devient http://1234:e@evil.com/url=.localtest.me:8080/storage/fileID.json. La requête récupérera alors les informations JSON du site evil.com au lieu du microservice local, et l’attaquant pourra contrôler la configuration du fichier de stockage.
Détecter les SSRF et analyser la sécurité avec Snyk Code
Vous vous demandez comment j’ai découvert cette vulnérabilité SSRF cachée dans mon code Go ?
J’ai installé l’extension Snyk dans mon IDE VS Code. Lorsque j’enregistre mon code, elle affiche un soulignement rouge ondulé et Snyk détecte que j’ai introduit une ligne de code non sécurisée susceptible d’entraîner une vulnérabilité de sécurité.
Voici comment Snyk détecte et signale en temps réel les vulnérabilités Server-Side Request Forgery :

Comme vous pouvez le constater, Snyk ne se contente pas de détecter les vulnérabilités de sécurité : il fournit également le détail du chemin d’exécution dans le code, depuis la donnée contrôlée par l’utilisateur jusqu’à la fonction cible, sous la forme d’un appel HTTP.
Snyk va encore plus loin en me proposant plusieurs correctifs pour résoudre le problème de sécurité, à partir de modifications apportées dans d’autres projets open source.
Pour bénéficier de la même expérience et sécuriser votre code, installez l’extension Snyk ou connectez simplement votre dépôt de code GitHub, GitLab ou Bitbucket à l’application Snyk. Celle-ci le surveillera en continu pour détecter les problèmes et proposer des correctifs.
Pour approfondir vos connaissances en sécurité, je recommande aux développeurs Go de consulter les bonnes pratiques de sécurité Go.
Comblez le fossé entre la sécurité et le développement
Découvrez les six piliers de la réussite du DevSecOps et comment les mettre en œuvre dans votre organisation.