In this article
Cómo evitar vulnerabilidades SSRF en aplicaciones de Go
Sin duda, Go sigue ganando popularidad para crear aplicaciones web escalables y eficientes, por lo que es imprescindible abordar vulnerabilidades de seguridad como SSRF. El modelo de concurrencia y el rendimiento de Go lo convierten en una excelente opción para las aplicaciones del lado del servidor, pero los desarrolladores deben mantenerse alerta ante posibles amenazas de seguridad.
En este artículo, veremos cómo se manifiestan las vulnerabilidades SSRF en las aplicaciones de Go y cómo los desarrolladores pueden implementar medidas de seguridad eficaces para proteger sus aplicaciones y datos.
Analizaremos el código paso a paso para identificar la vulnerabilidad SSRF y hablaremos de los riesgos de seguridad asociados. Además, presentaremos herramientas como Snyk Code, que pueden ayudar a los desarrolladores a detectar y corregir vulnerabilidades SSRF antes de implementar sus aplicaciones en producción.
¿Qué es una vulnerabilidad de SSRF?
La falsificación de solicitudes del lado del servidor (SSRF) es una vulnerabilidad de seguridad crítica que permite a un atacante enviar solicitudes desde una aplicación del lado del servidor a destinos no previstos. Esto puede dar lugar al acceso no autorizado a servicios internos, la exposición de datos confidenciales e incluso la ejecución remota de código. La SSRF explota la confianza que los servidores depositan en las comunicaciones de la red interna, lo que permite a los atacantes manipular URL y redirigir solicitudes a endpoints maliciosos.
Vectores de ataque e impactos comunes de SSRF
Las vulnerabilidades SSRF suelen surgir cuando una aplicación acepta entradas del usuario para crear URL destinadas a solicitudes del lado del servidor, sin validarlas ni depurarlas adecuadamente. Los atacantes aprovechan esto al crear entradas maliciosas que redirigen la solicitud del servidor a un destino no previsto, como:
Servicios internos (por ejemplo, servicios de metadatos en entornos de nube)
Interfaces de localhost
Otros recursos de la red interna
Si los atacantes explotan una vulnerabilidad SSRF en aplicaciones de Go, el impacto puede ser grave e incluir lo siguiente:
Exfiltración de datos: acceso a datos confidenciales de servicios internos, como otros microservicios a los que puede acceder la aplicación de Go vulnerable.
Interrupción del servicio: envío de solicitudes que sobrecargan o interrumpen los servicios internos.
Una aplicación vulnerable de Go
La siguiente aplicación de Go es un servidor HTTP sencillo creado con el framework web Gin. Expone un endpoint POST /cloudpawnery/image que permite a los usuarios descargar y cambiar el tamaño de un archivo de imagen. A continuación, se describen sus componentes principales:
Configuración y estructuras de Go:
Con el siguiente código de Go en nuestro programa web, definiremos algunas configuraciones iniciales para la funcionalidad de recuperación remota de archivos y el esquema de datos esperado en las solicitudes entrantes a la ruta de la API:
const baseHost = "localtest.me:8080"
type FileInfo struct {
Filename string `json:"filename"`
Download string `json:"download"`
}En el fragmento de código anterior:
baseHost es una constante que define el dominio base utilizado para construir la URL de descarga de archivos.
FileInfo es una estructura que representa la estructura JSON esperada del servidor de almacenamiento de archivos.
Una función para descargar recursos por HTTP y cambiarles el tamaño
La función downloadAndResize recibe tenantID, fileID y fileSize como parámetros. Construye una URL para descargar un archivo JSON con información sobre el archivo que se descargará y cuyo tamaño se cambiará.
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 función realiza los siguientes pasos:
Construye la URL usando
tenantIDyfileID.Realiza una solicitud HTTP GET para obtener los datos JSON.
Analiza el JSON para extraer la URL de descarga y el nombre del archivo.
Descarga el archivo y lo guarda localmente.
Cambia el tamaño de la imagen con el comando
convert.
La función principal de Go
En el núcleo del programa de Go, creamos una instancia del servidor web, definimos una ruta de endpoint POST e invocamos la función 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")
}El endpoint POST se define con la ruta HTTP /cloudpawnery/image, que extrae tenantID, fileID y fileSize de los parámetros de consulta. También realiza las siguientes tareas:
- Valida que estén presentes tenantID y fileID.
- Llama a downloadAndResize para procesar la imagen.
- Devuelve una respuesta JSON que indica si la operación se realizó correctamente o no.
Cómo identificar la vulnerabilidad SSRF en el código
La vulnerabilidad SSRF de esta aplicación de Go se debe a la forma en que se usa tenantID para construir la URL de la solicitud HTTP.
Analicemos el problema en detalle para entender esta vulnerabilidad de seguridad:
Entrada controlada por el usuario:
tenantIDse extrae directamente de la cadena de consulta de la solicitud HTTP, sin validación ni depuración. Esto permite que un atacante manipuletenantIDpara que apunte a cualquier dominio.
Construcción de la URL: la función downloadAndResize construye una URL usando tenantID y fileID. ¿Es esta concatenación de cadenas una forma segura de construir una URL? ¿Qué podría salir mal?
urlStr := fmt.Sprintf("http://%s.%s/storage/%s.json", tenantID, baseHost, fileID)Si un atacante proporciona un tenantID malicioso, puede controlar todo el subdominio y posiblemente redirigir la solicitud a un servidor malicioso.
Solicitud HTTP: la aplicación realiza una solicitud HTTP GET a la URL construida:
resp, err := http.Get(urlStr)Esta solicitud puede redirigirse a un servidor controlado por un atacante, lo que le permite acceder a servicios internos o realizar otras acciones maliciosas.
El código vulnerable de Go que provoca SSRF
En la aplicación de Go proporcionada, tenantID se extrae de la cadena de consulta de una solicitud HTTP y se usa directamente para construir una URL. Luego, esta URL se usa en una solicitud GET mediante la función http.Get, sin validación ni depuración.
Esta práctica abre la puerta a una vulnerabilidad de falsificación de solicitudes del lado del servidor (SSRF). Al manipular tenantID, un atacante puede crear una URL que apunte a un host no previsto y, posiblemente, acceder a datos o servicios internos confidenciales.
Cómo explotar la vulnerabilidad SSRF con un tenantID malicioso
Un atacante puede explotar esta vulnerabilidad proporcionando un tenantID malicioso para redirigir la solicitud a un servidor no previsto.
Por ejemplo, si el atacante envía una solicitud con un tenantID como e:@evil.com/url=, la URL construida es http://1234:e@evil.com/url=.localtest.me:8080/storage/fileID.json. Ahora, esta URL obtendría información JSON del sitio web evil.com en lugar del microservicio local, y el atacante podría controlar la configuración del archivo de almacenamiento.
Cómo usar Snyk Code para detectar SSRF y analizar la seguridad
¿Te preguntas cómo encontré esta vulnerabilidad SSRF oculta en mi código de Go?
Tengo instalada la extensión de Snyk en mi IDE de VS Code. Al guardar el código, aparece una línea ondulada roja y Snyk detecta que introduje una línea de código insegura que podría provocar una vulnerabilidad de seguridad.
Así es como Snyk encuentra y alerta sobre vulnerabilidades de falsificación de solicitudes del lado del servidor en tiempo real:

Como puedes ver, Snyk no solo encuentra las vulnerabilidades de seguridad, sino que también ofrece un flujo detallado de llamadas en la ruta del código que muestra cómo la entrada controlada por el usuario llega a la función de destino en forma de una llamada HTTP.
Snyk va un paso más allá y también me muestra varias soluciones sugeridas para mitigar el problema de seguridad, basadas en cambios de código de otros proyectos de código abierto.
Para obtener la misma experiencia y proteger también tu código, instala la extensión de Snyk o simplemente conecta tu repositorio de código de GitHub, GitLab o Bitbucket a la aplicación de Snyk. Esta lo supervisará continuamente para ofrecerte estas detecciones y correcciones.
Como complemento de esta información sobre seguridad, recomiendo a los desarrolladores de Go que lean las prácticas recomendadas de seguridad para Go.
Cierra la brecha entre seguridad y desarrollo
Descubre los seis pilares del éxito de DevSecOps y cómo aplicarlos en tu organización.