X框架选项允许来自多个域


99

我有一个ASP.NET 4.0 IIS7.5站点,需要使用X-Frame-Options标头对其进行保护。

我还需要启用来自同一个域以及我的facebook应用程序的网站页面。

目前,我的网站配置有以下网站:

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

当我使用Chrome或Firefox查看我的Facebook页面时,我的网站页面(与我的Facebook页面进行了iframed)显示正常,但是在IE9下,出现错误:

“由于X-Frame_Options限制,无法显示此页面…” 。

如何设置X-Frame-Options: ALLOW-FROM来支持多个域?

X-FRAME-OPTION 如果只能定义一个域,则将其作为新功能从根本上来说是有缺陷的。


2
这似乎是一个已知的限制:owasp.org/index.php/…–
Pierre Ernst

Answers:


108

X-Frame-Options不推荐使用。从MDN

此功能已从Web标准中删除。尽管某些浏览器可能仍支持它,但是它正在被删除。不要在新旧项目中使用它。使用它的页面或Web应用程序可能随时中断。

现代的替代方法是Content-Security-Policy标头,它与许多其他策略一起可以使用frame-ancestors伪指令将允许在页面中托管页面的URL列入白名单。
frame-ancestors支持多个域甚至通配符,例如:

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

不幸的是,Internet Explorer目前尚不完全支持Content-Security-Policy

更新: MDN已删除其弃用评论。这是W3C内容安全政策级别的类似评论

frame-ancestors指令使标头过时X-Frame-Options。如果资源同时具有两个策略,frame-ancestors则应强制执行X-Frame-Options策略,而应忽略该策略。


14
MDN上将frame-ancestors标记为“实验API,不应在生产代码中使用”。+不建议使用X-Frame-Options,但不要使用“ X-Frame-Options”,但要“广泛支持X-Frame-Options,并且可以与CSP一起使用”
Jonathan Muller

1
@JonathanMuller-措辞已X-Frame-Options更改,现在不那么严格。很好的一点是,使用未最终确定的规范是有风险的。谢谢!
Kobi 2015年

2
我再也找不到MDN上已停用的警告。Mozilla改变了看法吗?
thomaskonrad

2
@ to0om-谢谢!我用另一条评论更新了答案。我的回答可能太强了。无论哪种方式,X-Frame-Options都不支持多个来源。
科比

4
@Kobi,我认为答案需要重新组织。第一句话说,根据MDN,不推荐使用此方法。如果将更新添加到顶部(带有粗体的“ UPDATE:”),它将减少误导。谢谢。
Kasun Gajasinghe '16

39

RFC 7034开始

不允许在一个ALLOW-FROM语句中声明多个域的通配符或列表

所以,

如何设置X-Frame-Options:ALLOW-FROM支持多个域?

你不能 解决方法是,可以为不同的伙伴使用不同的URL。对于每个URL,您都可以使用它自己的X-Frame-Options值。例如:

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

yousite.com您可以使用X-Frame-Options: deny

顺便说一句,目前,Chrome(以及所有基于Webkit的浏览器)根本不支持 ALLOW-FROM语句。


1
看来webkit现在支持ALLOW-FROM使用您提供的链接。
Jimi

3
@Jimi不,不是-有关链接的最后一条评论说,您需要使用CSP策略。此选项在Chrome中仍然不起作用。
NickG '17

9

死灵法师。
提供的答案不完整。

首先,如上所述,您不能添加多个允许的主机,这是不支持的。
其次,您需要从HTTP引荐来源网址动态提取该值,这意味着您不能将其添加到Web.config中,因为它并不总是相同的值。

在浏览器为Chrome时,有必要进行浏览器检测以避免添加allow-from(它在调试控制台上产生错误,该错误会迅速填充控制台,或使应用程序变慢)。这也意味着您需要修改ASP.NET浏览器检测,因为它错误地将Edge标识为Chrome。

这可以在ASP.NET中通过编写一个在每个请求上运行的HTTP模块来完成,该模块为每个响应附加一个HTTP标头,具体取决于请求的引用者。对于Chrome,它需要添加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 

您需要在HTTP模块Init函数中注册context_EndRequest函数。

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


}

接下来,您需要将模块添加到您的应用程序。您可以通过重写HttpApplication的Init函数,在Global.asax中以编程方式执行此操作,如下所示:

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

        }


    }


}

或者,如果您不拥有应用程序源代码,则可以将条目添加到Web.config中:

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

system.webServer中的条目用于IIS7 +,system.web中的另一个条目用于IIS6。
请注意,您需要将runAllManagedModulesForAllRequests设置为true,因为它可以正常工作。

类型的字符串格式为"Namespace.Class, Assembly"。请注意,如果您使用VB.NET而不是C#编写程序集,则VB将为每个项目创建一个默认名称空间,因此您的字符串将如下所示

"[DefaultNameSpace.Namespace].Class, Assembly"

如果要避免此问题,请用C#编写DLL。


我认为您可能想从答案中删除“ vmswisslife”和“ vmraiffeisen”,这样就不会出现错误的相关性。
quetzalcoatl

@quetzalcoatl:我以他们为例,这不是疏忽,也不是任何机密。但没错,也许最好删除它们。做完了
Stefan Steiger,

7

不仅允许多个域,而且允许动态域的方法怎么样?

这里的用例是一个Sharepoint应用程序部分,该部分通过iframe将我们的网站加载到Sharepoint中。问题是共享点具有动态子域,例如https://yoursite.sharepoint.com。因此,对于IE,我们需要指定ALLOW-FROM https://.sharepoint.com

棘手的业务,但我们知道两个事实就可以完成:

  1. 加载iframe时,它只会在第一个请求上验证X-Frame-Options。加载iframe后,您可以在iframe中导航,并且在后续请求中不会检查标头。

  2. 另外,在加载iframe时,HTTP引荐来源网址是父iframe网址。

您可以在服务器端利用这两个事实。在ruby中,我使用以下代码:

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

在这里,我们可以基于父域动态允许域。在这种情况下,我们确保主机以sharepoint.com结尾,以确保我们的网站免遭点击劫持。

我希望听到有关此方法的反馈。


2
注意:如果主机为“ fakesharepoint.com”,则此操作将中断。正则表达式应为:/\.sharepoint\.com$/
nitsas 2015年

@StefanSteiger是正确的,但Chrome也没有遇到此问题。Chrome和其他符合标准的浏览器都遵循更新的内容安全策略(CSP)模型。
Peter P.

4

根据MDN规范X-Frame-Options: ALLOW-FROMChrome不支持,并且Edge和Opera不支持该功能。

Content-Security-Policy: frame-ancestors覆盖X-Frame-Options(按照此W3规范),但frame-ancestors兼容性有限。根据这些MDN规范,IE或Edge不支持它。


1

HTTP标头字段X-Frame-Options的RFC 指出,X-Frame-Options标头值中的“ ALLOW-FROM”字段只能包含一个域。不允许使用多个域。

RFC建议解决此问题。解决方案是在iframe src url中将域名指定为url参数。然后,托管iframe src url的服务器可以检查url参数中指定的域名。如果域名与有效域名列表匹配,则服务器可以发送X-Frame-Options标头,其值是:“ ALLOW-FROM domain-name”,其中domain name是尝试访问的域名。嵌入远程内容。如果未提供域名或该域名无效,则可以发送X-Frame-Options标头,其值为:“ deny”。


1

严格来说,不能。

但是X-Frame-Options: mysite.com,您可以指定,因此允许subdomain1.mysite.comsubdomain2.mysite.com。但是,是的,这仍然是一个领域。碰巧有一些解决方法,但是我认为最容易直接在RFC规范中阅读:https : //tools.ietf.org/html/rfc7034

还需要指出的是,Content-Security-Policy(CSP)标头的frame-ancestor指令已淘汰X-Frame-Options。在这里阅读更多


0

并不完全相同,但是在某些情况下可以使用:还有一个选项ALLOWALL可以有效消除限制,这对于测试/预生产环境可能是一件好事


这没有记录在MDN上。
andig

0

我必须为IE添加X-Frame-Options,为其他浏览器添加Content-Security-Policy。所以我做了类似的事情。

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

-4

一个可能的解决方法将使用“帧断路器”脚本描述这里

您只需要更改“ if”语句来检查您允许的域。

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

我认为,这种解决方法很安全。因为未启用javascript时,您不会对将您的页面构筑成恶意网站的安全性感到担忧。


1
由于在调用top.location时使用相同的原始策略,因此无法使用。
Eric R.

-8

是。此方法允许多个域。

VB.NET

response.headers.add("X-Frame-Options", "ALLOW-FROM " & request.urlreferer.tostring())

9
这似乎违反了X-Frame-Options的目的,因为它允许任何站点进行构架。
Andrey Shchekin

5
这个答案似乎可以作为一个很好的解决方案,但是它需要额外的逻辑,以便仅当request.urlreferer.tostring()是您希望允许的来源之一时才执行此代码。
Zergleb 2015年

如果您正在执行此操作,为什么还要使用X-Frame-Options标头...只需忽略它
vs4vijay 2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.