X-Frame-Options Allow-From multiple domains

99

Tengo un sitio ASP.NET 4.0 IIS7.5 que necesito asegurar usando el encabezado X-Frame-Options.

También necesito permitir que las páginas de mi sitio sean iframed desde mi mismo dominio, así como desde mi aplicación de Facebook.

Actualmente tengo mi sitio configurado con un sitio encabezado por:

Response.Headers.Add("X-Frame-Options", "ALLOW-FROM SAMEDOMAIN, www.facebook.com/MyFBSite")

Cuando vi mi página de Facebook con Chrome o Firefox, las páginas de mi sitio (que están enmarcadas con mi página de Facebook) se muestran bien, pero en IE9, aparece el error:

"esta página no se puede mostrar ..." (debido a la X-Frame_Optionsrestricción).

¿Cómo configuro el X-Frame-Options: ALLOW-FROMpara admitir más de un dominio?

X-FRAME-OPTION ser una característica nueva parece fundamentalmente defectuoso si solo se puede definir un dominio.

usuario1340663
fuente
2
Esto parece ser una limitación conocida: owasp.org/index.php/…
Pierre Ernst

Respuestas:

108

X-Frame-Optionses obsoleto. De MDN :

Esta función se ha eliminado de los estándares web. Aunque es posible que algunos navegadores aún lo admitan, está en proceso de descartarse. No lo utilice en proyectos nuevos o antiguos. Las páginas o aplicaciones web que lo utilizan pueden romperse en cualquier momento.

La alternativa moderna es el Content-Security-Policyencabezado, que junto con muchas otras políticas puede incluir en una lista blanca las URL que pueden albergar su página en un marco, utilizando la frame-ancestorsdirectiva.
frame-ancestorsadmite varios dominios e incluso comodines, por ejemplo:

Content-Security-Policy: frame-ancestors 'self' example.com *.example.net ;

Desafortunadamente, por ahora, Internet Explorer no es totalmente compatible con Content-Security-Policy .

ACTUALIZACIÓN: MDN ha eliminado su comentario de obsolescencia. Aquí hay un comentario similar del nivel de política de seguridad de contenido del W3C

La frame-ancestorsdirectiva deja obsoleto el X-Frame-Optionsencabezado. Si un recurso tiene ambas políticas, la frame-ancestorspolítica DEBE aplicarse y la X-Frame-Optionspolítica DEBE ignorarse.

Kobi
fuente
14
frame-ancestors está marcado como "API experimental y no debe usarse en código de producción" en MDN. + X-Frame-Options no está obsoleto, pero es "no estándar", pero "es ampliamente compatible y se puede utilizar junto con CSP"
Jonathan Muller
1
@JonathanMuller - La redacción X-Frame-Optionscambió y ahora es menos severa. Es un buen punto que es arriesgado usar una especificación que no está finalizada. ¡Gracias!
Kobi
2
Ya no puedo encontrar la advertencia desaprobada en MDN. ¿Mozilla ha cambiado de opinión?
thomaskonrad
2
@ to0om - ¡Gracias! Actualicé la respuesta con otro comentario. Puede que haya sido demasiado fuerte en mi respuesta. De cualquier manera, X-Frame-Optionsno admite múltiples fuentes.
Kobi
4
@Kobi, creo que la respuesta debe reorganizarse. La primera oración dice que esto está en desuso según el MDN. Será menos engañoso si agrega su actualización en la parte superior (con una "ACTUALIZACIÓN:" en negrita). Gracias.
Kasun Gajasinghe
39

Desde el RFC 7034 :

No se permiten comodines o listas para declarar varios dominios en una instrucción ALLOW-FROM

Entonces,

¿Cómo configuro X-Frame-Options: ALLOW-FROM para admitir más de un dominio?

No puedes. Como solución alternativa, puede utilizar diferentes URL para diferentes socios. Para cada URL puede usar su propio X-Frame-Optionsvalor. Por ejemplo:

partner   iframe URL       ALLOW-FROM
---------------------------------------
Facebook  fb.yoursite.com  facebook.com
VK.COM    vk.yoursite.com  vk.com

Porque yousite.comsolo puedes usar X-Frame-Options: deny.

Por cierto , por ahora Chrome (y todos los navegadores basados ​​en webkit) no admite ALLOW-FROM declaraciones en absoluto.

vbo
fuente
1
Parece que webkit ahora admite el ALLOW-FROMuso del enlace que proporcionó.
Jimi
3
@Jimi No, no es así: el último comentario sobre el enlace en cuestión dice que debes usar una política de CSP. Esta opción aún no funciona en Chrome.
NickG
9

Nigromante.
Las respuestas proporcionadas están incompletas.

Primero, como ya se dijo, no puede agregar múltiples hosts de permiso, eso no es compatible.
En segundo lugar, debe extraer dinámicamente ese valor de la referencia HTTP, lo que significa que no puede agregar el valor a Web.config, porque no siempre es el mismo valor.

Será necesario realizar una detección del navegador para evitar agregar permisos cuando el navegador sea Chrome (produce un error en la consola de depuración, que puede llenar rápidamente la consola o hacer que la aplicación sea lenta). Eso también significa que debe modificar la detección del navegador ASP.NET, ya que identifica erróneamente a Edge como Chrome.

Esto se puede hacer en ASP.NET escribiendo un módulo HTTP que se ejecuta en cada solicitud, que agrega un encabezado http para cada respuesta, dependiendo de la referencia de la solicitud. Para Chrome, debe agregar Content-Security-Policy.

// /programming/31870789/check-whether-browser-is-chrome-or-edge
public class BrowserInfo
{

    public System.Web.HttpBrowserCapabilities Browser { get; set; }
    public string Name { get; set; }
    public string Version { get; set; }
    public string Platform { get; set; }
    public bool IsMobileDevice { get; set; }
    public string MobileBrand { get; set; }
    public string MobileModel { get; set; }


    public BrowserInfo(System.Web.HttpRequest request)
    {
        if (request.Browser != null)
        {
            if (request.UserAgent.Contains("Edge")
                && request.Browser.Browser != "Edge")
            {
                this.Name = "Edge";
            }
            else
            {
                this.Name = request.Browser.Browser;
                this.Version = request.Browser.MajorVersion.ToString();
            }
            this.Browser = request.Browser;
            this.Platform = request.Browser.Platform;
            this.IsMobileDevice = request.Browser.IsMobileDevice;
            if (IsMobileDevice)
            {
                this.Name = request.Browser.Browser;
            }
        }
    }


}


void context_EndRequest(object sender, System.EventArgs e)
{
    if (System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Response != null)
    {
        System.Web.HttpResponse response = System.Web.HttpContext.Current.Response;

        try
        {
            // response.Headers["P3P"] = "CP=\\\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\\\"":
            // response.Headers.Set("P3P", "CP=\\\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\\\"");
            // response.AddHeader("P3P", "CP=\\\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\\\"");
            response.AppendHeader("P3P", "CP=\\\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\\\"");

            // response.AppendHeader("X-Frame-Options", "DENY");
            // response.AppendHeader("X-Frame-Options", "SAMEORIGIN");
            // response.AppendHeader("X-Frame-Options", "AllowAll");

            if (System.Web.HttpContext.Current.Request.UrlReferrer != null)
            {
                // "X-Frame-Options": "ALLOW-FROM " Not recognized in Chrome 
                string host = System.Web.HttpContext.Current.Request.UrlReferrer.Scheme + System.Uri.SchemeDelimiter
                            + System.Web.HttpContext.Current.Request.UrlReferrer.Authority
                ;

                string selfAuth = System.Web.HttpContext.Current.Request.Url.Authority;
                string refAuth = System.Web.HttpContext.Current.Request.UrlReferrer.Authority;

                // SQL.Log(System.Web.HttpContext.Current.Request.RawUrl, System.Web.HttpContext.Current.Request.UrlReferrer.OriginalString, refAuth);

                if (IsHostAllowed(refAuth))
                {
                    BrowserInfo bi = new BrowserInfo(System.Web.HttpContext.Current.Request);

                    // bi.Name = Firefox
                    // bi.Name = InternetExplorer
                    // bi.Name = Chrome

                    // Chrome wants entire path... 
                    if (!System.StringComparer.OrdinalIgnoreCase.Equals(bi.Name, "Chrome"))
                        response.AppendHeader("X-Frame-Options", "ALLOW-FROM " + host);    

                    // unsafe-eval: invalid JSON https://github.com/keen/keen-js/issues/394
                    // unsafe-inline: styles
                    // data: url(data:image/png:...)

                    // https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet
                    // https://www.ietf.org/rfc/rfc7034.txt
                    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
                    // https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

                    // /programming/10205192/x-frame-options-allow-from-multiple-domains
                    // https://content-security-policy.com/
                    // http://rehansaeed.com/content-security-policy-for-asp-net-mvc/

                    // This is for Chrome:
                    // response.AppendHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: *.msecnd.net vortex.data.microsoft.com " + selfAuth + " " + refAuth);


                    System.Collections.Generic.List<string> ls = new System.Collections.Generic.List<string>();
                    ls.Add("default-src");
                    ls.Add("'self'");
                    ls.Add("'unsafe-inline'");
                    ls.Add("'unsafe-eval'");
                    ls.Add("data:");

                    // http://az416426.vo.msecnd.net/scripts/a/ai.0.js

                    // ls.Add("*.msecnd.net");
                    // ls.Add("vortex.data.microsoft.com");

                    ls.Add(selfAuth);
                    ls.Add(refAuth);

                    string contentSecurityPolicy = string.Join(" ", ls.ToArray());
                    response.AppendHeader("Content-Security-Policy", contentSecurityPolicy);
                }
                else
                {
                    response.AppendHeader("X-Frame-Options", "SAMEORIGIN");
                }

            }
            else
                response.AppendHeader("X-Frame-Options", "SAMEORIGIN");
        }
        catch (System.Exception ex)
        {
            // WTF ? 
            System.Console.WriteLine(ex.Message); // Suppress warning
        }

    } // End if (System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Response != null)

} // End Using context_EndRequest


private static string[] s_allowedHosts = new string[] 
{
     "localhost:49533"
    ,"localhost:52257"
    ,"vmcompany1"
    ,"vmcompany2"
    ,"vmpostalservices"
    ,"example.com"
};


public static bool IsHostAllowed(string host)
{
    return Contains(s_allowedHosts, host);
} // End Function IsHostAllowed 


public static bool Contains(string[] allowed, string current)
{
    for (int i = 0; i < allowed.Length; ++i)
    {
        if (System.StringComparer.OrdinalIgnoreCase.Equals(allowed[i], current))
            return true;
    } // Next i 

    return false;
} // End Function Contains 

Debe registrar la función context_EndRequest en la función de inicio del módulo HTTP.

public class RequestLanguageChanger : System.Web.IHttpModule
{


    void System.Web.IHttpModule.Dispose()
    {
        // throw new NotImplementedException();
    }


    void System.Web.IHttpModule.Init(System.Web.HttpApplication context)
    {
        // /programming/441421/httpmodule-event-execution-order
        context.EndRequest += new System.EventHandler(context_EndRequest);
    }

    // context_EndRequest Code from above comes here


}

A continuación, debe agregar el módulo a su aplicación. Puede hacer esto programáticamente en Global.asax anulando la función Init de HttpApplication, así:

namespace ChangeRequestLanguage
{


    public class Global : System.Web.HttpApplication
    {

        System.Web.IHttpModule mod = new libRequestLanguageChanger.RequestLanguageChanger();

        public override void Init()
        {
            mod.Init(this);
            base.Init();
        }



        protected void Application_Start(object sender, System.EventArgs e)
        {

        }

        protected void Session_Start(object sender, System.EventArgs e)
        {

        }

        protected void Application_BeginRequest(object sender, System.EventArgs e)
        {

        }

        protected void Application_AuthenticateRequest(object sender, System.EventArgs e)
        {

        }

        protected void Application_Error(object sender, System.EventArgs e)
        {

        }

        protected void Session_End(object sender, System.EventArgs e)
        {

        }

        protected void Application_End(object sender, System.EventArgs e)
        {

        }


    }


}

o puede agregar entradas a Web.config si no posee el código fuente de la aplicación:

      <httpModules>
        <add name="RequestLanguageChanger" type= "libRequestLanguageChanger.RequestLanguageChanger, libRequestLanguageChanger" />
      </httpModules>
    </system.web>

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>

    <modules runAllManagedModulesForAllRequests="true">
      <add name="RequestLanguageChanger" type="libRequestLanguageChanger.RequestLanguageChanger, libRequestLanguageChanger" />
    </modules>
  </system.webServer>
</configuration>

La entrada en system.webServer es para IIS7 +, la otra en system.web es para IIS 6.
Tenga en cuenta que debe establecer runAllManagedModulesForAllRequests en true, para que funcione correctamente.

La cadena de tipo tiene el formato "Namespace.Class, Assembly". Tenga en cuenta que si escribe su ensamblado en VB.NET en lugar de C #, VB crea un espacio de nombres predeterminado para cada proyecto, por lo que su cadena se verá como

"[DefaultNameSpace.Namespace].Class, Assembly"

Si desea evitar este problema, escriba la DLL en C #.

Stefan Steiger
fuente
Creo que es posible que desee eliminar 'vmswisslife' y 'vmraiffeisen' de la respuesta para que no obtenga correlaciones falsas.
quetzalcoatl
@quetzalcoatl: Los dejé ahí como ejemplo, no es un descuido, no es de ninguna manera confidencial. Pero es cierto, quizás sea mejor eliminarlos. Hecho.
Stefan Steiger
7

¿Qué tal un enfoque que no solo permita múltiples dominios, sino que también permita dominios dinámicos?

El caso de uso aquí es con una parte de la aplicación Sharepoint que carga nuestro sitio dentro de Sharepoint a través de un iframe. El problema es que sharepoint tiene subdominios dinámicos como https://yoursite.sharepoint.com . Entonces, para IE, necesitamos especificar ALLOW-FROM https: //.sharepoint.com

Es un asunto complicado, pero podemos hacerlo sabiendo dos hechos:

  1. Cuando se carga un iframe, solo valida las opciones de X-Frame en la primera solicitud. Una vez que se carga el iframe, puede navegar dentro del iframe y el encabezado no se verifica en solicitudes posteriores.

  2. Además, cuando se carga un iframe, el referente HTTP es la URL del iframe principal.

Puede aprovechar estos dos hechos del lado del servidor. En ruby, estoy usando el siguiente código:

  uri = URI.parse(request.referer)
  if uri.host.match(/\.sharepoint\.com$/)
    url = "https://#{uri.host}"
    response.headers['X-Frame-Options'] = "ALLOW-FROM #{url}"
  end

Aquí podemos permitir dinámicamente dominios basados ​​en el dominio principal. En este caso, nos aseguramos de que el host termine en sharepoint.com manteniendo nuestro sitio a salvo de clickjacking.

Me encantaría escuchar comentarios sobre este enfoque.

Peter P.
fuente
2
Precaución: esto se rompe si el host es "fakesharepoint.com". La expresión regular debe ser:/\.sharepoint\.com$/
nitsas
@StefanSteiger, eso es correcto, pero Chrome tampoco experimenta este problema. Chrome y otros navegadores que cumplen con los estándares siguen el modelo más reciente de Política de seguridad de contenido (CSP).
Peter P.
1

El RFC para el campo de encabezado HTTP X-Frame-Options establece que el campo "ALLOW-FROM" en el valor del encabezado X-Frame-Options solo puede contener un dominio. No se permiten varios dominios.

El RFC sugiere una solución para este problema. La solución es especificar el nombre de dominio como un parámetro de url en el iframe src url. El servidor que aloja la url iframe src puede verificar el nombre de dominio dado en los parámetros de la url. Si el nombre de dominio coincide con una lista de nombres de dominio válidos, el servidor puede enviar el encabezado X-Frame-Options con el valor: "ALLOW-FROM domain-name", donde el nombre de dominio es el nombre del dominio que está intentando incrustar el contenido remoto. Si el nombre de dominio no se proporciona o no es válido, entonces el encabezado X-Frame-Options se puede enviar con el valor: "deny".

Nadir Latif
fuente
1

Estrictamente hablando, no, no puedes.

Sin embargo, puede especificar X-Frame-Options: mysite.comy, por lo tanto, permitir subdomain1.mysite.comy subdomain2.mysite.com. Pero sí, sigue siendo un dominio. Resulta que hay una solución para esto, pero creo que es más fácil leerlo directamente en las especificaciones de RFC: https://tools.ietf.org/html/rfc7034

También vale la pena señalar que la frame-ancestordirectiva del encabezado Content-Security-Policy (CSP) hace obsoleto X-Frame-Options. Leer más aquí .

Jim Aho
fuente
0

No es exactamente lo mismo, pero podría funcionar en algunos casos: hay otra opción ALLOWALLque eliminará la restricción de manera efectiva, lo que podría ser bueno para entornos de prueba / preproducción.

Willyfrog
fuente
Esto no está documentado en MDN.
andig
0

Tuve que agregar X-Frame-Options para IE y Content-Security-Policy para otros navegadores. Así que hice algo como seguir.

if allowed_domains.present?
  request_host = URI.parse(request.referer)
  _domain = allowed_domains.split(" ").include?(request_host.host) ? "#{request_host.scheme}://#{request_host.host}" : app_host
  response.headers['Content-Security-Policy'] = "frame-ancestors #{_domain}"
  response.headers['X-Frame-Options'] = "ALLOW-FROM #{_domain}"
else
  response.headers.except! 'X-Frame-Options'
end
jbmyid
fuente
-4

Una posible solución alternativa sería utilizar un script "frame-breaker" como se describe aquí

Solo necesita modificar la declaración "si" para verificar sus dominios permitidos.

   if (self === top) {
       var antiClickjack = document.getElementById("antiClickjack");
       antiClickjack.parentNode.removeChild(antiClickjack);
   } else {
       //your domain check goes here
       if(top.location.host != "allowed.domain1.com" && top.location.host == "allowed.domain2.com")
         top.location = self.location;
   }

Esta solución alternativa sería segura, creo. porque con javascript no habilitado, no tendrá ningún problema de seguridad sobre un sitio web malicioso que enmarque su página.

SinaX
fuente
1
Esto no funcionará debido a la misma política de origen al llamar a top.location.
Eric R.
-8

SI. Este método permitió múltiples dominios.

VB.NET

response.headers.add("X-Frame-Options", "ALLOW-FROM " & request.urlreferer.tostring())
usuario4778040
fuente
9
Esto parece anular el propósito de X-Frame-Options, ya que permite enmarcar a cualquier sitio.
Andrey Shchekin
5
Esta respuesta parece que podría ser una buena base como solución, pero necesita una lógica adicional para que solo ejecute este código si request.urlreferer.tostring () es uno de los orígenes que desea permitir.
Zergleb
Si está haciendo esto, ¿por qué está usando el encabezado X-Frame-Options ... simplemente
ignórelo