Frame Buster Buster ... se necesita el código buster

422

Supongamos que no desea que otros sitios "enmarquen" su sitio en un <iframe>:

<iframe src="http://example.org"></iframe>

Entonces inserta JavaScript anti-framing, frame busting en todas sus páginas:

/* break us out of any containing iframes */
if (top != self) { top.location.replace(self.location.href); }

¡Excelente! Ahora se "rompe" o se libera automáticamente de cualquier iframe que lo contenga. Excepto por un pequeño problema.

Como resultado, su código de ruptura de trama puede romperse , como se muestra aquí :

<script type="text/javascript">
    var prevent_bust = 0  
    window.onbeforeunload = function() { prevent_bust++ }  
    setInterval(function() {  
      if (prevent_bust > 0) {  
        prevent_bust -= 2  
        window.top.location = 'http://example.org/page-which-responds-with-204'  
      }  
    }, 1)  
</script>

Este código hace lo siguiente:

  • incrementa un contador cada vez que el navegador intenta navegar fuera de la página actual, a través del window.onbeforeunloadcontrolador de eventos
  • configura un temporizador que dispara cada milisegundo a través de setInterval(), y si ve que el contador se incrementa, cambia la ubicación actual a un servidor de control del atacante
  • ese servidor sirve una página con el código de estado HTTP 204 , que no hace que el navegador navegue a ningún lado

Mi pregunta es, y esto es más un rompecabezas de JavaScript que un problema real , ¿cómo puede derrotar al destructor de marcos?

Tuve algunos pensamientos, pero nada funcionó en mis pruebas:

  • intentar borrar el onbeforeunloadevento onbeforeunload = nullno tuvo efecto
  • al agregar y alert()detener el proceso, el usuario sabe que estaba sucediendo, pero no interfiere con el código de ninguna manera; Al hacer clic en Aceptar, el revés continúa de manera normal
  • No se me ocurre ninguna forma de borrar el setInterval()temporizador

No soy un gran programador de JavaScript, así que este es mi desafío para ti: hey buster, ¿puedes reventar el buster buster?

Jeff Atwood
fuente
66
No estoy seguro de que el frame-buster-buster realmente funcione ... cuando trato de probarlo (redirigiendo a un controlador que configuré para devolver un 204), me impide navegar a cualquier lugar fuera de la página, incluido escribir cosas en la barra de direcciones! Tengo que cerrar la pestaña del navegador y abrir una nueva para llegar a cualquier parte. En otras palabras, no estoy seguro de que esto necesite una solución, porque el frame-buster-buster que quiere ser arrestado es ... para empezar, roto. :) (O eso o arruiné mi prueba, lo que nunca podría suceder ...);)
Matt Winckler
16
Matt, el código frame-buster-buster publicado anteriormente definitivamente funciona. Un ... amigo ... mío ... me contó ... al respecto. O algo. :)
Jeff Atwood
10
Jeff, ¿estás probando con ambas ventanas en el mismo dominio? Parece que lo eres porque si no lo estuvieras, las restricciones de seguridad te impedirían modificar 'onBeforeUnload'
James
29
En una nota al margen: Al publicar ejemplos, utilice dominios example.orgcomo se especifica en RFC 2606 ietf.org/rfc/rfc2606.txt
Christoph
3
Con respecto al tema general de contra-contra-contramedidas: galactanet.com/comic/view.php?strip=209
Joey el

Respuestas:

149

No estoy seguro de si esto es viable o no, pero si no puede romper el marco, ¿por qué no simplemente mostrar una advertencia? Por ejemplo, si su página no es la "página superior", cree un método setInterval que intente romper el marco. Si después de 3 o 4 intentos su página aún no es la página superior, cree un elemento div que cubra toda la página (cuadro modal) con un mensaje y un enlace como ...

Estás viendo esta página en una ventana de marco no autorizada - (Bla, bla ... posible problema de seguridad)

haga clic en este enlace para solucionar este problema

No es el mejor, pero no veo ninguna forma en que puedan salir de eso.

Hugoware
fuente
2
He intentado esto y esto funciona. Otra pieza que me gusta de esta solución es que saca a la luz al usuario en qué tipo de sitio estaba antes de acceder a su contenido. Código de muestra: if (parent.frames.length> 0) {top.location.replace (document.location); setTimeout (function () {if (parent.frames.length> 0) {document.location = " google.com ";}}, 10); }
papa
Esta no solo es una buena manera de evitar el abuso, sino que es bastante amigable para los sitios que quieran enmarcar su sitio solo para echarle un vistazo, aunque no para permitir su uso. Idealmente, creo que debería usarse una captura de pantalla de la página de inicio del sitio, con alguna explicación de por qué no se puede usar en el iframe superpuesto en la parte superior.
wheresrhys
33
Así es como lo hace Facebook.
shamittomar
2
pero tal vez esto podría explotarse si el sitio que revienta a su vez creará un falso anti anti anti ... (no sé cuánto anti estamos hasta ahora) lighbox div por sí mismo presentando un enlace de phishing o lo que sea ... tbc
yunzen
77
Otra idea sería simplemente borrar la página por completo con algo como document.write("");(después de haber establecido que está siendo enmarcado
gabeio
211

Fwiw, la mayoría de los navegadores actuales apoyan los X-Frame-Options: negar directiva, que funciona incluso cuando la escritura se desactiva.

IE8:
http://blogs.msdn.com/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx

Firefox (3.6.9)
https://bugzilla.mozilla.org/show_bug.cgi?id=475530
https://developer.mozilla.org/en/The_X-FRAME-OPTIONS_response_header

Chrome / Webkit
http://blog.chromium.org/2010/01/security-in-depth-new-security-features.html
http://trac.webkit.org/changeset/42333

EricLaw
fuente
66
excelente, apoyar esto en el navegador .exe es el camino a seguir sin lugar a dudas. Cuando dices "la mayoría de los navegadores", ¿cuáles específicamente? No puedo encontrar buenas fuentes para nada más que IE8.
Jeff Atwood
2
Aquí hay una página de prueba: enhaie.com/test/clickjack . Chrome 4.1.249.1042 soporta. Opera 10.50 es compatible. Firefox 3.6.2 aún no es compatible. Safari 4.0.3 soporta.
EricLaw
1
Firefox 3.6.9 lo admitirá de forma nativa ( hackademix.net/2010/08/31/… ) y cualquier instalación de Firefox con NoScript la ha tenido desde principios de 2009 ( hackademix.net/2009/01/29/x-frame- opciones-en-firefox )
ssokolow
44
Es mejor combinar esto con el framebuster javascript.
Jesse Weigert
1
O combine esto con la respuesta de diciembre '12 en esta página: stackoverflow.com/a/13708510/328397
goodguys_activate
34

Hemos utilizado el siguiente enfoque en uno de nuestros sitios web desde http://seclab.stanford.edu/websec/framebusting/framebust.pdf

<style>
 body { 
 display : none   
}
</style>
<script>
if(self == top) {
document.getElementsByTagName("body")[0].style.display = 'block';
}
else{
top.location = self.location;
}
</script>
Cazador de mazmorras
fuente
3
¡Bingo! No estoy seguro de por qué esto no se votó más, ya que es la mejor respuesta (junto a la respuesta X-Frame-Options, pero es mejor combinar ambas)
Jesse Weigert
1
Esta respuesta o la mía debe seleccionarse :)
Me gusta su idea de usar tanto esto como las opciones de X-Frame: negar respuesta.
Max West
Esto requiere JS, que es una carga bastante grande en mi humilde opinión.
Navin
1
+1: esta es la mejor respuesta cuando X-FRAME-OPTIONS no se puede usar. (Por ejemplo, cuando necesita permitir o denegar condicionalmente dependiendo del referente).
Jay Sullivan
29

Se le ocurrió esto, y parece funcionar al menos en Firefox y el navegador Opera.

if(top != self) {
 top.onbeforeunload = function() {};
 top.location.replace(self.location.href);
}
Jani Hartikainen
fuente
2
la solución de Jani y Jeff (una vez editada) es correcta y funciona de manera equivalente; dando a Jani la aceptación porque su solución funcionó correctamente sin ninguna edición
Jeff Atwood
29
Esto solo funcionará si las dos ventanas son del mismo dominio; una ocurrencia rara cuando quieres escapar de un cuadro.
James
Si hay marcos anidados involucrados, tendrás que recorrer la cadena de marcos y eliminar todos los onbeforeunloadcontroladores, ¡no solo el que está arriba!
Christoph
12
aclaración importante: esto funcionó para mí porque el iframe src = se estaba configurando dinámicamente y, por lo tanto, la política de dominio cruzado NO estaba en vigor. JP tiene toda la razón, en un estático src = esto no funcionaría.
Jeff Atwood
55
ok ahora, ¿alguien puede tener un frame buster buster buster buster?
Epaga
23

Teniendo en cuenta el estándar HTML5 actual que introdujo el sandbox para el iframe, todos los códigos de eliminación de fotogramas que se proporcionan en esta página se pueden deshabilitar cuando el atacante usa el sandbox porque restringe el iframe de lo siguiente:

allow-forms: Allow form submissions.
allow-popups: Allow opening popup windows.
allow-pointer-lock: Allow access to pointer movement and pointer lock.
allow-same-origin: Allow access to DOM objects when the iframe loaded form same origin
allow-scripts: Allow executing scripts inside iframe
allow-top-navigation: Allow navigation to top level window

Consulte: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#attr-iframe-sandbox

Ahora, considere que el atacante usó el siguiente código para alojar su sitio en iframe:

<iframe src="URI" sandbox></iframe>

Entonces, todo el código de ruptura de trama de JavaScript fallará.

Después de verificar todo el código de bus de trama, solo esta defensa funciona en todos los casos:

<style id="antiClickjack">body{display:none !important;}</style>
<script type="text/javascript">
   if (self === top) {
       var antiClickjack = document.getElementById("antiClickjack");
       antiClickjack.parentNode.removeChild(antiClickjack);
   } else {
       top.location = self.location;
   }
</script>

propuesta originalmente por Gustav Rydstedt, Elie Bursztein, Dan Boneh y Collin Jackson (2010)


fuente
19

Después de reflexionar sobre esto por un momento, creo que esto les mostrará quién es el jefe ...

if(top != self) {
  window.open(location.href, '_top');
}

Usar _topcomo parámetro de destino window.open()lo iniciará en la misma ventana.

Josh Stodola
fuente
6
if (top != self) {
  top.location.replace(location);
  location.replace("about:blank"); // want me framed? no way!
}

fuente
6

Voy a ser valiente y arrojar mi sombrero en el ring en este (antiguo como es), ver cuántos votos negativos puedo recoger.

Aquí está mi intento, que parece funcionar en todas partes donde lo he probado (Chrome20, IE8 y FF14):

(function() {
    if (top == self) {
        return;
    }

    setInterval(function() {
        top.location.replace(document.location);
        setTimeout(function() {
            var xhr = new XMLHttpRequest();
            xhr.open(
                'get',
                'http://mysite.tld/page-that-takes-a-while-to-load',
                false
            );
            xhr.send(null);
        }, 0);
    }, 1);
}());

Puse este código en <head>y lo llamé desde el final <body>para asegurarme de que mi página se muestre antes de que comience a discutir con el código malicioso, no sé si este es el mejor enfoque, YMMV.

¿Como funciona?

... te escucho preguntar, bueno, la respuesta sincera es que realmente no lo sé. Me costó mucho trabajo hacer que funcionara en todos los lugares que estaba probando, y el efecto exacto que tiene varía ligeramente dependiendo de dónde lo ejecute.

Aquí está el pensamiento detrás de esto:

  • Establezca una función para ejecutarse en el intervalo más bajo posible. El concepto básico detrás de cualquiera de las soluciones realistas que he visto es llenar el programador con más eventos que el que tiene el buster-buster.
  • Cada vez que se active la función, intente y cambie la ubicación del marco superior. Requisito bastante obvio.
  • También programe una función para que se ejecute inmediatamente, lo que tomará mucho tiempo en completarse (lo que impide que el frame buster-buster interfiera con el cambio de ubicación). Elegí una XMLHttpRequest síncrona porque es el único mecanismo que se me ocurre que no requiere (o al menos solicita) la interacción del usuario y no reduce el tiempo de CPU del usuario.

Para mi http://mysite.tld/page-that-takes-a-while-to-load(el objetivo del XHR) utilicé un script PHP que se ve así:

<?php sleep(5);

¿Lo que pasa?

  • Chrome y Firefox esperan los 5 segundos mientras se completa el XHR, luego redirigen con éxito a la URL de la página enmarcada.
  • IE redirige casi de inmediato

¿No puedes evitar el tiempo de espera en Chrome y Firefox?

Aparentemente no. Al principio apunté el XHR a una URL que devolvería un 404; esto no funcionó en Firefox. Luego probé el sleep(5);enfoque que finalmente obtuve para esta respuesta, luego comencé a jugar con la duración del sueño de varias maneras. No pude encontrar un patrón real para el comportamiento, pero sí encontré que si es demasiado corto, específicamente Firefox no jugará a la pelota (Chrome e IE parecen comportarse bastante bien). No sé cuál es la definición de "demasiado corto" en términos reales, pero 5 segundos parecen funcionar cada vez.


Si algún ninjas de Javascript que pasa quiere explicar un poco mejor lo que está sucediendo, por qué esto (probablemente) es incorrecto, poco confiable, el peor código que han visto, etc. Estaré encantado de escucharlo.

DaveRandom
fuente
Parece que puedes eliminar todas tus frases preocupantes
mplungjan
6

A partir de 2015, debe usar la frame-ancestorsdirectiva CSP2 para esto. Esto se implementa a través de un encabezado de respuesta HTTP.

p.ej

Content-Security-Policy: frame-ancestors 'none'

Por supuesto, no muchos navegadores admiten CSP2 todavía, por lo que es aconsejable incluir el X-Frame-Optionsencabezado anterior:

X-Frame-Options: DENY

Aconsejaría incluir ambos de todos modos, de lo contrario su sitio continuaría siendo vulnerable a los ataques de Clickjacking en navegadores antiguos, y por supuesto obtendría un marco indeseable incluso sin intenciones maliciosas. La mayoría de los navegadores se actualizan automáticamente en estos días, sin embargo, todavía tiende a hacer que los usuarios corporativos se atasquen en versiones antiguas de Internet Explorer por razones de compatibilidad de aplicaciones heredadas.

SilverlightFox
fuente
1
Todos los principales navegadores ahora admiten CSP. Esta es la respuesta correcta en 2019 y el futuro previsible.
Stephen R
5

Ok, entonces sabemos que estaban en un marco. Entonces ubicamos.href a otra página especial con la ruta como una variable GET. Ahora explicamos al usuario lo que está sucediendo y proporcionamos un enlace con una opción target = "_ TOP". Es simple y probablemente funcionaría (no lo he probado), pero requiere cierta interacción del usuario. Tal vez podría señalar el sitio ofensivo para el usuario y hacer una vergüenza de clickers a su sitio en alguna parte ... Solo una idea, pero funciona de noche ...


fuente
5

Todas las soluciones propuestas fuerzan directamente un cambio en la ubicación de la ventana superior. ¿Qué pasa si un usuario quiere que el marco esté allí? Por ejemplo, el cuadro superior en los resultados de imagen de los motores de búsqueda.

Escribí un prototipo donde, por defecto, todas las entradas (enlaces, formularios y elementos de entrada) están deshabilitadas y / o no hacen nada cuando se activan.

Si se detecta un marco contenedor, las entradas quedan deshabilitadas y se muestra un mensaje de advertencia en la parte superior de la página. El mensaje de advertencia contiene un enlace que abrirá una versión segura de la página en una nueva ventana. Esto evita que la página se use para hacer clickjacking, al tiempo que permite al usuario ver el contenido en otras situaciones.

Si no se detecta un marco que contenga, las entradas están habilitadas.

Aquí está el código. Debe establecer los atributos HTML estándar en valores seguros y agregar atributos adicionales que contengan los valores reales. Probablemente esté incompleto y para mayor seguridad, los atributos adicionales (estoy pensando en los controladores de eventos) probablemente tendrán que ser tratados de la misma manera:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
  <head>
    <title></title>
    <script><!--
      function replaceAttributeValuesWithActualOnes( array, attributeName, actualValueAttributeName, additionalProcessor ) {
        for ( var elementIndex = 0; elementIndex < array.length; elementIndex += 1 ) {
          var element = array[ elementIndex ];
          var actualValue = element.getAttribute( actualValueAttributeName );
          if ( actualValue != null ) {
            element[ attributeName ] = actualValue;
          }

          if ( additionalProcessor != null ) {
            additionalProcessor( element );
          }
        }
      }

      function detectFraming() {
        if ( top != self ) {
          document.getElementById( "framingWarning" ).style.display = "block";
        } else {
          replaceAttributeValuesWithActualOnes( document.links, "href", "acme:href" );

          replaceAttributeValuesWithActualOnes( document.forms, "action", "acme:action", function ( form ) {
            replaceAttributeValuesWithActualOnes( form.elements, "disabled", "acme:disabled" );
          });
        }
      }
      // -->
    </script>
  </head>
  <body onload="detectFraming()">
    <div id="framingWarning" style="display: none; border-style: solid; border-width: 4px; border-color: #F00; padding: 6px; background-color: #FFF; color: #F00;">
      <div>
        <b>SECURITY WARNING</b>: Acme App is displayed inside another page.
        To make sure your data is safe this page has been disabled.<br>
        <a href="framing-detection.html" target="_blank" style="color: #090">Continue working safely in a new tab/window</a>
      </div>
    </div>
    <p>
      Content. <a href="#" acme:href="javascript:window.alert( 'Action performed' );">Do something</a>
    </p>
    <form name="acmeForm" action="#" acme:action="real-action.html">
      <p>Name: <input type="text" name="name" value="" disabled="disabled" acme:disabled=""></p>
      <p><input type="submit" name="save" value="Save" disabled="disabled" acme:disabled=""></p>
    </form>
  </body>
</html>
Johan Stuyts
fuente
El problema con esto es que el creador de marcos podría usar la posición: absoluto para colocar el botón activo encima de los botones inactivos y el usuario solo verá su página web y pensará que está haciendo clic en SUS botones.
jmucchiello
El mensaje de advertencia se seguiría mostrando, pero, por supuesto, es fácil cubrir el enlace a la página segura como sugiere. Pero, ¿por qué pasar por la molestia de enmarcar mi página para que la gente haga clic en un botón familiar si simplemente puede copiar la página y lograr el mismo efecto? El código anterior evita principalmente el clickjacking. Si muestra mi página de forma invisible encima de otra página, no es posible invocar acciones en mi sitio.
Johan Stuyts
Si esto se coloca en un marco de zona restringida de IE8 o un marco de sandbox de Chrome, el Javascript nunca se ejecutará. Me pregunto qué modificaciones son necesarias en esos casos
goodguys_activate
4

Bueno, puede modificar el valor del contador, pero obviamente es una solución frágil. Puede cargar su contenido a través de AJAX después de haber determinado que el sitio no está dentro de un marco, tampoco es una gran solución, pero con suerte evita que se active el evento antes de la descarga (supongo).

Editar: otra idea. Si detecta que está en un marco, pídale al usuario que desactive JavaScript, antes de hacer clic en un enlace que lo lleve a la URL deseada (pasando una cadena de consulta que le permita a su página decirle al usuario que puede volver a habilitar JavaScript una vez que hay).

Edición 2: Vuélvase nuclear: si detecta que está en un marco, simplemente elimine el contenido del cuerpo del documento e imprima un mensaje desagradable.

Edición 3: ¿Puede enumerar el documento superior y establecer todas las funciones en nulas (incluso las anónimas)?

RedFilter
fuente
Outlook (anteriormente Hotmail) 'se vuelve nuclear' si no puede salir de un marco: pone todo el contenido del <body>interior en una <plaintext>etiqueta configurada display: none. Es bastante efectivo
uınbɐɥs
4

Si agrega una alerta justo después del código buster, entonces la alerta detendrá el hilo de JavaScript y permitirá que se cargue la página. Esto es lo que hace StackOverflow, y sale de mis iframes, incluso cuando uso el buster buster buster. También funcionó con mi página de prueba simple. Esto solo se ha probado en Firefox 3.5 e IE7 en Windows.

Código:

<script type="text/javascript">
if (top != self){
  top.location.replace(self.location.href);
  alert("for security reasons bla bla bla");
}
</script>
Mario
fuente
3

Creo que ya casi estabas allí. Has probado:

window.parent.onbeforeunload = null;
window.parent.location.replace(self.location.href);

o alternativamente:

window.parent.prevent_bust = 0;

Nota: en realidad no probé esto.

Jeff Meatball Yang
fuente
1
Edité su muestra de código (la prueba para padres parece fallar) pero la versión editada ¡parece funcionar!
Jeff Atwood
1
Frio. Siempre es complicado responder con código no probado, lo hago para al menos transmitir la idea, y dejar que el pobre que hace la pregunta lo depure. :)
Jeff Meatball Yang
12
No funcionará si el padre está en un dominio diferente, ¡lo cual es probable!
Josh Stodola
2

¿Qué hay de llamar al buster repetidamente también? Esto creará una condición de carrera, pero uno puede esperar que el destructor salga ganando:

(function() {
    if(top !== self) {
        top.location.href = self.location.href;
        setTimeout(arguments.callee, 0);
    }
})();
Christoph
fuente
2

Si observa los valores devueltos por setInterval()ellos, generalmente son de un solo dígito, por lo que generalmente puede deshabilitar todas esas interrupciones con una sola línea de código:

for (var j = 0 ; j < 256 ; ++j) clearInterval(j)
Robin Nixon
fuente
2

Es posible que haya conseguido una forma de reventar el frame buster buster javascript. Usando getElementsByName en mi función javascript, establecí un bucle entre el frame buster y el script real del buster buster. mira esta publicación http://www.phcityonweb.com/frame-buster-buster-buster-2426

Phcityonweb
fuente
0

setInterval y setTimeout crean un intervalo de incremento automático. Cada vez que se llama a setTimeout o setInterval, este número aumenta en uno, de modo que si llama a setTimeout, obtendrá el valor más alto actual.

   var currentInterval = 10000;
   currentInterval += setTimeout( gotoHREF, 100 );
   for( var i = 0; i < currentInterval; i++ ) top.clearInterval( i );
   // Include setTimeout to avoid recursive functions.
   for( i = 0; i < currentInterval; i++ )     top.clearTimeout( i );

   function gotoHREF(){
           top.location.href = "http://your.url.here";
   }

Dado que es casi inaudito que haya 10000 setIntervals y setTimeouts simultáneos funcionando, y que setTimeout devuelve "último intervalo o tiempo de espera creado + 1", y dado que top.clearInterval sigue siendo accesible, esto derrotará los ataques de sombrero negro para enmarcar sitios web que se describen anteriormente.

cwallenpoole
fuente
0

Use htaccess para evitar el conjunto de marcos de jacking alto, iframe y cualquier contenido como imágenes.

RewriteEngine on
RewriteCond %{HTTP_REFERER} !^http://www\.yoursite\.com/ [NC]
RewriteCond %{HTTP_REFERER} !^$
RewriteRule ^(.*)$ /copyrights.html [L]

Esto mostrará una página de derechos de autor en lugar de la esperada.

BF
fuente
Esto se basa en el referente, que a) no siempre se establece (debido a la configuración o extensiones del navegador o simplemente porque la página de referencia está usando HTTPS sin usar <meta name="referrer" …/>yb) también se establece al hacer clic en los enlaces, por lo que también no permite enlaces a su página y se rompe La web.
Martin