X-Frame-Options Cho phép-Từ nhiều miền


99

Tôi có một trang ASP.NET 4.0 IIS7.5 mà tôi cần được bảo mật bằng cách sử dụng tiêu đề X-Frame-Options.

Tôi cũng cần kích hoạt các trang trong trang web của mình được iframed từ cùng một miền của tôi cũng như từ ứng dụng facebook của tôi.

Hiện tại, tôi đã định cấu hình trang web của mình với một trang đứng đầu:

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

Khi tôi xem trang Facebook của mình bằng Chrome hoặc Firefox, các trang trên trang web của tôi (được ghép trong khung với trang facebook của tôi) hiển thị ổn, nhưng dưới IE9, tôi gặp lỗi:

"không thể hiển thị trang này…" (vì X-Frame_Optionsgiới hạn).

Làm cách nào để đặt X-Frame-Options: ALLOW-FROMhỗ trợ nhiều hơn một tên miền?

X-FRAME-OPTION là một tính năng mới có vẻ thiếu sót về cơ bản nếu chỉ có thể xác định một miền duy nhất.


2
Đây dường như là một hạn chế đã biết: owasp.org/index.php/…
Pierre Ernst

Câu trả lời:


108

X-Frame-Optionskhông được dùng nữa. Từ MDN :

Tính năng này đã bị xóa khỏi tiêu chuẩn Web. Mặc dù một số trình duyệt có thể vẫn hỗ trợ nó, nhưng nó đang trong quá trình bị loại bỏ. Không sử dụng nó trong các dự án cũ hoặc mới. Các trang hoặc ứng dụng Web sử dụng nó có thể bị hỏng bất cứ lúc nào.

Giải pháp thay thế hiện đại là Content-Security-Policytiêu đề, cùng với nhiều chính sách khác có thể liệt kê trắng những URL nào được phép lưu trữ trang của bạn trong một khung, bằng cách sử dụng lệnh frame-ancestors.
frame-ancestorshỗ trợ nhiều miền và thậm chí cả ký tự đại diện, ví dụ:

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

Rất tiếc, hiện tại, Internet Explorer không hỗ trợ đầy đủ Nội dung-Bảo mật-Chính sách .

CẬP NHẬT: MDN đã xóa nhận xét không dùng nữa của họ. Đây là nhận xét tương tự từ Cấp chính sách bảo mật nội dung của W3C

Các frame-ancestorschỉ thị obsoletes các X-Frame-Optionstiêu đề. Nếu tài nguyên có cả hai chính sách, frame-ancestorschính sách NÊN được thực thi và X-Frame-Optionschính sách NÊN bị bỏ qua.


14
khung-tổ tiên được đánh dấu là "API thử nghiệm và không được sử dụng trong mã sản xuất" trên MDN. + X-Frame-Options không bị phản đối nhưng "phi tiêu chuẩn" nhưng "được hỗ trợ rộng rãi và có thể được sử dụng kết hợp với CSP"
Jonathan Muller

1
@JonathanMuller - Từ ngữ trên X-Frame-Optionsđã thay đổi và bây giờ ít nghiêm trọng hơn. Đó là một điểm tốt khi sử dụng một thông số kỹ thuật chưa được hoàn thiện. Cảm ơn!
Kobi

2
Tôi không thể tìm thấy cảnh báo đã sửa trên MDN nữa. Mozilla có thay đổi quan điểm của họ không?
thomaskonrad

2
@ to0om - Cảm ơn! Tôi đã cập nhật câu trả lời với một bình luận khác. Tôi có thể đã quá mạnh mẽ trong câu trả lời của mình. Dù bằng cách nào, X-Frame-Optionskhông hỗ trợ nhiều nguồn.
Kobi

4
@Kobi, tôi nghĩ câu trả lời cần được sắp xếp lại. Câu đầu tiên nói rằng điều này không được chấp nhận theo MDN. Sẽ ít gây hiểu lầm hơn nếu bạn thêm bản cập nhật của mình ở trên cùng (với "UPDATE:" được tô màu đậm). Cảm ơn.
Kasun Gajasinghe

39

Từ RFC 7034 :

Không được phép sử dụng ký tự đại diện hoặc danh sách để khai báo nhiều miền trong một câu lệnh CHO PHÉP

Vì thế,

Làm cách nào để đặt X-Frame-Options: ALLOW-FROM để hỗ trợ nhiều hơn một tên miền?

Bạn không thể. Để giải quyết vấn đề, bạn có thể sử dụng các URL khác nhau cho các đối tác khác nhau. Đối với mỗi URL, bạn có thể sử dụng X-Frame-Optionsgiá trị riêng của nó . Ví dụ:

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

Đối với yousite.combạn chỉ có thể sử dụng X-Frame-Options: deny.

BTW , hiện tại Chrome (và tất cả các trình duyệt dựa trên webkit) hoàn toàn không hỗ trợ các ALLOW-FROM câu lệnh.


1
Có vẻ như webkit hiện hỗ trợ ALLOW-FROMsử dụng liên kết bạn đã cung cấp.
Jimi

3
@Jimi Không, không - nhận xét cuối cùng về liên kết được đề cập, nói rằng bạn cần sử dụng chính sách CSP để thay thế. Tùy chọn này vẫn không hoạt động trong Chrome.
NickG

9

Thăng bằng.
Các câu trả lời được cung cấp không đầy đủ.

Đầu tiên, như đã nói, bạn không thể thêm nhiều máy chủ allow-from, điều đó không được hỗ trợ.
Thứ hai, bạn cần trích xuất động giá trị đó từ liên kết giới thiệu HTTP, có nghĩa là bạn không thể thêm giá trị vào Web.config, vì nó không phải lúc nào cũng có cùng giá trị.

Bạn sẽ cần phải phát hiện trình duyệt để tránh thêm quyền từ khi trình duyệt là Chrome (nó tạo ra lỗi trên bảng điều khiển gỡ lỗi, có thể nhanh chóng lấp đầy bảng điều khiển hoặc làm cho ứng dụng chạy chậm). Điều đó cũng có nghĩa là bạn cần phải sửa đổi tính năng phát hiện trình duyệt ASP.NET, vì nó xác định sai Edge là Chrome.

Điều này có thể được thực hiện trong ASP.NET bằng cách viết một mô-đun HTTP chạy trên mọi yêu cầu, mô-đun này gắn thêm tiêu đề http cho mọi phản hồi, tùy thuộc vào liên kết giới thiệu của yêu cầu. Đối với Chrome, nó cần thêm Nội dung-Bảo mật-Chính sách.

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

Bạn cần đăng ký hàm context_EndRequest trong hàm HTTP-module Init.

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


}

Tiếp theo, bạn cần thêm mô-đun vào ứng dụng của mình. Bạn có thể thực hiện việc này theo lập trình trong Global.asax bằng cách ghi đè hàm Init của HttpApplication, như sau:

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

        }


    }


}

hoặc bạn có thể thêm các mục nhập vào Web.config nếu bạn không sở hữu mã nguồn ứng dụng:

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

Mục nhập trong system.webServer dành cho IIS7 +, mục nhập còn lại trong system.web dành cho IIS 6.
Lưu ý rằng bạn cần đặt runAllManagedModulesForAllRequests thành true để nó hoạt động bình thường.

Chuỗi trong loại có định dạng "Namespace.Class, Assembly". Lưu ý rằng nếu bạn viết hợp ngữ của mình bằng VB.NET thay vì C #, VB sẽ tạo không gian tên mặc định cho mỗi dự án, vì vậy chuỗi của bạn sẽ giống như

"[DefaultNameSpace.Namespace].Class, Assembly"

Nếu bạn muốn tránh vấn đề này, hãy viết DLL trong C #.


Tôi nghĩ bạn có thể muốn xóa 'vmswisslife' và 'vmraiffeisen' khỏi câu trả lời để nó không nhận được tương quan sai.
quetzalcoatl

@quetzalcoatl: Tôi để chúng ở đó làm ví dụ, nó không phải là một sự giám sát, nó không bí mật theo bất kỳ cách nào. Nhưng đúng, có lẽ tốt hơn nên loại bỏ chúng. Làm xong.
Stefan Steiger

7

Làm thế nào về một cách tiếp cận không chỉ cho phép nhiều miền mà còn cho phép các miền động.

Trường hợp sử dụng ở đây là với một phần ứng dụng Sharepoint tải trang web của chúng tôi bên trong Sharepoint thông qua iframe. Vấn đề là sharepoint có các tên miền phụ động như https://yoursite.sharepoint.com . Vì vậy, đối với IE, chúng ta cần chỉ định CHO PHÉP TỪ https: //.sharepoint.com

Công việc khó khăn, nhưng chúng ta có thể hoàn thành nó khi biết hai sự thật:

  1. Khi một iframe tải, nó chỉ xác thực X-Frame-Options theo yêu cầu đầu tiên. Khi iframe được tải, bạn có thể điều hướng trong iframe và tiêu đề không được chọn trong các yêu cầu tiếp theo.

  2. Ngoài ra, khi một iframe được tải, trình tham chiếu HTTP là url iframe chính.

Bạn có thể tận dụng hai phía máy chủ dữ kiện này. Trong ruby, tôi đang sử dụng mã sau:

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

Tại đây, chúng tôi có thể cho phép động các miền dựa trên miền mẹ. Trong trường hợp này, chúng tôi đảm bảo rằng máy chủ lưu trữ kết thúc bằng sharepoint.com, giữ cho trang web của chúng tôi an toàn khỏi bị kích chuột.

Tôi muốn nghe phản hồi về cách tiếp cận này.


2
Thận trọng: điều này sẽ bị lỗi nếu máy chủ lưu trữ là "fakesharepoint.com". Hệ số regex phải là:/\.sharepoint\.com$/
nitsas

@StefanSteiger đúng, nhưng Chrome cũng không gặp sự cố này. Chrome và các trình duyệt tuân thủ các tiêu chuẩn khác tuân theo mô hình Chính sách bảo mật nội dung (CSP) mới hơn.
Peter P.


1

RFC cho trường tiêu đề HTTP X-Frame-Options tuyên bố rằng trường "CHO ​​PHÉP TỪ" trong giá trị tiêu đề X-Frame-Options chỉ có thể chứa một miền. Nhiều miền không được phép.

RFC đề xuất một giải pháp cho vấn đề này. Giải pháp là chỉ định tên miền làm tham số url trong iframe src url. Sau đó, máy chủ lưu trữ iframe src url có thể kiểm tra tên miền được cung cấp trong các tham số url. Nếu tên miền khớp với danh sách tên miền hợp lệ, thì máy chủ có thể gửi tiêu đề X-Frame-Options với giá trị: "CHO ​​PHÉP TỪ tên miền", trong đó tên miền là tên miền đang cố gắng nhúng nội dung từ xa. Nếu tên miền không được cung cấp hoặc không hợp lệ, thì tiêu đề X-Frame-Options có thể được gửi với giá trị: "từ chối".


1

Nói chính xác là không, bạn không thể.

Tuy nhiên, bạn có thể chỉ định X-Frame-Options: mysite.comvà do đó cho phép subdomain1.mysite.comsubdomain2.mysite.com. Nhưng có, đó vẫn là một miền. Tình cờ có một số cách giải quyết cho vấn đề này, nhưng tôi nghĩ dễ nhất là đọc trực tiếp tại thông số kỹ thuật RFC: https://tools.ietf.org/html/rfc7034

Cũng cần phải chỉ ra rằng frame-ancestorchỉ thị của tiêu đề Nội dung-Bảo mật-Chính sách (CSP) đã ngăn cản X-Frame-Options. Đọc thêm tại đây .


0

Không hoàn toàn giống nhau, nhưng có thể hoạt động trong một số trường hợp: có một tùy chọn khác ALLOWALLsẽ loại bỏ hạn chế một cách hiệu quả, đây có thể là một điều tốt cho môi trường thử nghiệm / tiền sản xuất


Điều này không được ghi lại trên MDN.
andig

0

Tôi đã phải thêm X-Frame-Options cho IE và Content-Security-Policy cho các trình duyệt khác. Vì vậy, tôi đã làm một cái gì đó như sau.

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

Một giải pháp khả thi là sử dụng tập lệnh "frame-breaker" như được mô tả ở đây

Bạn chỉ cần thay đổi câu lệnh "if" để kiểm tra các miền được phép của mình.

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

Tôi nghĩ rằng giải pháp này sẽ an toàn. bởi vì với javascript không được kích hoạt, bạn sẽ không phải lo lắng về bảo mật về một trang web độc hại đóng khung trang của bạn.


1
Điều này sẽ không hoạt động do cùng một chính sách nguồn gốc khi gọi top.location.
Eric R.

-8

ĐÚNG. Phương pháp này cho phép nhiều miền.

VB.NET

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

9
Điều này dường như đánh bại mục đích của X-Frame-Options vì nó cho phép bất kỳ trang web nào đóng khung.
Andrey Shchekin

5
Câu trả lời này có vẻ như nó có thể là một cơ sở tốt như một giải pháp nhưng nó cần thêm logic để nó chỉ thực thi mã này nếu request.urlreferer.tostring () là một trong những nguồn gốc mà bạn muốn cho phép.
Zergleb

Nếu bạn đang làm điều này, tại sao bạn thậm chí sử dụng X-Frame-Options Tiêu đề ... chỉ cần bỏ qua nó
vs4vijay
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.