Параметри X-Frame Дозволити з кількох доменів


99

У мене є сайт ASP.NET 4.0 IIS7.5, який мені потрібно захистити за допомогою заголовка X-Frame-Options.

Мені також потрібно дозволити, щоб сторінки мого сайту отримували фрейми з мого того самого домену, а також із мого додатка facebook.

На даний момент у мене на сайті налаштовано сайт із назвою:

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

Коли я переглядав свою сторінку Facebook у Chrome або Firefox, сторінки моїх веб-сайтів (які мають фрейми з моєю сторінкою facebook) відображаються нормально, але згідно з IE9, я отримую помилку:

"цю сторінку неможливо відобразити ..." (через X-Frame_Optionsобмеження).

Як налаштувати X-Frame-Options: ALLOW-FROMпідтримку більш ніж одного домену?

X-FRAME-OPTION будучи новою функцією, здається принципово вадним, якщо можна визначити лише один домен.


2
Здається, це відоме обмеження: owasp.org/index.php/…
П'єр Ернст,

Відповіді:


108

X-Frame-Optionsзастаріло. З MDN :

Ця функція була вилучена із веб-стандартів. Хоча деякі браузери все ще можуть його підтримувати, він перебуває в стадії видалення. Не використовуйте його в старих або нових проектах. Сторінки або веб-програми, які використовують його, можуть зламатися в будь-який час.

Сучасна альтернатива - це Content-Security-Policyзаголовок, який поряд з багатьма іншими політиками може вносити в білий список, які URL-адреси дозволяють розміщувати вашу сторінку у кадрі, використовуючи frame-ancestorsдирективу.
frame-ancestorsпідтримує кілька доменів і навіть символи підстановки, наприклад:

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

На жаль, наразі Internet Explorer не повністю підтримує Політику щодо захисту вмісту .

ОНОВЛЕННЯ: MDN видалив коментарі щодо припинення їх використання. Ось подібний коментар від рівня політики безпеки вмісту W3C

frame-ancestorsДиректива скасовує дію в X-Frame-Optionsзаголовок. Якщо ресурс має обидві політики, frame-ancestorsполітику ПОВИННО застосувати, а X-Frame-Optionsполітику ПОВИННО ігнорувати.


14
предки фрейму позначені як "експериментальний API і не повинні використовуватися у виробничому коді" на MDN. + Параметри X-Frame не є застарілими, але "нестандартні", але "широко підтримуються і можуть використовуватися разом із CSP"
Джонатан Мюллер,

1
@JonathanMuller - Формулювання X-Frame-Optionsзмінено і зараз менш суворе. Хорошим моментом є те, що ризиковано використовувати специфікацію, яка не доопрацьована. Дякую!
Кобі

2
Я більше не можу знайти попереджене попередження на MDN. Чи змінила Мозілла свою думку?
thomaskonrad

2
@ to0om - Дякую! Відповідь я оновив іншим коментарем. Можливо, я відповів занадто сильно. У будь-якому випадку, X-Frame-Optionsне підтримує кілька джерел.
Кобі

4
@Kobi, я думаю, що відповідь потребує реорганізації. У першому реченні сказано, що це застаріло згідно з MDN. Це буде менш оманливим, якщо ви додасте своє оновлення вгорі (жирним кольором "ОНОВИТИ:"). Дякую.
Касун Гаджасінгхе

39

З RFC 7034 :

Підстановочні символи або списки для оголошення декількох доменів в одному операторі ALLOW-FROM заборонені

Так,

Як встановити параметри X-Frame: 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
Схоже, вебкіт тепер підтримує ALLOW-FROMпосилання, яке ви надали.
Джимі

3
@Jimi Ні, ні - останній коментар до відповідного посилання говорить про те, що натомість потрібно використовувати політику CSP. Ця опція досі не працює в Chrome.
NickG

9

Некромантування.
Надані відповіді неповні.

По-перше, як уже було сказано, ви не можете додавати кілька дозволів із хостів, це не підтримується.
По-друге, вам потрібно динамічно витягувати це значення із посилання HTTP, а це означає, що ви не можете додати значення до Web.config, оскільки це не завжди однакове значення.

Потрібно буде розпізнати браузер, щоб уникнути додавання дозволів, коли браузер є Chrome (це видає помилку на налагоджувальній консолі, яка може швидко заповнити консоль або зробити програму повільною). Це також означає, що вам потрібно змінити виявлення браузера ASP.NET, оскільки він помилково визначає Edge як Chrome.

Це можна зробити в ASP.NET, написавши HTTP-модуль, який запускається при кожному запиті, який додає заголовок http до кожної відповіді, залежно від реферала запиту. Для Chrome йому потрібно додати політику щодо захисту вмісту.

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

Вам потрібно зареєструвати функцію context_EndRequest у функції Init HTTP-модуля.

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


}

Далі вам потрібно додати модуль до своєї програми. Ви можете зробити це програмно в Global.asax, замінивши функцію Init в HttpApplication, наприклад:

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"

Якщо ви хочете уникнути цієї проблеми, напишіть DLL на C #.


Я думаю, ви можете видалити з відповіді „vmswisslife“ та „vmraiffeisen“, щоб він не отримав хибних кореляцій.
quetzalcoatl

@quetzalcoatl: Я залишив їх там як приклад, це не недогляд, це жодним чином не конфіденційно. Але правда, можливо, краще їх видалити. Зроблено.
Стефан Штайгер

7

Як щодо підходу, який дозволяє не тільки кілька доменів, але й динамічні домени.

Приклад використання тут - частина програми Sharepoint, яка завантажує наш сайт всередині Sharepoint за допомогою iframe. Проблема в тому, що sharepoint має динамічні субдомени, такі як https://yoursite.sharepoint.com . Отже, для IE нам потрібно вказати ALLOW-FROM https: //.sharepoint.com

Хитрий бізнес, але ми можемо зробити це, знаючи два факти:

  1. Коли iframe завантажується, він перевіряє параметри X-Frame лише за першим запитом. Після того, як iframe буде завантажено, ви зможете переміщатись по iframe, і заголовок не перевіряється при наступних запитах.

  2. Крім того, коли завантажується iframe, референтом HTTP є батьківська URL-адреса 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

@StefanSteiger це правильно, але Chrome також не стикається з цією проблемою. Chrome та інші браузери, що відповідають стандартам, відповідають новій моделі Політики безпеки вмісту (CSP).
Пітер П.

4

Відповідно до специфікацій MDN , X-Frame-Options: ALLOW-FROMвін не підтримується в Chrome, а підтримка невідома в Edge та Opera.

Content-Security-Policy: frame-ancestorsзамінює X-Frame-Options(відповідно до цієї специфікації W3 ), але frame-ancestorsмає обмежену сумісність. Відповідно до цих специфікацій MDN , це не підтримується в IE або Edge.


1

RFC для поля заголовка HTTP X-Frame-Options зазначає, що поле "ALLOW-FROM" у заголовку X-Frame-Options може містити лише один домен. Кілька доменів заборонені.

RFC пропонує вирішити цю проблему. Рішення полягає в тому, щоб вказати доменне ім’я як параметр URL-адреси в URL-адресі iframe src. Потім сервер, що розміщує URL-адресу iframe src, може перевірити доменне ім'я, вказане в параметрах url. Якщо доменне ім'я відповідає списку дійсних доменних імен, тоді сервер може надіслати заголовок X-Frame-Options зі значенням: "ALLOW-FROM доменне ім'я", де доменне ім'я - це ім'я домену, який намагається вбудувати віддалений вміст. Якщо ім'я домену не вказане або недійсне, тоді заголовок X-Frame-Options можна надіслати зі значенням: "deny".


1

Строго кажучи, ні, не можна.

Однак ви можете вказати X-Frame-Options: mysite.comі, отже, дозволити subdomain1.mysite.comі subdomain2.mysite.com. Але так, це все одно один домен. Тут трапляються певні обхідні шляхи, але, думаю, найпростіше це прочитати безпосередньо у специфікаціях RFC: https://tools.ietf.org/html/rfc7034

Варто також зазначити, що frame-ancestorдиректива заголовка Content-Security-Policy (CSP) застаріває X-Frame-Options. Детальніше читайте тут .


0

Не зовсім те саме, але в деяких випадках може працювати: є інший варіант, ALLOWALLякий ефективно усуне обмеження, що може бути приємно для середовищ тестування / попереднього виробництва


Це не задокументовано на MDN.
andig

0

Мені довелося додати X-Frame-Options для IE та 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 (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, оскільки дозволяє будь-якому веб-сайту створювати кадри.
Андрій Щекін

5
Ця відповідь здається, що це може бути хорошою базою як рішення, але їй потрібна додаткова логіка, щоб він виконував цей код лише у тому випадку, якщо request.urlreferer.tostring () є одним із джерел, яке ви хочете дозволити.
Церглеб

Якщо ви робите це, чому ви навіть використовуєте заголовок X-Frame-Options ... просто ігноруйте це
vs4vijay
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.