[asp.net] X-Frame-Options Allow-From 여러 도메인

X-Frame-Options 헤더를 사용하여 보호해야하는 ASP.NET 4.0 IIS7.5 사이트가 있습니다.

또한 내 사이트 페이지가 내 페이스 북 앱뿐만 아니라 내 동일한 도메인에서 iframed되도록해야합니다.

현재 내 사이트는 다음 사이트로 구성되어 있습니다.

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 하나의 도메인 만 정의 할 수 있다면 새로운 기능이 근본적으로 결함이있는 것처럼 보입니다.



답변

X-Frame-Options더 이상 사용되지 않습니다. 에서 MDN :


이 기능은 웹 표준에서 제거되었습니다. 일부 브라우저는 여전히 지원할 수 있지만 삭제 중입니다. 이전 또는 새 프로젝트에서 사용하지 마십시오. 이를 사용하는 페이지 또는 웹 앱은 언제든지 중단 될 수 있습니다.

현대적인 대안은 Content-Security-Policy다른 많은 정책과 함께 frame-ancestors지시문을 사용하여 프레임에서 페이지를 호스팅 할 수있는 URL을 허용 목록에 추가 할 수 있는 헤더 입니다.
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정책을 무시해야합니다 (SHOULD).


답변

에서 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.

BTW , 현재 Chrome (및 모든 웹킷 기반 브라우저) ALLOW-FROM 문을 전혀 지원하지 않습니다 .


답변

네크 로맨싱.
제공된 답변이 불완전합니다.

첫째, 이미 말했듯이 지원되지 않는 여러 허용 호스트를 추가 할 수 없습니다.
둘째, HTTP 리퍼러에서 해당 값을 동적으로 추출해야합니다. 즉, 항상 같은 값이 아니기 때문에 Web.config에 값을 추가 할 수 없습니다.

브라우저가 Chrome 일 때 allow-from을 추가하지 않도록 브라우저 감지를 수행해야합니다 (디버그 콘솔에 오류가 발생하여 콘솔이 빠르게 채워지거나 애플리케이션 속도가 느려질 수 있음). 또한 Edge를 Chrome으로 잘못 식별하므로 ASP.NET 브라우저 감지를 수정해야합니다.

이것은 모든 요청에서 실행되는 HTTP 모듈을 작성하여 ASP.NET에서 수행 할 수 있으며 요청의 리퍼러에 따라 모든 응답에 http-header를 추가합니다. 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 

HTTP 모듈 Init 함수에 context_EndRequest 함수를 등록해야합니다.

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


}

다음으로 애플리케이션에 모듈을 추가해야합니다. 다음과 같이 HttpApplication의 Init 함수를 재정 의하여 Global.asax에서 프로그래밍 방식으로이 작업을 수행 할 수 있습니다.

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로 설정해야합니다.

type의 문자열은 형식 "Namespace.Class, Assembly"입니다. C # 대신 VB.NET에서 어셈블리를 작성하는 경우 VB는 각 프로젝트에 대한 기본 네임 스페이스를 생성하므로 문자열은 다음과 같습니다.

"[DefaultNameSpace.Namespace].Class, Assembly"

이 문제를 피하려면 DLL을 C #으로 작성하십시오.


답변

여러 도메인을 허용 할뿐만 아니라 동적 도메인을 허용하는 접근 방식은 어떻습니까?

여기서 사용 사례는 iframe을 통해 Sharepoint 내에서 사이트를로드하는 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

여기에서 부모 도메인을 기반으로 도메인을 동적으로 허용 할 수 있습니다. 이 경우 호스트가 sharepoint.com에서 끝나는 것을 확인하여 사이트를 클릭 재킹으로부터 안전하게 보호합니다.

이 접근 방식에 대한 피드백을 듣고 싶습니다.


답변

당으로 MDN 사양 , X-Frame-Options: ALLOW-FROM크롬에서 지원 및 지원 가장자리와 오페라에서 알 수 없습니다.

Content-Security-Policy: frame-ancestorsX-Frame-Options( 이 W3 사양에 따라 ) 무시 되지만 frame-ancestors호환성이 제한됩니다. 이러한 MDN 사양 에 따라 IE 또는 Edge에서는 지원되지 않습니다.


답변

HTTP 헤더 필드 X-Frame-Options에 대한 RFC 에 따르면 X-Frame-Options 헤더 값의 “ALLOW-FROM”필드는 하나의 도메인 만 포함 할 수 있습니다. 여러 도메인은 허용되지 않습니다.

RFC는이 문제에 대한 해결 방법을 제안합니다. 해결책은 도메인 이름을 iframe src url에 url 매개 변수로 지정하는 것입니다. 그런 다음 iframe src URL을 호스팅하는 서버는 url 매개 변수에 지정된 도메인 이름을 확인할 수 있습니다. 도메인 이름이 유효한 도메인 이름 목록과 일치하면 서버는 “ALLOW-FROM domain-name”값과 함께 X-Frame-Options 헤더를 보낼 수 있습니다. 여기서 domain name은 시도하려는 도메인의 이름입니다. 원격 콘텐츠를 포함합니다. 도메인 이름이 지정되지 않았거나 유효하지 않은 경우 X-Frame-Options 헤더를 “deny”값으로 전송할 수 있습니다.


답변

엄격히 말하면 안됩니다.

그러나 지정할 수 X-Frame-Options: mysite.com있으므로 subdomain1.mysite.comsubdomain2.mysite.com. 그러나 예, 그것은 여전히 ​​하나의 도메인입니다. 이에 대한 해결 방법이 있지만 RFC 사양 ( https://tools.ietf.org/html/rfc7034) 에서 직접 읽는 것이 가장 쉬운 방법이라고 생각합니다.

Content-Security-Policy (CSP) 헤더의 frame-ancestor지시문이 X-Frame-Options를 사용 하지 않는다는 점도 지적 할 가치가 있습니다. 여기에서 자세한 내용을 읽어보십시오 .