X-Frame-Options Autoriser plusieurs domaines

100

J'ai un site ASP.NET 4.0 IIS7.5 dont j'ai besoin sécurisé à l'aide de l'en-tête X-Frame-Options.

Je dois également activer les pages de mon site pour être iframées à partir de mon même domaine ainsi que de mon application Facebook.

Actuellement, mon site est configuré avec un site intitulé:

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

Lorsque j'ai consulté ma page Facebook avec Chrome ou Firefox, les pages de mes sites (étant iframées avec ma page facebook) s'affichent correctement, mais sous IE9, j'obtiens l'erreur:

"cette page ne peut pas être affichée…" (à cause de la X-Frame_Optionsrestriction).

Comment configurer le X-Frame-Options: ALLOW-FROMpour qu'il prenne en charge plusieurs domaines?

X-FRAME-OPTION être une nouvelle fonctionnalité semble fondamentalement défectueuse si un seul domaine peut être défini.

user1340663
la source
2
Cela semble être une limitation connue: owasp.org/index.php/…
Pierre Ernst

Réponses:

108

X-Frame-Optionsest obsolète. De MDN :

Cette fonctionnalité a été supprimée des standards Web. Bien que certains navigateurs puissent toujours le prendre en charge, il est en cours de suppression. Ne l'utilisez pas dans des projets anciens ou nouveaux. Les pages ou applications Web qui l'utilisent peuvent être interrompues à tout moment.

L'alternative moderne est l'en- Content-Security-Policytête, qui, avec de nombreuses autres politiques, peut lister quelles URL sont autorisées à héberger votre page dans un cadre, en utilisant la frame-ancestorsdirective.
frame-ancestorsprend en charge plusieurs domaines et même des caractères génériques, par exemple:

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

Malheureusement, pour l'instant, Internet Explorer ne prend pas entièrement en charge la stratégie de sécurité du contenu .

MISE À JOUR: MDN a supprimé son commentaire d'obsolescence. Voici un commentaire similaire du niveau de politique de sécurité du contenu du W3C

La frame-ancestorsdirective rend obsolète l'en- X-Frame-Optionstête. Si une ressource a les deux politiques, la frame-ancestorspolitique DEVRAIT être appliquée et la X-Frame-Optionspolitique DEVRAIT être ignorée.

Kobi
la source
14
frame-ancestors est marqué comme "API expérimentale et ne doit pas être utilisé dans le code de production" sur MDN. + X-Frame-Options n'est pas obsolète mais "non standard" mais "est largement supporté et peut être utilisé en conjonction avec CSP"
Jonathan Muller
1
@JonathanMuller - Le libellé a X-Frame-Optionschangé et est moins sévère maintenant. C'est un bon point qu'il est risqué d'utiliser une spécification qui n'est pas finalisée. Merci!
Kobi
2
Je ne trouve plus l'avertissement obsolète sur MDN. Mozilla a-t-il changé d'avis?
thomaskonrad
2
@ to0om - Merci! J'ai mis à jour la réponse avec un autre commentaire. Je suis peut-être venu trop fort dans ma réponse. Quoi qu'il en soit, X-Frame-Optionsne prend pas en charge plusieurs sources.
Kobi
4
@Kobi, je pense que la réponse doit être réorganisée. La toute première phrase dit que cela est obsolète selon le MDN. Ce sera moins trompeur si vous ajoutez votre mise à jour en haut (avec un "UPDATE:" en gras). Merci.
Kasun Gajasinghe
39

À partir de la RFC 7034 :

Les caractères génériques ou les listes pour déclarer plusieurs domaines dans une instruction ALLOW-FROM ne sont pas autorisés

Alors,

Comment définir les options X-Frame: ALLOW-FROM pour prendre en charge plusieurs domaines?

Vous ne pouvez pas. Pour contourner ce problème, vous pouvez utiliser différentes URL pour différents partenaires. Pour chaque URL, vous pouvez utiliser sa propre X-Frame-Optionsvaleur. Par exemple:

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

Car yousite.comvous pouvez simplement utiliser X-Frame-Options: deny.

BTW , pour l'instant Chrome (et tous les navigateurs basés sur le Webkit) ne prend pas du tout en charge les ALLOW-FROM déclarations.

vbo
la source
1
Il semble que Webkit prend désormais en charge l' ALLOW-FROMutilisation du lien que vous avez fourni.
Jimi
3
@Jimi Non, ce n'est pas le cas - le dernier commentaire sur le lien en question indique que vous devez utiliser une stratégie CSP à la place. Cette option ne fonctionne toujours pas dans Chrome.
NickG
9

Nécromancie.
Les réponses fournies sont incomplètes.

Tout d'abord, comme déjà dit, vous ne pouvez pas ajouter plusieurs hôtes autorisés, ce n'est pas pris en charge.
Deuxièmement, vous devez extraire dynamiquement cette valeur du référent HTTP, ce qui signifie que vous ne pouvez pas ajouter la valeur à Web.config, car ce n'est pas toujours la même valeur.

Il sera nécessaire de faire une détection du navigateur pour éviter d'ajouter une autorisation lorsque le navigateur est Chrome (cela produit une erreur sur la console de débogage, ce qui peut rapidement remplir la console ou ralentir l'application). Cela signifie également que vous devez modifier la détection du navigateur ASP.NET, car il identifie à tort Edge comme Chrome.

Cela peut être fait dans ASP.NET en écrivant un module HTTP qui s'exécute à chaque demande, qui ajoute un en-tête http pour chaque réponse, en fonction du référent de la demande. Pour Chrome, il doit ajouter 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 

Vous devez enregistrer la fonction context_EndRequest dans la fonction Init du module 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


}

Ensuite, vous devez ajouter le module à votre application. Vous pouvez le faire soit par programme dans Global.asax en remplaçant la fonction Init de HttpApplication, comme ceci:

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 vous pouvez ajouter des entrées à Web.config si vous ne possédez pas le code source de l'application:

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

L'entrée dans system.webServer est pour IIS7 +, l'autre dans system.web est pour IIS 6.
Notez que vous devez définir runAllManagedModulesForAllRequests sur true, pour que cela fonctionne correctement.

La chaîne de type est au format "Namespace.Class, Assembly". Notez que si vous écrivez votre assembly dans VB.NET au lieu de C #, VB crée un espace de noms par défaut pour chaque projet, ainsi votre chaîne ressemblera à

"[DefaultNameSpace.Namespace].Class, Assembly"

Si vous souhaitez éviter ce problème, écrivez la DLL en C #.

Stefan Steiger
la source
Je pense que vous voudrez peut-être supprimer «vmswisslife» et «vmraiffeisen» de la réponse pour éviter de fausses corrélations.
quetzalcoatl
@quetzalcoatl: Je les ai laissés là à titre d'exemple, ce n'est pas un oubli, ce n'est en aucun cas confidentiel. Mais vrai, il vaut peut-être mieux les supprimer. Terminé.
Stefan Steiger
7

Que diriez-vous d'une approche qui permet non seulement plusieurs domaines, mais autorise des domaines dynamiques.

Le cas d'utilisation ici est avec une partie d'application Sharepoint qui charge notre site à l'intérieur de Sharepoint via un iframe. Le problème est que sharepoint a des sous-domaines dynamiques tels que https://yoursite.sharepoint.com . Donc, pour IE, nous devons spécifier ALLOW-FROM https: //.sharepoint.com

Une affaire délicate, mais nous pouvons y arriver en sachant deux faits:

  1. Lorsqu'un iframe se charge, il ne valide les options X-Frame qu'à la première requête. Une fois l'iframe chargée, vous pouvez naviguer dans l'iframe et l'en-tête n'est pas vérifié lors des demandes suivantes.

  2. De plus, lorsqu'un iframe est chargé, le référent HTTP est l'url iframe parent.

Vous pouvez tirer parti de ces deux faits côté serveur. Dans ruby, j'utilise le code suivant:

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

Ici, nous pouvons autoriser dynamiquement des domaines basés sur le domaine parent. Dans ce cas, nous nous assurons que l'hébergeur se termine par sharepoint.com, protégeant notre site contre le détournement de clics.

J'aimerais entendre vos commentaires sur cette approche.

Peter P.
la source
2
Attention: cela s'arrête si l'hôte est "fakesharepoint.com". L'expression /\.sharepoint\.com$/
régulière
@StefanSteiger c'est vrai, mais Chrome ne rencontre pas non plus ce problème. Chrome et d'autres navigateurs conformes aux normes suivent le nouveau modèle de politique de sécurité du contenu (CSP).
Peter P.
4

Selon les spécifications MDN , X-Frame-Options: ALLOW-FROMn'est pas pris en charge dans Chrome et la prise en charge est inconnue dans Edge et Opera.

Content-Security-Policy: frame-ancestorsremplace X-Frame-Options(selon cette spécification W3 ), mais frame-ancestorssa compatibilité est limitée. Conformément à ces spécifications MDN , il n'est pas pris en charge dans IE ou Edge.

Andrew
la source
1

Le RFC pour le champ d'en-tête HTTP X-Frame-Options indique que le champ "ALLOW-FROM" dans la valeur d'en-tête X-Frame-Options ne peut contenir qu'un seul domaine. Plusieurs domaines ne sont pas autorisés.

La RFC suggère une solution à ce problème. La solution consiste à spécifier le nom de domaine en tant que paramètre d'url dans l'url iframe src. Le serveur qui héberge l'url src iframe peut alors vérifier le nom de domaine donné dans les paramètres d'url. Si le nom de domaine correspond à une liste de noms de domaine valides, le serveur peut envoyer l'en-tête X-Frame-Options avec la valeur: "ALLOW-FROM nom de domaine", où nom de domaine est le nom du domaine qui tente de incorporer le contenu distant. Si le nom de domaine n'est pas donné ou n'est pas valide, alors l'en-tête X-Frame-Options peut être envoyé avec la valeur: "deny".

Nadir Latif
la source
1

Strictement non, vous ne pouvez pas.

Vous pouvez cependant spécifier X-Frame-Options: mysite.comet donc autoriser subdomain1.mysite.comet subdomain2.mysite.com. Mais oui, c'est encore un domaine. Il se trouve qu'il existe une solution de contournement pour cela, mais je pense qu'il est plus facile de le lire directement dans les spécifications RFC: https://tools.ietf.org/html/rfc7034

Il convient également de souligner que la frame-ancestordirective d'en-tête Content-Security-Policy (CSP) rend obsolète X-Frame-Options. En savoir plus ici .

Jim Aho
la source
0

Pas exactement la même chose, mais pourrait fonctionner dans certains cas: il existe une autre option ALLOWALLqui supprimera efficacement la restriction, ce qui pourrait être une bonne chose pour les environnements de test / pré-production

Willyfrog
la source
Ceci n'est pas documenté sur MDN.
andig
0

J'ai dû ajouter X-Frame-Options pour IE et Content-Security-Policy pour les autres navigateurs. Alors j'ai fait quelque chose comme suivre.

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
la source
-4

Une solution de contournement possible consisterait à utiliser un script "frame-breaker" comme décrit ici

Il vous suffit de modifier l'instruction "if" pour vérifier vos domaines autorisés.

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

Cette solution de contournement serait sûre, je pense. car si javascript n'est pas activé, vous n'aurez aucun problème de sécurité à propos d'un site Web malveillant encadrant votre page.

SinaX
la source
1
Cela ne fonctionnera pas en raison de la même politique d'origine lors de l'appel de top.location.
Eric R.
-8

OUI. Cette méthode autorisait plusieurs domaines.

VB.NET

response.headers.add("X-Frame-Options", "ALLOW-FROM " & request.urlreferer.tostring())
user4778040
la source
9
Cela semble aller à l'encontre de l'objectif de X-Frame-Options car il permet à n'importe quel site de cadrer.
Andrey Shchekin
5
Cette réponse semble être une bonne base de solution mais elle a besoin d'une logique supplémentaire pour qu'elle n'exécute ce code que si request.urlreferer.tostring () est l'une des origines que vous souhaitez autoriser.
Zergleb
Si vous faites cela, pourquoi utilisez-vous même l'en-tête X-Frame-Options ... ignorez-le simplement
vs4vijay