In this article
Como evitar vulnerabilidades SSRF em aplicações Go
Sem dúvida, Go continua ganhando popularidade na criação de aplicações web escaláveis e eficientes. Por isso, é essencial lidar com vulnerabilidades de segurança como SSRF. O modelo de concorrência e o desempenho de Go fazem dessa linguagem uma excelente opção para aplicações do lado do servidor, mas os desenvolvedores precisam estar sempre atentos às possíveis ameaças à segurança.
Neste artigo, vamos aprender como as vulnerabilidades SSRF se manifestam em aplicações Go e como os desenvolvedores podem implementar medidas de segurança eficazes para proteger suas aplicações e seus dados.
Vamos analisar o código passo a passo para identificar a vulnerabilidade SSRF e discutir os riscos de segurança associados a ela. Além disso, vamos apresentar ferramentas como Snyk Code, que ajudam os desenvolvedores a detectar e corrigir vulnerabilidades SSRF antes de implantar suas aplicações em produção.
O que é uma vulnerabilidade de SSRF?
A falsificação de solicitação do lado do servidor (SSRF) é uma vulnerabilidade crítica de segurança que permite que um invasor faça solicitações de um aplicativo no lado do servidor para destinos não previstos. Isso pode resultar em acesso não autorizado a serviços internos, exposição de dados confidenciais e até execução remota de código. Os ataques de SSRF exploram a confiança dos servidores nas comunicações da rede interna, permitindo que invasores manipulem URLs e redirecionem solicitações para endpoints maliciosos.
Vetores de ataque e impactos comuns de SSRF
As vulnerabilidades SSRF costumam surgir quando uma aplicação aceita dados fornecidos pelo usuário para construir URLs de solicitações feitas pelo servidor, sem validação ou sanitização adequadas. Os invasores exploram essa falha criando entradas maliciosas que redirecionam a solicitação do servidor para um destino não previsto, como:
Serviços internos (por exemplo, serviços de metadados em ambientes de nuvem)
Interfaces localhost
Outros recursos da rede interna
Se explorada por invasores em aplicações Go, uma vulnerabilidade SSRF pode ter consequências graves, incluindo:
Exfiltração de dados: acesso a dados confidenciais de serviços internos, como outros microsserviços acessíveis pela aplicação Go vulnerável.
Interrupção de serviços: envio de solicitações que sobrecarregam ou interrompem serviços internos.
Uma aplicação Go vulnerável
A aplicação Go a seguir é um servidor HTTP simples criado com o framework web Gin. Ela disponibiliza um endpoint POST /cloudpawnery/image que permite aos usuários baixar e redimensionar um arquivo de imagem. Veja uma análise dos principais componentes:
Configuração e structs Go:
Com o código Go a seguir em nosso programa web, vamos definir algumas configurações iniciais para buscar arquivos remotamente e especificar o esquema de dados esperado nas solicitações recebidas pela rota da API:
const baseHost = "localtest.me:8080"
type FileInfo struct {
Filename string `json:"filename"`
Download string `json:"download"`
}No trecho de código acima:
baseHost é uma constante que define o domínio base usado para construir a URL de download dos arquivos.
FileInfo é uma struct que representa a estrutura JSON esperada do servidor de armazenamento de arquivos.
Uma função para baixar arquivos por HTTP e redimensionar recursos
A função downloadAndResize recebe tenantID, fileID e fileSize como parâmetros. Ela constrói uma URL para baixar um arquivo JSON com informações sobre o arquivo que será baixado e redimensionado.
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
}A função executa as seguintes etapas:
Constrói a URL usando
tenantIDefileID.Faz uma solicitação HTTP GET para buscar os dados JSON.
Analisa o JSON para extrair a URL de download e o nome do arquivo.
Baixa o arquivo e o salva localmente.
Redimensiona a imagem usando o comando
convert.
Nossa função principal em Go
É no núcleo do programa Go que instanciamos um servidor web, definimos uma rota de endpoint POST e chamamos a função 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")
}O endpoint POST é definido pela rota HTTP /cloudpawnery/image, que extrai tenantID, fileID e fileSize dos parâmetros da consulta. Ele também executa as seguintes tarefas:
- Verifica se tenantID e fileID estão presentes.
- Chama downloadAndResize para processar a imagem.
- Retorna uma resposta JSON indicando sucesso ou falha.
Identificando a vulnerabilidade SSRF no código
A vulnerabilidade SSRF nesta aplicação Go decorre da maneira como tenantID é usado para construir a URL da solicitação HTTP.
Vamos analisar o caso em detalhes para entender a vulnerabilidade de segurança:
Entrada controlada pelo usuário:
tenantIDé extraído diretamente da string de consulta da solicitação HTTP, sem validação ou sanitização. Isso permite que um invasor manipuletenantIDpara apontar para qualquer domínio.
Construção da URL: a função downloadAndResize constrói uma URL usando tenantID e fileID. É uma boa oportunidade para se perguntar: essa concatenação de strings é uma forma segura de construir uma URL? O que poderia dar errado?
urlStr := fmt.Sprintf("http://%s.%s/storage/%s.json", tenantID, baseHost, fileID)Se um invasor fornecer um tenantID malicioso, poderá controlar todo o subdomínio e potencialmente redirecionar a solicitação para um servidor malicioso.
Solicitação HTTP: a aplicação faz uma solicitação HTTP GET para a URL construída:
resp, err := http.Get(urlStr)Essa solicitação pode ser redirecionada para um servidor controlado pelo invasor, permitindo que ele acesse serviços internos ou realize outras ações maliciosas.
O código Go vulnerável que causa SSRF
Na aplicação Go apresentada, tenantID é extraído da string de consulta de uma solicitação HTTP e usado diretamente para construir uma URL. Em seguida, essa URL é usada em uma solicitação GET com a função http.Get, sem validação ou sanitização.
Essa prática abre caminho para uma vulnerabilidade de falsificação de solicitação do lado do servidor (SSRF). Ao manipular tenantID, um invasor pode criar uma URL que aponta para um host não previsto, com potencial para acessar dados ou serviços internos confidenciais.
Explorando a vulnerabilidade SSRF com um tenantID malicioso
Um invasor pode explorar essa vulnerabilidade fornecendo um tenantID malicioso para redirecionar a solicitação a um servidor não previsto.
Por exemplo, se o invasor enviar uma solicitação com um tenantID como e:@evil.com/url=, a URL construída será http://1234:e@evil.com/url=.localtest.me:8080/storage/fileID.json. Essa URL buscará informações JSON no site evil.com, em vez de no microsserviço local, e o invasor poderá controlar a configuração do arquivo de armazenamento.
Usando Snyk Code para detectar SSRF e analisar a segurança
Você deve estar se perguntando: como encontrei essa vulnerabilidade SSRF escondida no meu código Go?
Tenho a extensão Snyk instalada no VS Code, meu IDE. Ao salvar o código, ela adiciona um sublinhado vermelho ondulado, e a Snyk detecta que introduzi uma linha de código insegura que pode causar uma vulnerabilidade de segurança.
Veja como a Snyk encontra e alerta sobre vulnerabilidades de falsificação de solicitação do lado do servidor em tempo real:

Como você pode ver, a Snyk não apenas encontra vulnerabilidades de segurança, mas também mostra em detalhes o fluxo do caminho no código, desde a entrada controlada pelo usuário até a função de destino, na forma de uma chamada HTTP.
A Snyk vai além e também mostra várias correções sugeridas para mitigar o problema de segurança, com base em commits de outros projetos de código aberto.
Para ter a mesma experiência e proteger seu código também, instale a extensão Snyk ou conecte seu repositório de código do GitHub, GitLab ou Bitbucket ao app Snyk. Ele fará o monitoramento contínuo para encontrar vulnerabilidades e sugerir correções.
Para continuar aprendendo sobre segurança, recomendo que desenvolvedores Go leiam as práticas recomendadas de segurança em Go.
Aproxime as equipes de segurança e desenvolvimento
Conheça os seis pilares do sucesso em DevSecOps e saiba como aplicá-los à sua organização.