In this article
So vermeiden Sie SSRF-Schwachstellen in Go-Anwendungen
Go wird für die Entwicklung skalierbarer und effizienter Webanwendungen immer beliebter. Daher ist es unerlässlich, Sicherheitslücken wie SSRF zu beheben. Das Nebenläufigkeitsmodell und die Performance von Go machen die Sprache zu einer ausgezeichneten Wahl für serverseitige Anwendungen. Entwickler müssen jedoch stets auf potenzielle Sicherheitsbedrohungen achten.
In diesem Artikel erfahren Sie, wie sich SSRF-Schwachstellen in Go-Anwendungen äußern und wie Entwickler wirksame Sicherheitsmaßnahmen umsetzen können, um ihre Anwendungen und Daten zu schützen.
Wir analysieren den Code Schritt für Schritt, um die SSRF-Schwachstelle zu identifizieren, und besprechen die damit verbundenen Sicherheitsrisiken. Außerdem stellen wir Tools wie Snyk Code vor, mit denen Entwickler SSRF-Schwachstellen erkennen und beheben können, bevor sie ihre Anwendungen in der Produktionsumgebung bereitstellen.
Was ist eine SSRF-Sicherheitslücke?
Server-Side Request Forgery (SSRF) ist eine kritische Sicherheitslücke, die es Angreifern ermöglicht, von einer serverseitigen Anwendung aus Anfragen an unbeabsichtigte Ziele zu senden. Dies kann zu unbefugtem Zugriff auf interne Dienste, zur Offenlegung sensibler Daten und sogar zur Remote-Codeausführung führen. SSRF nutzt das Vertrauen aus, das Server in die interne Netzwerkkommunikation setzen. Dadurch können Angreifer URLs manipulieren und Anfragen an bösartige Endpunkte umleiten.
Häufige Angriffsvektoren und Auswirkungen von SSRF
SSRF-Schwachstellen entstehen häufig, wenn eine Anwendung Benutzereingaben verwendet, um URLs für serverseitige Anfragen zu erstellen, ohne diese ordnungsgemäß zu validieren oder zu bereinigen. Angreifer nutzen dies aus, indem sie schädliche Eingaben erstellen, die die Serveranfrage an ein unbeabsichtigtes Ziel umleiten, etwa:
Interne Dienste (z. B. Metadatendienste in Cloud-Umgebungen)
Localhost-Schnittstellen
Andere interne Netzwerkressourcen
Wird eine SSRF-Schwachstelle in Go-Anwendungen von Angreifern ausgenutzt, kann dies schwerwiegende Folgen haben, darunter:
Datenexfiltration: Zugriff auf sensible Daten interner Dienste, etwa anderer Microservices, auf die die anfällige Go-Anwendung zugreifen kann.
Dienstunterbrechung: Senden von Anfragen, die interne Dienste überlasten oder stören.
Eine anfällige Go-Anwendung
Die folgende Go-Anwendung ist ein einfacher HTTP-Server, der mit dem Gin-Web-Framework erstellt wurde. Er stellt einen POST-Endpunkt /cloudpawnery/image bereit, über den Benutzer eine Bilddatei herunterladen und ihre Größe ändern können. Hier ein Überblick über die wichtigsten Komponenten:
Konfiguration und Go-Strukturen:
Mit dem folgenden Go-Code in unserem Webprogramm definieren wir zunächst die Konfiguration für das Abrufen entfernter Dateien sowie das erwartete Datenschema für eingehende Anfragen an die API-Route:
const baseHost = "localtest.me:8080"
type FileInfo struct {
Filename string `json:"filename"`
Download string `json:"download"`
}Im obigen Codeausschnitt:
baseHost ist eine Konstante, die die Basisdomain für die URL zum Herunterladen von Dateien festlegt.
FileInfo ist eine Struktur, die das erwartete JSON-Format des Dateispeicherservers abbildet.
Eine Funktion zum Herunterladen über HTTP und Ändern der Größe von Assets
Die Funktion downloadAndResize erhält tenantID, fileID und fileSize als Parameter. Sie erstellt eine URL, um eine JSON-Datei mit Informationen zu der herunterzuladenden und in der Größe anzupassenden Datei abzurufen.
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
}Die Funktion führt folgende Schritte aus:
Erstellt mithilfe von
tenantIDundfileIDdie URL.Sendet eine HTTP-GET-Anfrage, um die JSON-Daten abzurufen.
Analysiert das JSON, um die Download-URL und den Dateinamen zu extrahieren.
Lädt die Datei herunter und speichert sie lokal.
Ändert die Bildgröße mit dem Befehl
convert.
Unsere Go-Hauptfunktion
Im Zentrum des Go-Programms instanziieren wir einen Webserver, definieren eine POST-Endpunkt-Route und rufen die Funktion downloadAndResize auf:
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")
}Der POST-Endpunkt ist über die HTTP-Route /cloudpawnery/image definiert und extrahiert tenantID, fileID und fileSize aus den Abfrageparametern. Außerdem führt er folgende Aufgaben aus:
- Prüft, ob tenantID und fileID vorhanden sind.
- Ruft downloadAndResize auf, um das Bild zu verarbeiten.
- Gibt eine JSON-Antwort zurück, die den Erfolg oder Misserfolg angibt.
Die SSRF-Schwachstelle im Code identifizieren
Die SSRF-Schwachstelle in dieser Go-Anwendung entsteht dadurch, wie tenantID zum Erstellen der URL für die HTTP-Anfrage verwendet wird.
Schauen wir uns die Sicherheitslücke genauer an:
Vom Benutzer kontrollierte Eingabe:
tenantIDwird direkt aus der Abfragezeichenfolge der HTTP-Anfrage extrahiert, ohne validiert oder bereinigt zu werden. Dadurch kann ein AngreifertenantIDso manipulieren, dass sie auf eine beliebige Domain verweist.
URL-Erstellung: Die Funktion downloadAndResize erstellt mithilfe von tenantID und fileID eine URL. Fragen Sie sich: Ist diese Zeichenfolgenverkettung eine sichere Methode, um eine URL zu erstellen? Was könnte schiefgehen?
urlStr := fmt.Sprintf("http://%s.%s/storage/%s.json", tenantID, baseHost, fileID)Gibt ein Angreifer eine schädliche tenantID an, kann er die gesamte Subdomain kontrollieren und die Anfrage möglicherweise an einen schädlichen Server umleiten.
HTTP-Anfrage: Die Anwendung sendet eine HTTP-GET-Anfrage an die erstellte URL:
resp, err := http.Get(urlStr)Diese Anfrage kann an einen vom Angreifer kontrollierten Server umgeleitet werden. So kann dieser auf interne Dienste zugreifen oder andere schädliche Aktionen ausführen.
Der anfällige Go-Code, der zu SSRF führt
In der bereitgestellten Go-Anwendung wird tenantID aus der Abfragezeichenfolge einer HTTP-Anfrage extrahiert und direkt zum Erstellen einer URL verwendet. Anschließend wird diese URL in einer http.Get-GET-Anfrage verwendet, ohne sie zu validieren oder zu bereinigen.
Dadurch entsteht eine Server-Side-Request-Forgery-Schwachstelle (SSRF). Durch Manipulation von tenantID kann ein Angreifer eine URL erstellen, die auf einen unbeabsichtigten Host verweist und möglicherweise den Zugriff auf sensible interne Dienste oder Daten ermöglicht.
Die SSRF-Schwachstelle mit einer schädlichen tenantID ausnutzen
Ein Angreifer kann diese Schwachstelle ausnutzen, indem er eine schädliche tenantID angibt, die die Anfrage an einen unbeabsichtigten Server umleitet.
Sendet der Angreifer beispielsweise eine Anfrage mit einer tenantID wie e:@evil.com/url=, wird die folgende URL erstellt: http://1234:e@evil.com/url=.localtest.me:8080/storage/fileID.json. Dadurch werden JSON-Informationen von der Website evil.com statt vom lokalen Microservice abgerufen, und der Angreifer kann die Konfiguration der Speicherdatei kontrollieren.
SSRF erkennen und Sicherheitsanalysen mit Snyk Code durchführen
Sie fragen sich, wie ich diese SSRF-Schwachstelle in meinem Go-Code entdeckt habe?
Ich habe die Snyk-Erweiterung in meiner VS-Code-IDE installiert. Beim Speichern meines Codes erscheint eine rote Wellenlinie, und Snyk erkennt, dass ich eine unsichere Codezeile eingefügt habe, die zu einer Sicherheitslücke führen kann.
So findet und meldet Snyk Server-Side-Request-Forgery-Schwachstellen in Echtzeit:

Wie Sie sehen, findet Snyk nicht nur Sicherheitslücken, sondern zeigt auch detailliert den Codepfad und den Aufrufablauf der benutzergesteuerten Eingabe bis zur Sink-Funktion in Form eines HTTP-Aufrufs.
Snyk geht noch einen Schritt weiter und zeigt mir mehrere Lösungsvorschläge, die auf Commits aus anderen Open-Source-Projekten basieren und dabei helfen, das Sicherheitsproblem zu beheben.
Installieren Sie die Snyk-Erweiterung, um dieselbe Erfahrung zu machen und auch Ihren Code zu schützen. Oder verbinden Sie einfach Ihr Code-Repository in GitHub, GitLab oder Bitbucket mit der Snyk-App. Sie überwacht Ihr Repository kontinuierlich und stellt diese Erkennungen und Korrekturen bereit.
Als weiterführende Lektüre zur Sicherheit empfehle ich Go-Entwicklern, sich mit den Best Practices für Go-Sicherheit vertraut zu machen.
Schließen Sie die Lücke zwischen Sicherheit und Entwicklung
Entdecken Sie die sechs Säulen für den Erfolg von DevSecOps und erfahren Sie, wie sie sich in Ihrem Unternehmen umsetzen lassen.