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

我有一个 ASP.NET 4.0 IIS7.5站点,我需要使用 X-Frame-Options 头部的安全。

我还需要使我的网站页面,以 iframe 从我的同一个域,以及从我的 Facebook 应用程序。

目前,我的网站配置为:

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

当我使用 Chrome 或 Firefox 浏览我的 Facebook 页面时,我的网站页面(与我的 Facebook 页面一起被重组)显示正常,但是在 IE9下,我得到了错误:

“此页不能显示...”(因为 X-Frame_Options限制)。

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

如果只能定义一个域,那么 X-FRAME-OPTION作为一个新特性似乎有着根本性的缺陷。

233850 次浏览

来自 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语句完全没有问题。

一种可能的解决方案是使用 给你描述的“帧破坏”脚本

您只需要更改“ 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,您将不会担心恶意网站框架您的网页的安全问题。

不推荐使用 X-Frame-Options:

此特性已从 Web 标准中删除。虽然一些浏览器可能仍然支持它,但它正在被放弃的过程中。不要在旧的或新的项目中使用它。使用它的页面或 Web 应用程序可能随时中断。

现代的替代方法是使用 frame-ancestors指令的 Content-Security-Policy头文件,它与许多其他策略一起可以白名单列出哪些 URL 被允许在一个框架中承载您的页面。
frame-ancestors支持多个域甚至通配符,例如:

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

不幸的是,现在 Internet Explorer 并不完全支援内容保安政策

更新: MDN 已经删除了他们的弃用注释

frame-ancestors指令 过时了 X-Frame-Options报头。如果一个资源同时具有这两个策略,那么应该执行 frame-ancestors策略,而忽略 X-Frame-Options策略。

这个方法允许多个域。

VB.NET

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

不仅允许多个域,而且允许动态域的方法如何。

这里的用例是一个 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。

您可以利用这两个事实服务器端:

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

Here we can dynamically allow domains based upon the parent domain. In this case, we ensure that the host ends in sharepoint.com keeping our site safe from clickjacking.

我很想听听关于这种方法的反馈意见。

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

巫师。
提供的答案是不完整的。

首先,如前所述,您不能添加多个 allow-from 主机,这是不受支持的。
其次,您需要从 HTTP 引用程序动态提取该值,这意味着您不能将该值添加到 Web.config,因为它并不总是相同的值。

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

这可以在 ASP.NET 中通过编写一个 HTTP 模块来完成,该模块可以在每个请求上运行,并根据请求的引用为每个响应附加一个 http 头。对于 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

You need to register the context_EndRequest function in the HTTP-module Init function.

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




}

接下来,您需要将该模块添加到应用程序中。 You can either do this programmatically in Global.asax by overriding the Init function of the HttpApplication, like this:

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>

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

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

"[DefaultNameSpace.Namespace].Class, Assembly"

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

As per the MDN 规格, X-Frame-Options: ALLOW-FROM is not supported in Chrome and support is unknown in Edge and Opera.

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

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

HTTP 头字段 X-帧-选项的 RFC 声明 X-Frame-Options 头部值中的“ ALLOW-FROM”字段只能包含一个域。不允许多个域。

RFC 建议解决这个问题。解决方案是在 iframe src url 中将域名指定为 url 参数。然后,托管 iframe src url 的服务器可以检查 url 参数中给出的域名。如果域名匹配有效域名列表,那么服务器可以发送 X-Frame-Options 头部的值: “ ALLOW-FROM domain-name”,其中域名是试图嵌入远程内容的域名。如果没有给出域名或者域名无效,那么 X-Frame-Options 头部可以以值“拒绝”的形式发送。

Strictly speaking no, you cant.

但是,您可以指定 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

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

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

例如:

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

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

这里是参考 link

在 next.config.js 中放入相同的代码

module.exports = {
async headers() {
return [
{
source: '/((?!embed).*)',
headers: [
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
}
]
}
];
}
}