【问题标题】:X-Frame-Options Allow-From multiple domainsX-Frame-Options Allow-From 多个域
【发布时间】:2012-04-29 14:48:07
【问题描述】:

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

我还需要使我的网站页面能够从我的同一个域以及我的 facebook 应用程序进行 iframe。

目前我的站点配置了一个站点,其标题为:

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

当我使用 Chrome 或 Firefox 查看我的 Facebook 页面时,我的网站页面(使用我的 Facebook 页面进行 iframe)显示正常,但在 IE9 下,我收到错误:

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

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

如果只能定义一个域,X-FRAME-OPTION 作为一个新功能似乎存在根本缺陷。

【问题讨论】:

标签: asp.net security iis-7 header internet-explorer-9


【解决方案1】:

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 does not fully support Content-Security-Policy

更新: MDN 删除了他们的弃用评论。这是来自W3C's Content Security Policy Level 的类似评论

frame-ancestors 指令废弃 X-Frame-Options 标头。如果资源同时具有这两种策略,则应强制执行 frame-ancestors 策略,而应忽略 X-Frame-Options 策略。

【讨论】:

  • frame-ancestors 在 MDN 上被标记为“实验性 API,不应在生产代码中使用”。 + X-Frame-Options 没有被弃用,而是“非标准”,但“得到广泛支持,可与 CSP 结合使用”
  • @JonathanMuller - X-Frame-Options 上的措辞发生了变化,现在不那么严重了。使用未最终确定的规范是有风险的,这是一个很好的观点。谢谢!
  • 我在 MDN 上找不到已弃用的警告。 Mozilla 改变了看法吗?
  • @to0om - 谢谢!我用另一条评论更新了答案。我的回答可能太强势了。无论哪种方式,X-Frame-Options 都不支持多个来源。
  • @Kobi,我认为答案需要重新组织。第一句话说,根据 MDN,这已被弃用。如果您在顶部添加更新(带有粗体颜色的“更新:”),则误导性会降低。谢谢。
【解决方案2】:

来自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 的浏览器)does not supportALLOW-FROM 声明。

【讨论】:

  • 看起来 webkit 现在支持 ALLOW-FROM 使用您提供的链接。
  • @Jimi 不,它没有 - 有问题的链接的最后一条评论说您需要使用 CSP 策略。此选项在 Chrome 中仍然不起作用。
【解决方案3】:

死灵术。
提供的答案不完整。

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

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

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

您需要在 HTTP-module 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)
    {
        // https://stackoverflow.com/questions/441421/httpmodule-event-execution-order
        context.EndRequest += new System.EventHandler(context_EndRequest);
    }

    // context_EndRequest Code from above comes here


}

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

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 中的另一个条目适用于 IIS 6。
请注意,您需要将 runAllManagedModulesForAllRequests 设置为 true,因为它可以正常工作。

类型中的字符串格式为"Namespace.Class, Assembly"。 请注意,如果您在 VB.NET 而不是 C# 中编写程序集,VB 会为每个项目创建一个默认命名空间,因此您的字符串看起来像

"[DefaultNameSpace.Namespace].Class, Assembly"

如果你想避免这个问题,用 C# 编写 DLL。

【讨论】:

  • 我认为您可能希望从答案中删除 'vmswisslife' 和 'vmraiffeisen',以免出现错误的相关性。
  • @quetzalcoatl:我把它们留在那里作为一个例子,这不是疏忽,也不是机密。但确实如此,也许更好地删除它们。完成。
  • "避免在浏览器是 Chrome 时添加 allow-from(它会在调试 - 控制台上产生错误,这可以快速填满控制台,或使应用程序变慢)" - 这可能已更改在最新版本的 Chrome 中?我在 Chrome 的控制台中没有看到这样的“错误”?
  • ...进一步到我上面的评论。我只在 Chrome 的控制台中看到 X-Frame-Options 标头的完全无效指令报告的“错误”。 ALLOW-FROM 甚至 ALLOWALL(严格无效,但在“常用”中)不会导致“错误”,但 X-Frame-Options THIS-IS-INVALID 会(尽管我假设所有这些都被 Chrome 忽略)。我想知道我是否错过了增加控制台中调试/错误报告敏感性的技巧 - 但我不这么认为?使用 Chrome 86。
【解决方案4】:

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

这里的用例是一个 Sharepoint 应用程序部分,它通过 iframe 在 Sharepoint 内加载我们的网站。问题是sharepoint 具有动态子域,例如https://yoursite.sharepoint.com。所以对于 IE,我们需要指定 ALLOW-FROM https://.sharepoint.com

棘手的事情,但我们可以在知道两个事实的情况下完成它:

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

  2. 另外,当加载 iframe 时,HTTP 引用者是父 iframe url。

您可以在服务器端利用这两个事实。在 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 结尾,以确保我们的网站免受点击劫持。

我很想听听有关这种方法的反馈。

【讨论】:

  • 警告:如果主机是“fakesharepoint.com”,这会中断。正则表达式应该是:/\.sharepoint\.com$/
  • @StefanSteiger 没错,但 Chrome 也没有遇到这个问题。 Chrome 和更多符合标准的浏览器遵循更新的内容安全策略 (CSP) 模型。
【解决方案5】:

根据MDN Specifications,Chrome 不支持X-Frame-Options: ALLOW-FROM,Edge 和 Opera 中的支持未知。

Content-Security-Policy: frame-ancestors 覆盖X-Frame-Options(根据this W3 spec),但frame-ancestors 的兼容性有限。根据这些MDN Specs,IE 或 Edge 不支持它。

【讨论】:

    【解决方案6】:

    HTTP Header Field 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”。

    【讨论】:

      【解决方案7】:

      严格来说不可以。

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

      还值得指出的是,Content-Security-Policy (CSP) 标头的 frame-ancestor 指令已淘汰 X-Frame-Options。 Read more here.

      【讨论】:

        【解决方案8】:

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

        【讨论】:

        • 这在 MDN 上没有记录。
        【解决方案9】:

        我必须为 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
        

        【讨论】:

          【解决方案10】:

          适用于 Apache.htaccess 的多个域和子域的规则如下:

          Header always append Content-Security-Policy "frame-ancestors 'self' site1 site2;"
          

          示例:

          以下规则将只允许yoursite (self)https://example1.com/https://example2.com 放置iFrameyoursite

          Header always append Content-Security-Policy "frame-ancestors 'self' https://example1.com/ https://example.com;"
          

          这里是参考link

          【讨论】:

          • 请注意 newbedev 是一个 Stack Overflow 刮板;不要链接到它。相反,谷歌搜索文本或标题(可选site:stackoverflow.com)并找到正确的现场链接,而不是为抓取工具提供更多他们不应该获得的流量。
          • 我不知道你在这里的答案中做了什么编辑,你能具体说明一下@Zoe吗?
          • 都可以在the edit history找到
          【解决方案11】:

          一种可能的解决方法是使用here 中描述的“frame-breaker”脚本

          您只需要更改“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,您将不会担心恶意网站构建您的页面。

          【讨论】:

          • 调用 top.location 时,由于同源策略,这将不起作用。
          【解决方案12】:

          是的。此方法允许多个域。

          VB.NET

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

          【讨论】:

          • 这似乎违背了 X-Frame-Options 的目的,因为它允许任何站点进行框架。
          • 这个答案似乎可以作为解决方案的一个很好的基础,但它需要额外的逻辑,以便它仅在 request.urlreferer.tostring() 是您希望的来源之一时执行此代码允许。
          • 如果你这样做,你为什么还要使用 X-Frame-Options Header... 忽略它
          猜你喜欢
          • 2016-12-09
          • 2017-08-02
          • 2019-03-25
          • 1970-01-01
          • 2015-08-24
          • 1970-01-01
          • 2016-12-14
          • 2012-05-26
          • 2018-05-13
          相关资源
          最近更新 更多