X-Frame-Options Allow-From wielu domen

100

Mam witrynę ASP.NET 4.0 IIS7.5, którą muszę zabezpieczyć za pomocą nagłówka X-Frame-Options.

Muszę również umożliwić wyświetlanie ramek iframe na moich stronach witryny z mojej samej domeny, a także z mojej aplikacji Facebook.

Obecnie mam skonfigurowaną witrynę z witryną o nazwie:

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

Kiedy przeglądałem moją stronę na Facebooku w przeglądarce Chrome lub Firefox, moje strony (które są w ramce iframe z moją stroną na Facebooku) są wyświetlane poprawnie, ale w IE9 pojawia się błąd:

„nie można wyświetlić tej strony…” (z powodu X-Frame_Optionsograniczenia).

Jak ustawić X-Frame-Options: ALLOW-FROMobsługę więcej niż jednej domeny?

X-FRAME-OPTION Bycie nową funkcją wydaje się zasadniczo wadliwe, jeśli można zdefiniować tylko jedną domenę.

user1340663
źródło
2
Wydaje się, że jest to znane ograniczenie: owasp.org/index.php/ ...
Pierre Ernst

Odpowiedzi:

109

X-Frame-Optionsjest przestarzałe. Z MDN :

Ta funkcja została usunięta ze standardów internetowych. Chociaż niektóre przeglądarki mogą nadal go obsługiwać, jest w trakcie usuwania. Nie używaj go w starych lub nowych projektach. Strony lub aplikacje internetowe, które go używają, mogą się zepsuć w dowolnym momencie.

Nowoczesną alternatywą jest Content-Security-Policynagłówek, który wraz z wieloma innymi zasadami może zawierać białą listę adresów URL, które mogą hostować twoją stronę w ramce, przy użyciu frame-ancestorsdyrektywy.
frame-ancestorsobsługuje wiele domen, a nawet symbole wieloznaczne, na przykład:

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

Niestety, na razie Internet Explorer nie obsługuje w pełni Content-Security-Policy .

UPDATE: MDN usunął swój komentarz dotyczący wycofania. Oto podobny komentarz z poziomu polityki bezpieczeństwa treści W3C

frame-ancestorsDyrektywa obsoletes ten X-Frame-Optionsnagłówek. Jeśli zasób ma obie zasady, frame-ancestorsPOWINNY one być egzekwowane, a X-Frame-Optionszasady POWINNY być ignorowane.

Kobi
źródło
14
frame-ancestors są oznaczone jako „eksperymentalne API i nie powinny być używane w kodzie produkcyjnym” na MDN. + X-Frame-Options nie są przestarzałe, ale „niestandardowe”, ale „są szeroko obsługiwane i mogą być używane w połączeniu z CSP”
Jonathan Muller
1
@JonathanMuller - Sformułowanie dotyczące X-Frame-Optionszmieniło się i jest teraz mniej surowe. Dobrze, że użycie specyfikacji, która nie została sfinalizowana, jest ryzykowne. Dzięki!
Kobi
2
Nie mogę znaleźć przestarzałego ostrzeżenia na MDN. Czy Mozilla zmieniła zdanie?
thomaskonrad
2
@ to0om - Dzięki! Zaktualizowałem odpowiedź o kolejny komentarz. Mogłem odpowiedzieć zbyt mocno. Tak czy inaczej, X-Frame-Optionsnie obsługuje wielu źródeł.
Kobi
4
@Kobi, myślę, że odpowiedź wymaga reorganizacji. Pierwsze zdanie mówi, że zgodnie z MDN jest to przestarzałe. Będzie mniej mylące, jeśli dodasz swoją aktualizację u góry (z pogrubionym napisem „UPDATE:”). Dzięki.
Kasun Gajasinghe,
39

Z RFC 7034 :

Symbole wieloznaczne lub listy służące do deklarowania wielu domen w jednej instrukcji ALLOW-FROM są niedozwolone

Więc,

Jak ustawić X-Frame-Options: ALLOW-FROM na obsługę więcej niż jednej domeny?

Nie możesz. Aby obejść ten problem, możesz użyć różnych adresów URL dla różnych partnerów. Dla każdego adresu URL możesz użyć jego własnej X-Frame-Optionswartości. Na przykład:

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

Dla yousite.commożna po prostu używać X-Frame-Options: deny.

BTW , na razie Chrome (i wszystkie przeglądarki oparte na webkitach) w ogóle nie obsługuje ALLOW-FROM instrukcji.

vbo
źródło
1
Wygląda na to, że webkit obsługuje teraz ALLOW-FROMkorzystanie z podanego linku.
Jimi
3
@Jimi Nie, nie ma - ostatni komentarz do odnośnika, o którym mowa, mówi, że zamiast tego należy użyć polityki CSP. Ta opcja nadal nie działa w Chrome.
NickG,
9

Nekromancja.
Podane odpowiedzi są niekompletne.

Po pierwsze, jak już powiedziano, nie można dodać wielu hostów z zezwoleniem, to nie jest obsługiwane.
Po drugie, musisz dynamicznie wyodrębnić tę wartość ze strony odsyłającej HTTP, co oznacza, że ​​nie możesz dodać wartości do Web.config, ponieważ nie zawsze jest to ta sama wartość.

Konieczne będzie wykonanie wykrywania przeglądarki, aby uniknąć dodawania zezwalania z przeglądarki Chrome (powoduje to błąd w konsoli debugowania, która może szybko zapełnić konsolę lub spowolnić aplikację). Oznacza to również, że musisz zmodyfikować wykrywanie przeglądarki ASP.NET, ponieważ błędnie identyfikuje ona Edge jako Chrome.

Można to zrobić w ASP.NET, pisząc moduł HTTP, który działa na każde żądanie, który dołącza nagłówek http dla każdej odpowiedzi, w zależności od strony odsyłającej żądania. W przypadku Chrome musi dodać politykę bezpieczeństwa treści.

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

Musisz zarejestrować funkcję context_EndRequest w funkcji Init modułu 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


}

Następnie musisz dodać moduł do swojej aplikacji. Możesz to zrobić programowo w Global.asax, zastępując funkcję Init HttpApplication, na przykład:

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

        }


    }


}

lub możesz dodać wpisy do Web.config, jeśli nie jesteś właścicielem kodu źródłowego aplikacji:

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

Wpis w system.webServer jest przeznaczony dla IIS7 +, drugi w system.web dla IIS 6.
Należy pamiętać, że należy ustawić runAllManagedModulesForAllRequests na true, aby działał poprawnie.

Ciąg znaków w typie ma format "Namespace.Class, Assembly". Zauważ, że jeśli napiszesz swój zestaw w VB.NET zamiast w C #, VB utworzy domyślną przestrzeń nazw dla każdego projektu, więc twój ciąg będzie wyglądał jak

"[DefaultNameSpace.Namespace].Class, Assembly"

Jeśli chcesz uniknąć tego problemu, napisz bibliotekę DLL w języku C #.

Stefan Steiger
źródło
Myślę, że możesz chcieć usunąć „vmswisslife” i „vmraiffeisen” z odpowiedzi, aby nie uzyskała fałszywych korelacji.
quetzalcoatl
@quetzalcoatl: Zostawiłem je jako przykład, to nie jest przeoczenie, nie jest to w żaden sposób poufne. Ale prawda, może lepiej je usuń. Gotowe.
Stefan Steiger
7

Co powiesz na podejście, które nie tylko zezwala na wiele domen, ale także na domeny dynamiczne.

Przykładem użycia jest tutaj część aplikacji Sharepoint, która ładuje naszą witrynę w Sharepoint za pośrednictwem iframe. Problem polega na tym, że sharepoint ma dynamiczne subdomeny, takie jak https://yoursite.sharepoint.com . W przypadku IE musimy więc określić ALLOW-FROM https: //.sharepoint.com

Trudna sprawa, ale możemy to zrobić, znając dwa fakty:

  1. Kiedy element iframe jest ładowany, sprawdza poprawność opcji X-Frame tylko przy pierwszym żądaniu. Po załadowaniu elementu iframe możesz poruszać się po nim, a nagłówek nie będzie sprawdzany w kolejnych żądaniach.

  2. Ponadto, gdy ładowany jest element iframe, punktem odniesienia HTTP jest nadrzędny adres URL elementu iframe.

Możesz wykorzystać te dwa fakty po stronie serwera. W Rubim używam następującego kodu:

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

Tutaj możemy dynamicznie zezwalać na domeny oparte na domenie nadrzędnej. W takim przypadku zapewniamy, że host kończy się na sharepoint.com, chroniąc naszą witrynę przed przechwytywaniem kliknięć.

Bardzo chciałbym poznać opinie na temat tego podejścia.

Peter P.
źródło
2
Uwaga: to się psuje, jeśli hostem jest „fakesharepoint.com”. Wyrażenie regularne powinno wyglądać następująco:/\.sharepoint\.com$/
nitsas
@StefanSteiger to prawda, ale Chrome również nie ma tego problemu. Przeglądarki Chrome i inne przeglądarki zgodne ze standardami są zgodne z nowszym modelem zasad bezpieczeństwa treści (CSP).
Peter P.,
4

Zgodnie ze specyfikacjami MDN , X-Frame-Options: ALLOW-FROMnie jest obsługiwany w Chrome, a wsparcie jest nieznane w Edge i Opera.

Content-Security-Policy: frame-ancestorszastępuje X-Frame-Options(zgodnie ze specyfikacją W3 ), ale frame-ancestorsma ograniczoną kompatybilność. Zgodnie z tymi specyfikacjami MDN nie jest obsługiwany w IE ani Edge.

Andrzej
źródło
1

Dokument RFC dla pola nagłówka HTTP X-Frame-Options stwierdza, że ​​pole „ALLOW-FROM” w wartości nagłówka X-Frame-Options może zawierać tylko jedną domenę. Wiele domen jest niedozwolonych.

Dokument RFC sugeruje obejście tego problemu. Rozwiązaniem jest określenie nazwy domeny jako parametru url w adresie url src elementu iframe. Serwer, który obsługuje adres URL źródła iframe, może następnie sprawdzić nazwę domeny podaną w parametrach adresu URL. Jeśli nazwa domeny pasuje do listy prawidłowych nazw domen, serwer może wysłać nagłówek X-Frame-Options z wartością: „ALLOW-FROM nazwa-domeny”, gdzie nazwa domeny to nazwa domeny, która próbuje osadzić zdalną zawartość. Jeśli nazwa domeny nie została podana lub jest nieprawidłowa, wówczas nagłówek X-Frame-Options może zostać wysłany z wartością: „deny”.

Nadir Latif
źródło
1

Ściśle mówiąc nie, nie możesz.

Możesz jednak określić, X-Frame-Options: mysite.coma zatem zezwolić na subdomain1.mysite.comi subdomain2.mysite.com. Ale tak, to wciąż jedna domena. Jest jakieś obejście tego problemu, ale myślę, że najłatwiej jest to przeczytać bezpośrednio w specyfikacjach RFC: https://tools.ietf.org/html/rfc7034

Warto również zwrócić uwagę, że frame-ancestordyrektywa nagłówka Content-Security-Policy (CSP) przestaje obowiązywać X-Frame-Options. Przeczytaj więcej tutaj .

Jim Aho
źródło
0

Niezupełnie to samo, ale może działać w niektórych przypadkach: istnieje inna opcja, ALLOWALLktóra skutecznie usunie ograniczenie, co może być przyjemne w przypadku środowisk testowych / przedprodukcyjnych

Willyfrog
źródło
Nie jest to udokumentowane w MDN.
andig
0

Musiałem dodać X-Frame-Options dla IE i Content-Security-Policy dla innych przeglądarek. Więc zrobiłem coś takiego jak śledzenie.

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
źródło
-4

Jednym z możliwych obejść byłoby użycie skryptu „frame-breaker”, jak opisano tutaj

Wystarczy zmienić instrukcję „if”, aby sprawdzić dozwolone domeny.

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

Myślę, że to obejście byłoby bezpieczne. ponieważ przy wyłączonym javascript nie będziesz mieć obaw o bezpieczeństwo w związku ze złośliwą witryną umieszczającą ramkę na Twojej stronie.

SinaX
źródło
1
To nie zadziała z powodu tych samych zasad pochodzenia podczas wywoływania top.location.
Eric R.
-8

TAK. Ta metoda dopuszczała wiele domen.

VB.NET

response.headers.add("X-Frame-Options", "ALLOW-FROM " & request.urlreferer.tostring())
user4778040
źródło
9
Wydaje się, że jest to sprzeczne z celem opcji X-Frame, ponieważ umożliwia tworzenie ramek w dowolnej witrynie.
Andrey Shchekin
5
Wydaje się, że ta odpowiedź może być dobrą podstawą jako rozwiązanie, ale wymaga dodatkowej logiki, aby wykonywał ten kod tylko wtedy, gdy request.urlreferer.tostring () jest jednym z źródeł, na które chcesz zezwolić.
Zergleb
Jeśli to robisz, dlaczego w ogóle używasz nagłówka opcji X-Frame ... po prostu zignoruj ​​to
vs4vijay