Opções de X-Frame Permitir de múltiplos domínios

99

Eu tenho um site ASP.NET 4.0 IIS7.5 que preciso proteger usando o cabeçalho X-Frame-Options.

Também preciso habilitar as páginas do meu site para serem iframed do meu mesmo domínio, bem como do meu aplicativo do Facebook.

Atualmente tenho meu site configurado com um site dirigido a:

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

Quando eu visualizei minha página do Facebook com Chrome ou Firefox, as páginas de meus sites (sendo iframed com minha página do Facebook) são exibidas ok, mas no IE9, recebo o erro:

"esta página não pode ser exibida ..." (devido à X-Frame_Optionsrestrição).

Como configuro o X-Frame-Options: ALLOW-FROMpara suportar mais de um único domínio?

X-FRAME-OPTION ser um novo recurso parece fundamentalmente falho se apenas um único domínio puder ser definido.

user1340663
fonte
2
Esta parece ser uma limitação conhecida: owasp.org/index.php/…
Pierre Ernst de

Respostas:

108

X-Frame-Optionsestá obsoleto. Do MDN :

Este recurso foi removido dos padrões da web. Embora alguns navegadores ainda possam suportá-lo, ele está em processo de exclusão. Não o use em projetos novos ou antigos. Páginas ou aplicativos da Web que o usam podem quebrar a qualquer momento.

A alternativa moderna é o Content-Security-Policycabeçalho, que junto com muitas outras políticas pode colocar na lista branca quais URLs têm permissão para hospedar sua página em um quadro, usando a frame-ancestorsdiretiva.
frame-ancestorssuporta vários domínios e até curingas, por exemplo:

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

Infelizmente, por enquanto, o Internet Explorer não oferece suporte completo à Política de Segurança de Conteúdo .

ATUALIZAÇÃO: MDN removeu seu comentário de descontinuação. Aqui está um comentário semelhante do nível de política de segurança de conteúdo do W3C

A frame-ancestorsdiretiva torna o X-Frame-Optionscabeçalho obsoleto . Se um recurso tiver ambas as políticas, a frame-ancestorspolítica DEVE ser aplicada e a X-Frame-Optionspolítica DEVE ser ignorada.

Kobi
fonte
14
frame-ancestors é marcado como "API experimental e não deve ser usado no código de produção" no MDN. + X-Frame-Options não está obsoleto, mas "não padrão", mas "é amplamente suportado e pode ser usado em conjunto com CSP"
Jonathan Muller
1
@JonathanMuller - O texto X-Frame-Optionsmudou e é menos severo agora. É um bom ponto que é arriscado usar uma especificação que não foi finalizada. Obrigado!
Kobi
2
Não consigo mais encontrar o aviso de descontinuado no MDN. A Mozilla mudou de opinião?
thomaskonrad
2
@ to0om - Obrigado! Eu atualizei a resposta com outro comentário. Posso ter sido muito forte em minha resposta. De qualquer forma, X-Frame-Optionsnão oferece suporte a fontes múltiplas.
Kobi
4
@Kobi, acho que a resposta precisa ser reorganizada. A primeira frase diz que isso está obsoleto de acordo com o MDN. Será menos enganoso se você adicionar sua atualização no topo (com um "UPDATE:" em negrito). Obrigado.
Kasun Gajasinghe
39

Do RFC 7034 :

Caracteres curinga ou listas para declarar vários domínios em uma instrução ALLOW-FROM não são permitidos

Assim,

Como defino as Opções do X-Frame: ALLOW-FROM para suportar mais de um único domínio?

Você não pode. Como alternativa, você pode usar URLs diferentes para parceiros diferentes. Para cada URL, você pode usar seu próprio X-Frame-Optionsvalor. Por exemplo:

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

Pois yousite.comvocê pode apenas usar X-Frame-Options: deny.

BTW , por enquanto, o Chrome (e todos os navegadores baseados em webkit) não oferece suporte a ALLOW-FROM declarações.

vbo
fonte
1
Parece que o webkit agora suporta o ALLOW-FROMuso do link fornecido.
Jimi
3
@Jimi Não, não - o último comentário no link em questão diz que você precisa usar uma política CSP. Esta opção ainda não funciona no Chrome.
NickG
9

Necromante.
As respostas fornecidas estão incompletas.

Primeiro, como já foi dito, você não pode adicionar vários hosts allow-from, isso não é suportado.
Em segundo lugar, você precisa extrair dinamicamente esse valor do referenciador HTTP, o que significa que você não pode adicionar o valor a Web.config, porque nem sempre é o mesmo valor.

Será necessário fazer a detecção do navegador para evitar adicionar allow-from quando o navegador é Chrome (isso produz um erro no console de depuração, que pode encher rapidamente o console ou tornar o aplicativo lento). Isso também significa que você precisa modificar a detecção do navegador ASP.NET, uma vez que identifica erroneamente o Edge como Chrome.

Isso pode ser feito no ASP.NET escrevendo um módulo HTTP que é executado em cada solicitação, que acrescenta um cabeçalho http para cada resposta, dependendo do referenciador da solicitação. Para o Chrome, é necessário adicionar 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 

Você precisa registrar a função context_EndRequest na função Init do 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


}

Em seguida, você precisa adicionar o módulo ao seu aplicativo. Você pode fazer isso programaticamente em Global.asax substituindo a função Init do HttpApplication, como este:

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)
        {

        }


    }


}

ou você pode adicionar entradas ao Web.config se não possuir o código-fonte do aplicativo:

      <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>

A entrada em system.webServer é para IIS7 +, a outra em system.web é para IIS 6.
Observe que você precisa definir runAllManagedModulesForAllRequests como true, para que funcione corretamente.

A string em tipo está no formato "Namespace.Class, Assembly". Observe que se você escrever seu assembly em VB.NET em vez de C #, o VB cria um namespace padrão para cada projeto, de modo que sua string será semelhante a

"[DefaultNameSpace.Namespace].Class, Assembly"

Se você quiser evitar esse problema, escreva a DLL em C #.

Stefan Steiger
fonte
Acho que você pode querer remover 'vmswisslife' e 'vmraiffeisen' da resposta para que não haja falsas correlações.
quetzalcoatl
@quetzalcoatl: Eu os deixei lá como exemplo, não é um descuido, não é de forma alguma confidencial. Mas é verdade, talvez seja melhor removê-los. Feito.
Stefan Steiger
7

Que tal uma abordagem que não apenas permite vários domínios, mas permite domínios dinâmicos.

O caso de uso aqui é com uma parte do aplicativo Sharepoint que carrega nosso site dentro do Sharepoint por meio de um iframe. O problema é que o sharepoint tem subdomínios dinâmicos como https://yoursite.sharepoint.com . Portanto, para o IE, precisamos especificar ALLOW-FROM https: //.sharepoint.com

Negócio complicado, mas podemos realizá-lo sabendo de dois fatos:

  1. Quando um iframe é carregado, ele só valida as Opções do X-Frame na primeira solicitação. Depois que o iframe é carregado, você pode navegar dentro do iframe e o cabeçalho não é verificado nas solicitações subsequentes.

  2. Além disso, quando um iframe é carregado, o referenciador HTTP é o url do iframe pai.

Você pode aproveitar esses dois fatos do lado do servidor. Em ruby, estou usando o seguinte 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

Aqui, podemos permitir domínios dinamicamente com base no domínio pai. Nesse caso, garantimos que o host termina em sharepoint.com, mantendo nosso site protegido contra clickjacking.

Eu adoraria ouvir comentários sobre essa abordagem.

Peter P.
fonte
2
Cuidado: isso é interrompido se o host for "fakesharepoint.com". A regex deve ser:/\.sharepoint\.com$/
nitsas
@StefanSteiger isso mesmo, mas o Chrome também não enfrenta esse problema. O Chrome e outros navegadores compatíveis com os padrões seguem o modelo mais recente da Política de Segurança de Conteúdo (CSP).
Peter P.
4

De acordo com as especificações MDN , X-Frame-Options: ALLOW-FROMnão é compatível com o Chrome e o suporte é desconhecido no Edge e Opera.

Content-Security-Policy: frame-ancestorssubstitui X-Frame-Options(de acordo com esta especificação W3 ), mas frame-ancestorstem compatibilidade limitada. De acordo com essas especificações MDN , não é compatível com IE ou Edge.

Andrew
fonte
1

O RFC para as opções do X-Frame do campo do cabeçalho HTTP afirma que o campo "ALLOW-FROM" no valor do cabeçalho X-Frame-Options pode conter apenas um domínio. Vários domínios não são permitidos.

O RFC sugere uma solução alternativa para esse problema. A solução é especificar o nome de domínio como um parâmetro de url no url iframe src. O servidor que hospeda o url iframe src pode então verificar o nome de domínio fornecido nos parâmetros do url. Se o nome de domínio corresponder a uma lista de nomes de domínio válidos, o servidor pode enviar o cabeçalho X-Frame-Options com o valor: "ALLOW-FROM domain-name", onde domain name é o nome do domínio que está tentando incorporar o conteúdo remoto. Se o nome de domínio não for fornecido ou não for válido, o cabeçalho X-Frame-Options pode ser enviado com o valor: "deny".

Nadir Latif
fonte
1

Estritamente falando, não, você não pode.

No entanto, você pode especificar X-Frame-Options: mysite.come, portanto, permitir subdomain1.mysite.come subdomain2.mysite.com. Mas sim, ainda é um domínio. Acontece que há alguma solução alternativa para isso, mas acho que é mais fácil ler diretamente nas especificações RFC: https://tools.ietf.org/html/rfc7034

Também vale a pena salientar que a frame-ancestordiretiva do cabeçalho Content-Security-Policy (CSP) torna as opções X-Frame obsoletas. Leia mais aqui .

Jim Aho
fonte
0

Não é exatamente o mesmo, mas pode funcionar em alguns casos: há outra opção ALLOWALLque removerá efetivamente a restrição, o que pode ser uma coisa boa para ambientes de teste / pré-produção

Willyfrog
fonte
Isso não está documentado no MDN.
andig
0

Eu tive que adicionar X-Frame-Options para IE e Content-Security-Policy para outros navegadores. Então eu fiz 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
fonte
-4

Uma possível solução alternativa seria usar um script "frame-breaker" conforme descrito aqui

Você só precisa alterar a instrução "if" para verificar seus domínios 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;
   }

Essa solução alternativa seria segura, eu acho. porque com o javascript não habilitado, você não terá nenhuma preocupação de segurança sobre um site mal-intencionado enquadrando sua página.

SinaX
fonte
1
Isso não funcionará devido à política de mesma origem ao chamar top.location.
Eric R.
-8

SIM. Este método permitia múltiplos domínios.

VB.NET

response.headers.add("X-Frame-Options", "ALLOW-FROM " & request.urlreferer.tostring())
user4778040
fonte
9
Isso parece anular o propósito das X-Frame-Options, pois permite que qualquer site seja enquadrado.
Andrey Shchekin
5
Esta resposta parece que pode ser uma boa base como solução, mas precisa de lógica extra para que só execute esse código se request.urlreferer.tostring () for uma das origens que você deseja permitir.
Zergleb
Se você está fazendo isso, por que está usando o X-Frame-Options Header ... apenas ignore
vs4vijay