121 votes

X-Frame-Options Allow-From plusieurs domaines

J'ai un site ASP.NET 4.0 IIS7.5 que je dois sécuriser en utilisant l'en-tête X-Frame-Options.

J'ai également besoin de permettre aux pages de mon site d'être affichées à partir du même domaine et de mon application Facebook.

Actuellement, mon site est configuré avec un en-tête de site de :

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

Lorsque je consulte ma page Facebook avec Chrome ou Firefox, les pages de mon site (qui sont iframées avec ma page Facebook) s'affichent correctement, mais sous IE9, j'obtiens l'erreur suivante :

"Cette page ne peut pas être affichée " (à cause de la X-Frame_Options ).

Comment paramétrer le X-Frame-Options: ALLOW-FROM pour prendre en charge plus d'un seul domaine ?

X-FRAME-OPTION La possibilité de créer une nouvelle fonctionnalité semble fondamentalement erronée si seul un domaine unique peut être défini.

136voto

Kobi Points 65357

X-Frame-Options est obsolète. De MDN :

Cette fonctionnalité a été supprimée des normes Web. Bien que certains navigateurs puissent encore la prendre en charge, elle est en passe d'être abandonnée. Ne l'utilisez pas dans les projets anciens ou nouveaux. Les pages ou les applications Web qui l'utilisent peuvent tomber en panne à tout moment.

L'alternative moderne est le Content-Security-Policy qui, avec de nombreuses autres politiques, peut établir une liste blanche des URL autorisées à héberger votre page dans un cadre, en utilisant l'en-tête frame-ancestors directive.
frame-ancestors prend 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 totalement en charge la politique de sécurité du contenu (Content-Security-Policy) .

UPDATE : MDN a supprimé le commentaire de dépréciation. Voici un commentaire similaire de Niveau de la politique de sécurité du contenu du W3C

En frame-ancestors directive obsoletes el X-Frame-Options l'en-tête. Si une ressource possède les deux politiques, l'en-tête frame-ancestors doit être mise en œuvre et la politique de X-Frame-Options devrait être ignorée.

42voto

vbo Points 2347

De RFC 7034 :

Les caractères génériques ou les listes permettant de déclarer plusieurs domaines dans une seule déclaration ALLOW-FROM ne sont pas autorisés.

Ainsi,

Comment puis-je définir l'option X-Frame-Options : ALLOW-FROM pour prendre en charge plus d'un seul domaine ?

Vous ne pouvez pas. Pour contourner le problème, vous pouvez utiliser des URL différents pour des partenaires différents. Pour chaque URL, vous pouvez utiliser sa propre adresse X-Frame-Options valeur. Par exemple :

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

Pour yousite.com vous pouvez simplement utiliser X-Frame-Options: deny .

BTW pour l'instant Chrome (et tous les navigateurs basés sur webkit) ne prend pas en charge ALLOW-FROM de l'Union européenne.

11voto

Quandary Points 12867

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

Tout d'abord, comme cela a déjà été dit, vous ne pouvez pas ajouter plusieurs hôtes "allow-from", ce n'est pas supporté.
Deuxièmement, vous devez extraire dynamiquement cette valeur du référent HTTP, ce qui signifie que vous ne pouvez pas ajouter la valeur au fichier Web.config, car ce n'est pas toujours la même valeur.

Il faudra faire de la détection de navigateur pour éviter d'ajouter allow-from lorsque le navigateur est Chrome (cela produit une erreur sur la console de débogage, ce qui peut rapidement remplir la console, ou rendre l'application lente). Cela signifie aussi qu'il faut modifier la détection du navigateur ASP.NET, qui identifie à tort Edge comme Chrome.

Cela peut être fait en ASP.NET en écrivant un module HTTP qui s'exécute à chaque requête et qui ajoute un en-tête http à chaque réponse, en fonction du référent de la requête. Pour Chrome, il faut ajouter Content-Security-Policy.

// https://stackoverflow.com/questions/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

                    // https://stackoverflow.com/questions/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)
    {
        // https://stackoverflow.com/questions/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 de manière programmatique dans Global.asax en surchargeant 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 à true, pour que cela fonctionne correctement.

La chaîne de caractères est au format "Namespace.Class, Assembly" . Notez que si vous écrivez votre assemblage en VB.NET au lieu de C#, VB crée un espace de noms par défaut pour chaque projet, de sorte que votre chaîne de caractères aura la forme suivante

"[DefaultNameSpace.Namespace].Class, Assembly"

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

7voto

Peter P. Points 973

Pourquoi ne pas adopter une approche qui permette non seulement d'avoir plusieurs domaines, mais aussi des domaines dynamiques ?

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

C'est une affaire délicate, mais nous pouvons y parvenir en connaissant deux faits :

  1. Lorsqu'une iframe se charge, elle ne valide les X-Frame-Options que lors de la première demande. Une fois l'iframe chargée, vous pouvez naviguer à l'intérieur de l'iframe et l'en-tête n'est pas vérifié lors des requêtes suivantes.

  2. De même, lorsqu'une iframe est chargée, le référent HTTP est l'url de l'iframe parent.

Vous pouvez exploiter ces deux faits côté serveur. En 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 en fonction du domaine parent. Dans ce cas, nous nous assurons que l'hôte se termine par sharepoint.com, ce qui protège notre site contre le détournement de clics.

J'aimerais connaître votre avis sur cette approche.

4voto

Andrew Points 473

Conformément à la Spécifications MDN , X-Frame-Options: ALLOW-FROM n'est pas pris en charge dans Chrome et sa prise en charge est inconnue dans Edge et Opera.

Content-Security-Policy: frame-ancestors dérogations X-Frame-Options (conformément à la cette spécification W3 ), mais frame-ancestors a une compatibilité limitée. Selon ces Spécifications MDN il n'est pas pris en charge par IE ou Edge.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X