[WEB] ASP.NET MVC에 HTTPS 적용하기

2015. 6. 19. 16:07WEB

HTTPS란?

HTTPS는 Web을 구성하는 프로토콜 중 하나인 HTTP 프로토콜의 보안강화버전이라고 볼 수 있습니다. HTTP는 Hyper text transfer protocol의 약자로서 하이퍼텍스트를 통한 전송 규약을 말합니다. 즉, 서버와 브라우저 사이에 하이퍼텍스트 문서를 교환하고 전송하는데 사용되는 통신규약입니다.

여기서, 하이퍼텍스트 문서란 문서 내에 하이퍼링크를 통해 하이퍼텍스트 문서를 유기적으로 결합하고 참조할 수 있는 문서를 가리킵니다. 쉽게 말해 <a href=’’>링크</a>를 통해 문서가 유기적으로 연결된 문서가 하이퍼텍스트 문서라고 볼 수 있습니다. (href란 Hyper Reference의 약자)

이러한 HTTP프로토콜은 다양한 문서를 연결하고 보여주는 데에는 문제가 없으나, 중요한 정보를 전송하는데 있어 정보를 보호할 수 있는 수단이 전혀 제공되어 있지 않습니다. HTTPS는 이러한 HTTP의 단점을 보완하기 위한 프로토콜입니다.

HTTPS의 동작방식

HTTPS는 TCP/IP와 HTTP 프로토콜의 사이에 SSL(Secure Sockets Layer) 또는 TSL(Transport Layer Security) 프로토콜 계층을 제공함으로써 동작하게 됩니다. 구체적인 서버와 브라우저의 HTTPS 접속과정은 다음과 같습니다.

  1. 브라우저는 요청된 URL이 HTTPS Scheme을 가진다면, 443 포트로 서버에 접속하고 디지털 인증서를 내려 받습니다.
  2. 브라우저는 디지털 인증서의 서명기관을 검사합니다.
  3. 만약, 신뢰할 수 없는 서명기관으로 판단되면 사용자에게 ‘서명기관 신뢰여부’를 확인하는 대화상자를 보여줍니다.
    image
  4. 브라우저는 대칭키를 생성합니다. 그리고, 인증서에 포함된 공개키로 대칭키를 암호화하여 서버에 전달합니다.
  5. 서버는 브라우저에서 전달한 대칭키를 수신합니다.
  6. 이제 서버와 브라우저는 대칭키를 공유하므로, 세션이 유지되는 동안 대칭키를 이용하여 암호화된 통신을 합니다.

HTTPS 적용 순서

1. HTTPS인증서 구입 및 설치

  1. HTTPS인증서를 발행하는 서명기관으로부터 HTTPS인증서를 구입합니다.
  2. 서명기관에서 제공하는 설치방법에 따라 IIS에 인증서를 설치합니다.
  3. 설치 후에는 브라우저에서 해당 서버로 HTTPS를 통해 접속할 수 있어야 합니다.

2. ASP.NET MVC설정

1) HTTPS로 Redirect하도록 설정

사용자가 HTTP로 접근하더라도 HTTPS로 접속되도록 설정해야 합니다. 이를 위해 ASP.NET MVC에서는 RequireHttpsAttribute를 지원합니다. 이 Attribute를 Controller, Action에 적용함으로써 사용하실 수 있습니다.

개발 중에 RequireHttpsAttribute를 적용하면, 모든 개발자 PC의 IIS에 SSL설정을 해야 합니다. 모든 PC에 SSL설정을 하는 번거로운 방법 대신에 RequiredHttpsAttribute를 상속하여 개발 중일 때는 사용하지 않도록 하는 방법도 있습니다.

public class RequireHttpsConnectionFilter : RequireHttpsAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        if (filterContext.HttpContext.Request.IsLocal)
        {
            //Local환경에서는 HTTPS를 사용하지 않도록 구성
            return;
        }

        base.OnAuthorization(filterContext);
    }
}

RequireHttpsAttribute를 Action, Controller에 개별 적용하기 보다, Filter를 통해 한번에 전역 적용하실 수 도 있습니다.

public static class FilterConfig
{
    public void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //필터를 이용하여 전역적용
        filters.Add(new RequireHttpsConnectionFilter());
    }
}

2) Secure Cookie 설정

쿠키는 HTTP 프로토콜에서는 암호화되지 않고 전송되지만, HTTPS 프로토콜을 사용하는 순간 쿠키도 암호화되어 전송되게 됩니다. 즉, 사용하는 프로토콜에 따라 암호화 여부가 결정됩니다.

중요한 정보를 포함하는 쿠키는 HTTPS 프로토콜에서만 전송되도록 강제해야 합니다. 이를 위해 Secure Cookie를 사용할 수 있습니다.

ASP.NET에서는 해당 쿠키를 생성할 때 Secure 속성을 true로 설정하시면 됩니다.

Response.Cookies.Add(new HttpCookie("쿠키명")
{
    Value = "쿠키 값",
    Secure = true
});

특정 쿠키만 암호화 전송 대상으로 구분하는 것보다, 모든 쿠키가 암호화되어 전달되도록 하는 것이 단순하고 권장되는 보안설정입니다. 따라서 모든 쿠키를 SSL로 전송하도록 하려면, Web.config의 requireSSL을 true로 설정하시면 됩니다.

<system.web>
    <httpCookies requireSSL="true"/>
</system.web>

위와 같이 설정할 경우, 개발환경도 모두 HTTPS환경을 구축해주어야 합니다. 개발환경 모두 HTTPS를 설정하는 것은 상당히 번거로운 작업이므로, web.config transformation을 이용하여 개발환경은 HTTP, 호스팅 환경은 HTTPS를 사용하도록 설정할 수 있습니다.

web.config (개발환경)
<system.web>
    <!-- 개발 시에는 requireSSL="true" 설정을 사용하지 않음 -->
    <httpCookies httpOnlyCookies="true" />
</system.web>
web.Release.config (릴리즈 환경)
<system.web>
    <!-- Release 환경에서는 requireSSL 사용 -->
    <httpCookies httpOnlyCookies="true" requireSSL="true" lockItem="true" xdt:Transform="Replace" />
</system.web>

3) Secure authentication cookie 설정

로그인 등 사용자인증도 반드시 HTTPS로 진행되도록 web.config의 authentication/forms을 설정하는 것이 필요합니다.

<authentication mode="Forms">
    <forms loginUrl="~/User/SignIn" requireSSL="true" xdt:Transform="Replace"/>
</authentication>

위의 설정도 개발환경에 HTTPS설정을 요구하므로, 마찬가지로 web.config transformation을 활용하여 개발환경과 릴리즈 환경을 구분하도록 합시다.

4) HTTP Strict Transport Security 설정

HTTP Strict Transport Security(HSTS)을 사용하면 브라우저에게 항상 HTTPS 프로토콜만 사용하도록 강제할 수 있습니다. HTTP Header에 Strict-Transport-Security 설정을 포함하면 브라우저는 항상 HTTPS만을 사용하게 됩니다. 이 설정은 개발자의 실수 등 인재에 의한 보안누수를 방지하고, Downgrade Attack 혹은 SSLStrip 공격 등 기술적인 보안이슈도 해결할 수 있는 방법입니다.

HSTS의 설정은 브라우저로 하여금 다음과 같이 동작하게 합니다.

  1. HTTPS를 통해 웹사이트에 접속하면, 브라우저는 Header에 HSTS설정이 포함되었는지 확인합니다. 헤더에 HSTS설정이 포함된 경우 HSTS를 적용합니다. (HTTP 프로토콜은 HSTS를 지원하지 않으며, 브라우저도 HTTP 요청으로 전달된 HSTS설정은 무시합니다.)
  2. max-age는 해당 도메인이 앞으로 HTTPS만을 사용할 시간을 초단위로 정합니다. 31536000은 대략 1년의 기간을 나타냅니다.
  3. includeSubDomains는 하위 도메인을 포함할지 여부를 결정합니다.
  4. HSTS가 적용되면, 브라우저는 해당 도메인의 모든 HTTP 연결도 HTTPS로 요청하게 됩니다.
  5. 만약 HTTPS를 보장할 수 없다면 에러페이지를 출력합니다. (인증서를 신뢰할 수 없는 경우 등)

HSTS는 web.config 설정을 통해 간단히 활성화 할 수 있습니다. 다음과 같이 web.config의 customHeaders에 HSTS header를 추가해줍시다.

<system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains" />
      </customHeaders>
    </httpProtocol>
</system.webServer> 

마찬가지로 릴리즈 환경에서만 설정하고자 한다면, 아래의 설정을 web.Release.config에 추가해주세요.

<system.webServer>
    <httpProtocol>
        <customHeaders>
           <add name="Strict-Transport-Security" value="max-age=3153600; includeSubDomains" xdt:Transform="Insert" />
        </customHeaders>
    </httpProtocol>
</system.webServer>

5) WebApi에 보안설정 적용하기

ASP.NET MVC Template에 기본적으로 활성화되어있는 WebApi는 기본적으로 HTTP를 이용합니다. 위에서 사용한 [RequireHttps] Attribute를 WebApi에는 적용할 수 없기 때문에, WebApi에 HTTPS를 요구하는 코드는 직접 구현해야 합니다. 이 코드는 Implementing HTTPS Everywhere in ASP.Net MVC application에서 확인할 수 있으며, 해당 블로그에서 구현한 코드는 다음과 같습니다.

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

public class EnforceHttpsHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // if request is local, just serve it without https
        object httpContextBaseObject;
        if (request.Properties.TryGetValue("MS_HttpContext", out httpContextBaseObject))
        {
            var httpContextBase = httpContextBaseObject as HttpContextBase;

            if (httpContextBase != null && httpContextBase.Request.IsLocal)
            {
                return base.SendAsync(request, cancellationToken);
            }
        }

        // if request is remote, enforce https
        if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
        {
            return Task<HttpResponseMessage>.Factory.StartNew(
                () =>
                {
                    var response = new HttpResponseMessage(HttpStatusCode.Forbidden)
                    {
                        Content = new StringContent("HTTPS Required")
                    };

                    return response;
                });
        }

        return base.SendAsync(request, cancellationToken);
    }
}

마지막으로 위의 Handler를 전역레벨에서 추가해줍시다.

namespace MyApp.Web.App_Start
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            //모든 WebApi가 HTTPS를 사용하도록 한다
            config.MessageHandlers.Add(new EnforceHttpsHandler());
        }
    }
}

6) 사이트를 스캔하여 보안상의 누수가 없는지 확인

https://asafaweb.com/에 접속하여 사이트를 스캔하여, 보안상의 누수가 없는지 확인합니다.