X-Frame-Options Allow-From нескольких доменов

100

У меня есть сайт ASP.NET 4.0 IIS7.5, который мне нужно защитить с помощью заголовка X-Frame-Options.

Мне также нужно включить iframe для страниц моего сайта из моего домена и из моего приложения 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 новая функция кажется в корне ошибочной, если можно определить только один домен.

пользователь1340663
источник
2
Кажется, это известное ограничение: owasp.org/index.php/…
Пьер Эрнст

Ответы:

109

X-Frame-Optionsустарела. Из MDN :

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

Современная альтернатива - это Content-Security-Policyзаголовок, который наряду со многими другими политиками может заносить в белый список URL-адреса, которым разрешено размещать вашу страницу во фрейме, с помощью frame-ancestorsдирективы.
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
frame-ancestors отмечен как «экспериментальный API и не должен использоваться в производственном коде» на MDN. + X-Frame-Options не является устаревшим, но «нестандартный», но «широко поддерживается и может использоваться вместе с CSP»
Джонатан Мюллер
1
@JonathanMuller - Формулировка X-Frame-Optionsизменилась и стала менее суровой. Хорошо, что использовать еще не завершенную спецификацию рискованно. Спасибо!
Коби
2
Я больше не могу найти устаревшее предупреждение на MDN. Изменила ли Mozilla свое мнение?
thomaskonrad
2
@ to0om - Спасибо! Я обновил ответ другим комментарием. Возможно, я слишком настойчиво ответил. В любом случае X-Frame-Optionsне поддерживает несколько источников.
Коби
4
@Kobi, я думаю, ответ нужно реорганизовать. В самом первом предложении говорится, что это устарело согласно MDN. Будет меньше вводить в заблуждение, если вы добавите свое обновление вверху (жирным шрифтом «UPDATE:»). Спасибо.
Kasun Gajasinghe
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 инструкции.

vbo
источник
1
Похоже, что теперь webkit поддерживает ALLOW-FROMиспользование предоставленной вами ссылки.
Джими
3
@Jimi Нет, это не так - последний комментарий к рассматриваемой ссылке говорит, что вместо этого вам нужно использовать политику CSP. Эта опция по-прежнему не работает в Chrome.
NickG
9

Некромантинг.
Предоставленные ответы неполные.

Во-первых, как уже было сказано, вы не можете добавить несколько разрешенных хостов, это не поддерживается.
Во-вторых, вам нужно динамически извлекать это значение из HTTP-реферера, что означает, что вы не можете добавить значение в Web.config, потому что это не всегда одно и то же значение.

Будет необходимо выполнить обнаружение браузера, чтобы избежать добавления разрешения, когда браузер работает в Chrome (это вызывает ошибку в консоли отладки, которая может быстро заполнить консоль или замедлить работу приложения). Это также означает, что вам нужно изменить определение браузера 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 

Вам необходимо зарегистрировать функцию 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: Я оставил их там в качестве примера, это не недосмотр, это никоим образом не конфиденциально. Но правда, может лучше их убрать. Готово.
Stefan Steiger
7

Как насчет подхода, который позволяет не только использовать несколько доменов, но и динамические домены.

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

Дело непростое, но мы можем его решить, зная два факта:

  1. Когда загружается iframe, он проверяет параметры X-Frame только при первом запросе. После загрузки 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$/
Регулярное
@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 domain-name», где имя домена - это имя домена, который пытается встроить удаленный контент. Если имя домена не указано или недействительно, то заголовок 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
jbmyid
источник
-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 у вас не будет проблем с безопасностью, связанных с вредоносным веб-сайтом, который обрамляет вашу страницу.

SinaX
источник
1
Это не сработает из-за той же политики происхождения при вызове top.location.
Эрик Р.
-8

ДА. Этот метод позволяет использовать несколько доменов.

VB.NET

response.headers.add("X-Frame-Options", "ALLOW-FROM " & request.urlreferer.tostring())
user4778040
источник
9
Похоже, что это противоречит цели X-Frame-Options, поскольку позволяет создавать фреймы для любого сайта.
Андрей Щекин
5
Этот ответ кажется хорошей основой в качестве решения, но для него нужна дополнительная логика, чтобы он выполнял этот код только в том случае, если request.urlreferer.tostring () является одним из источников, которые вы хотите разрешить.
Зерглеб
Если вы делаете это, почему вы даже используете заголовок X-Frame-Options ... просто игнорируйте его
vs4vijay