ASP.NET MVC 애플리케이션에서 CSRF(교차 사이트 요청 위조) 공격 방지

CSRF(교차 사이트 요청 위조)는 악의적인 사이트가 사용자가 현재 로그인되어 있는 취약한 사이트에 요청을 보내는 공격입니다.

다음은 CSRF 공격의 예입니다.

  1. 사용자가 양식 인증을 사용하여 로그인 www.example.com 합니다.

  2. 서버에서 사용자를 인증합니다. 서버의 응답에는 인증 쿠키가 포함됩니다.

  3. 사용자가 로그아웃하지 않고 악성 웹 사이트를 방문합니다. 이 악성 사이트에는 다음 HTML 양식이 포함되어 있습니다.

    <h1>You Are a Winner!</h1>
      <form action="http://example.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
      <input type="submit" value="Click Me"/>
    </form>
    

    양식 작업은 악성 사이트가 아닌 취약한 사이트에 게시합니다. CSRF의 “교차 사이트” 부분입니다.

  4. 사용자가 제출 단추를 클릭합니다. 브라우저에는 요청이 포함된 인증 쿠키가 포함됩니다.

  5. 요청은 사용자의 인증 컨텍스트를 사용하여 서버에서 실행되며 인증된 사용자가 수행할 수 있는 모든 작업을 수행할 수 있습니다.

이 예제에서는 사용자가 양식 단추를 클릭해야 하지만 악성 페이지는 양식을 자동으로 제출하는 스크립트를 쉽게 실행할 수 있습니다. 또한 SSL을 사용하면 악성 사이트에서 "https://" 요청을 보낼 수 있으므로 CSRF 공격을 방지할 수 없습니다.

일반적으로 CSRF 공격은 브라우저가 모든 관련 쿠키를 대상 웹 사이트로 보내기 때문에 인증에 쿠키를 사용하는 웹 사이트에 대해 가능합니다. 그러나 CSRF 공격은 쿠키 악용에 국한되지 않습니다. 예를 들어, 기본 및 다이제스트 인증에도 취약해집니다. 사용자가 기본 또는 다이제스트 인증을 사용하여 로그인한 후 브라우저는 세션이 종료될 때까지 자격 증명을 자동으로 보냅니다.

위조 방지 토큰

CSRF 공격을 방지하기 위해 ASP.NET MVC는 요청 확인 토큰이라고도 하는 위조 방지 토큰을 사용합니다.

  1. 클라이언트는 양식이 포함된 HTML 페이지를 요청합니다.
  2. 서버에는 응답에 두 개의 토큰이 포함됩니다. 하나의 토큰이 쿠키로 전송됩니다. 다른 하나는 숨겨진 양식 필드에 배치됩니다. 토큰은 악의적 사용자가 값을 추측할 수 없도록 임의로 생성됩니다.
  3. 클라이언트가 양식을 제출하면 두 토큰을 모두 서버로 다시 보내야 합니다. 클라이언트는 쿠키 토큰을 쿠키로 보내고 양식 데이터 내에서 양식 토큰을 보냅니다. (브라우저 클라이언트는 사용자가 양식을 제출할 때 자동으로 이 작업을 수행합니다.)
  4. 요청에 두 토큰이 모두 포함되지 않으면 서버는 요청을 허용하지 않습니다.

다음은 숨겨진 양식 토큰이 있는 HTML 양식의 예입니다.

<form action="/Home/Test" method="post">
    <input name="__RequestVerificationToken" type="hidden"   
           value="6fGBtLZmVBZ59oUad1Fr33BuPxANKY9q3Srr5y[...]" />    
    <input type="submit" value="Submit" />
</form>

위조 방지 토큰은 동일한 원본 정책으로 인해 악의적인 페이지에서 사용자의 토큰을 읽을 수 없으므로 작동합니다. (동일 원본 정책은 서로 다른 두 사이트에서 호스트되는 문서가 서로의 콘텐츠에 액세스하지 못하도록 합니다. 따라서 이전 예제에서 악의적인 페이지는 example.com 요청을 보낼 수 있지만 응답을 읽을 수는 없습니다.)

CSRF 공격을 방지하려면 사용자가 로그인한 후 브라우저에서 자격 증명을 자동으로 보내는 인증 프로토콜과 함께 위조 방지 토큰을 사용합니다. 여기에는 양식 인증과 같은 쿠키 기반 인증 프로토콜과 기본 및 다이제스트 인증과 같은 프로토콜이 포함됩니다.

비사용 메서드(POST, PUT, DELETE)에 대한 위조 방지 토큰이 필요합니다. 또한 안전한 방법(GET, HEAD)에 부작용이 없는지 확인합니다. 또한 CORS 또는 JSONP와 같은 도메인 간 지원을 사용하도록 설정하면 GET과 같은 안전한 방법도 잠재적으로 CSRF 공격에 취약하여 공격자가 잠재적으로 중요한 데이터를 읽을 수 있습니다.

ASP.NET MVC의 위조 방지 토큰

위조 방지 토큰을 Razor 페이지에 추가하려면 HtmlHelper.AntiForgeryToken 도우미 메서드를 사용합니다.

@using (Html.BeginForm("Manage", "Account")) {
    @Html.AntiForgeryToken()
}

이 메서드는 숨겨진 양식 필드를 추가하고 쿠키 토큰도 설정합니다.

CSRF 및 AJAX 방지

AJAX 요청이 HTML 양식 데이터가 아닌 JSON 데이터를 보낼 수 있으므로 양식 토큰은 AJAX 요청에 문제가 될 수 있습니다. 한 가지 솔루션은 사용자 지정 HTTP 헤더에 토큰을 보내는 것입니다. 다음 코드는 Razor 구문을 사용하여 토큰을 생성한 다음 AJAX 요청에 토큰을 추가합니다. 토큰은 AntiForgery.GetTokens를 호출하여 서버에서 생성됩니다.

<script>
    @functions{
        public string TokenHeaderValue()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return cookieToken + ":" + formToken;                
        }
    }

    $.ajax("api/values", {
        type: "post",
        contentType: "application/json",
        data: {  }, // JSON data goes here
        dataType: "json",
        headers: {
            'RequestVerificationToken': '@TokenHeaderValue()'
        }
    });
</script>

요청을 처리하는 경우 요청 헤더에서 토큰을 추출합니다. 그런 다음 AntiForgery.Validate 메서드를 호출하여 토큰의 유효성을 검사합니다. 토큰이 유효하지 않으면 Validate 메서드가 예외를 throw합니다.

void ValidateRequestHeader(HttpRequestMessage request)
{
    string cookieToken = "";
    string formToken = "";

    IEnumerable<string> tokenHeaders;
    if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
    {
        string[] tokens = tokenHeaders.First().Split(':');
        if (tokens.Length == 2)
        {
            cookieToken = tokens[0].Trim();
            formToken = tokens[1].Trim();
        }
    }
    AntiForgery.Validate(cookieToken, formToken);
}