ASP.NET Core에서 XSRF/CSRF(교차 사이트 요청 위조) 공격 방지

피야즈 하산릭 앤더슨

사이트 간 요청 위조는 악의적인 웹앱이 클라이언트 브라우저와 해당 브라우저를 신뢰하는 웹앱 간의 상호 작용에 영향을 줄 수 있는 웹 호스팅 앱에 대한 공격입니다. 웹 브라우저에서 웹 사이트에 대한 모든 요청과 함께 특정 형식의 인증 토큰을 자동으로 보내기 때문에 이러한 공격이 가능합니다. 이러한 형태의 악용은 공격이 이전에 인증된 사용자 세션을 활용하기 때문에 ‘원클릭 공격’ 또는 ‘세션 라이딩’이라고도 합니다. 사이트 간 요청 위조를 XSRF 또는 CSRF라고도 합니다.

CSRF 공격의 예는 다음과 같습니다.

  1. 사용자가 폼 인증을 사용하여 www.good-banking-site.example.com에 로그인합니다. 서버가 사용자를 인증하고 인증 cookie를 포함하는 응답을 발급합니다. 사이트가 수신하는 모든 요청을 유효한 인증 cookie로 신뢰하기 때문에 공격에 취약합니다.

  2. 사용자가 악의적인 사이트인 www.bad-crook-site.example.com을 방문합니다.

    악의적인 사이트인 www.bad-crook-site.example.com이 다음 예제와 유사한 HTML 폼을 포함합니다.

    <h1>Congratulations! You're a Winner!</h1>
    <form action="https://good-banking-site.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
        <input type="submit" value="Click to collect your prize!" />
    </form>
    

    폼의 action이 악의적인 사이트가 아닌 취약한 사이트에 게시합니다. CSRF의 “교차 사이트” 부분입니다.

  3. 사용자가 제출 단추를 선택합니다. 브라우저가 요청을 만들고 요청된 도메인 www.good-banking-site.example.com에 대한 인증 cookie를 자동으로 포함합니다.

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

사용자가 폼을 제출하는 단추를 선택하는 시나리오 외에도 악의적인 사이트는 다음과 같은 일을 할 수 있습니다.

  • 폼을 자동으로 제출하는 스크립트를 실행합니다.
  • 폼 제출을 AJAX 요청으로 보냅니다.
  • CSS를 사용하여 폼을 숨깁니다.

이러한 대체 시나리오에서는 처음에 악성 사이트를 방문하는 것 외에 사용자의 다른 작업 또는 입력이 필요하지 않습니다.

HTTPS를 사용할 경우 CSRF 공격이 방지되지 않습니다. 악의적인 사이트는 안전하지 않은 https://www.good-banking-site.com/ 요청을 보낼 수 있는 만큼 쉽게 요청을 보낼 수 있습니다.

일부 공격은 GET 요청에 응답하는 엔드포인트를 대상으로 하며, 이 경우 이미지 태그를 사용하여 작업을 수행할 수 있습니다. 이러한 형태의 공격은 이미지를 허용하지만 JavaScript를 차단하는 포럼 사이트에서 일반적입니다. 변수 또는 리소스가 변경되는 GET 요청에 대해 상태를 변경하는 앱은 악의적인 공격에 취약합니다. 상태를 변경하는 GET 요청은 안전하지 않습니다. GET 요청의 상태를 변경하지 않는 것이 가장 좋습니다.

CSRF 공격은 다음과 같은 이유로 인증에 cookie를 사용하는 웹앱에서 가능합니다.

  • 브라우저는 웹앱에서 발급한 cookie를 저장합니다.
  • 저장된 cookie에는 인증된 사용자에 대한 세션 cookie가 포함됩니다.
  • 브라우저는 브라우저에서 앱에 대한 요청이 생성된 방식에 관계 없이 모든 요청에 대해 도메인에 연결된 모든 cookie를 웹앱으로 보냅니다.

그러나 CSRF 공격은 cookie를 악용하는 것만으로 제한되지 않습니다. 예를 들어, 기본 및 다이제스트 인증에도 취약해집니다. 사용자가 기본 또는 다이제스트 인증을 사용하여 로그인한 후에는 세션이 종료될 때까지 브라우저에서 자동으로 자격 증명을 보냅니다.

이 컨텍스트 에서 세션 은 사용자가 인증되는 동안 클라이언트 쪽 세션을 참조합니다. 서버 쪽 세션 또는 ASP.NET Core 세션 미들웨어와는 관련이 없습니다.

사용자는 예방 조치를 취하여 CSRF 취약성으로부터 보호할 수 있습니다.

  • 웹앱 사용을 마치면 웹앱에서 로그아웃합니다.
  • 브라우저의 cookie를 정기적으로 지웁니다.

그러나 CSRF 취약점은 기본적으로 최종 사용자가 아닌 웹앱의 문제입니다.

인증 기본 사항

Cookie 기반 인증은 널리 사용되는 인증 형식입니다. 토큰 기반 인증 시스템은 특히 SPA(단일 페이지 애플리케이션)에서 널리 사용됩니다.

사용자가 자신의 사용자 이름 및 암호를 사용하여 인증하면 인증 티켓이 포함된 토큰이 발급됩니다. 토큰은 인증 및 권한 부여에 사용할 수 있습니다. 토큰은 클라이언트에서 수행하는 모든 요청과 함께 전송되는 cookie로 저장됩니다. 인증 미들웨어를 사용하여 생성 cookieCookie 하고 유효성을 검사합니다. 미들웨어는 사용자 보안 주체를 암호화된 cookie로 serialize합니다. 후속 요청에서 미들웨어는 cookie의 유효성을 검사하고, 보안 주체를 다시 만들고, 보안 주체를 HttpContext.User 속성에 할당합니다.

토큰 기반 인증

사용자가 인증되면 토큰(위조 방지 토큰이 아님)이 발급됩니다. 이 토큰에는 클레임 형식의 사용자 정보 또는 앱에서 유지 관리되는 사용자 상태를 가리키는 참조 토큰이 포함됩니다. 사용자가 인증을 요구하는 리소스에 액세스하려고 하면 토큰은 전달자 토큰의 형태로 추가 권한 부여 헤더를 사용하여 앱으로 전송됩니다. 이 방법은 앱을 비정상 상태로 만듭니다. 각 후속 요청에서 토큰은 서버 쪽 유효성 검사에 대한 요청에 전달됩니다. 이 토큰은 ‘암호화’되지 않고 ‘인코딩됩’니다. 서버에서 토큰은 해당 정보에 액세스하기 위해 디코딩됩니다. 후속 요청에서 토큰을 보내려면 브라우저의 로컬 스토리지에 토큰을 저장합니다. 브라우저 로컬 스토리지에 토큰을 배치하고 검색하고 전달자 토큰으로 사용하면 CSRF 공격에 대한 보호를 제공합니다. 그러나 앱이 XSS 또는 손상된 외부 JavaScript 파일을 통한 스크립트 주입에 취약할 경우 공격자는 로컬 스토리지에서 값을 검색하여 자신에게 보낼 수 있습니다. ASP.NET Core는 기본적으로 변수의 모든 서버 쪽 출력을 인코딩하여 XSS의 위험을 줄입니다. 신뢰할 수 없는 입력으로 Html.Raw 또는 사용자 지정 코드를 사용하여 이 동작을 재정의하는 경우 XSS의 위험이 증가할 수 있습니다.

토큰이 브라우저의 로컬 스토리지에 저장된 경우 CSRF 취약성에 대해 걱정하지 마세요. CSRF는 토큰을 cookie에 저장하는 경우에 문제가 됩니다. 자세한 내용은 GitHub 이슈 SPA 코드 샘플이 두 개의 cookie를 추가함을 참조하세요.

여러 앱이 한 도메인에 호스트됨

공유 호스팅 환경은 세션 하이재킹, 로그인 CSRF 및 기타 공격에 취약합니다.

example1.contoso.netexample2.contoso.net은 서로 다른 호스트이지만 *.contoso.net 도메인 아래의 호스트 간에 암시적 트러스트 관계가 있습니다. 이 암시적 트러스트 관계를 사용하면 신뢰할 수 없는 호스트가 서로의 cookie에 영향을 줄 수 있습니다(AJAX 요청을 제어하는 동일 원본 정책이 반드시 HTTP cookie에 적용되는 것은 아님).

동일한 도메인에서 호스트되는 앱 간에 트러스트된 cookie를 활용하는 공격은 도메인을 공유하지 않도록 하여 방지할 수 있습니다. 각 앱이 자체 도메인에서 호스트되는 경우에는 악용할 암시적 cookie 트러스트 관계가 없습니다.

ASP.NET Core 위조 방지

Warning

ASP.NET Core는 ASP.NET Core 데이터 보호를 사용하여 위조 방지를 구현합니다. 데이터 보호 스택을 서버 팜에서 작동하도록 구성해야 합니다. 자세한 내용은 데이터 보호 구성을 참조하세요.

Program.cs에서 다음 API 중 하나가 호출될 때 위조 방지 미들웨어가 종속성 주입 컨테이너에 추가됩니다.

자세한 내용은 최소 API를 사용한 위조 방지를 참조 하세요.

FormTagHelper는 위조 방지 토큰을 HTML 양식 요소에 삽입합니다. Razor 파일의 다음 태그는 위조 방지 토큰을 자동으로 생성합니다.

<form method="post">
    <!-- ... -->
</form>

마찬가지로 양식 IHtmlHelper.BeginForm 의 메서드가 GET이 아닌 경우 기본적으로 위조 방지 토큰을 생성합니다.

HTML 폼 요소에 대한 위조 방지 토큰 자동 생성은 <form> 태그에 method="post" 특성이 포함되어 있고 다음 중 하나에 해당하는 경우에 발생합니다.

  • action 특성이 비어 있는 경우(action="")
  • action 특성이 제공되지 않은 경우(<form method="post">)

다음과 같이 HTML 폼 요소에 대한 위조 방지 토큰 자동 생성을 사용하지 않도록 설정할 수 있습니다.

  • asp-antiforgery 특성을 사용하여 위조 방지 토큰을 명시적으로 사용하지 않도록 설정합니다.

    <form method="post" asp-antiforgery="false">
        <!-- ... -->
    </form>
    
  • 폼 요소는 태그 도우미 ! 옵트아웃 기호을 사용하여 태그 도우미에서 옵트아웃됩니다.

    <!form method="post">
        <!-- ... -->
    </!form>
    
  • 뷰에서 FormTagHelper를 제거합니다. Razor 뷰에 다음 지시문을 추가하여 뷰에서 FormTagHelper를 제거할 수 있습니다.

    @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
    

참고 항목

Razor Pages는 XSRF/CSRF에서 자동으로 보호됩니다. 자세한 내용은 XSRF/CSRF 및 Razor Pages를 참조하세요.

CSRF 공격으로부터 보호하는 가장 일반적인 방법은 STP(‘동기화 장치 토큰 패턴’)를 사용하는 것입니다. 사용자가 폼 데이터를 사용하여 페이지를 요청하면 STP가 사용됩니다.

  1. 서버는 현재 사용자의 ID와 연결된 토큰을 클라이언트에 보냅니다.
  2. 클라이언트에서 확인을 위해 토큰을 서버에 다시 보냅니다.
  3. 서버가 인증된 사용자의 ID와 일치하지 않는 토큰을 받으면 요청이 거부됩니다.

토큰은 고유하며 예측할 수 없습니다. 토큰을 사용하여 일련의 요청을 적절하게 시퀀싱할 수도 있습니다. 예를 들어 페이지 1 > 페이지 2 > 페이지 3 요청 시퀀스 보장할 수 있습니다. ASP.NET Core MVC 및 Razor Pages 템플릿의 모든 폼은 위조 방지 토큰을 생성합니다. 다음 뷰 예제 쌍은 위조 방지 토큰을 생성합니다.

<form asp-action="Index" asp-controller="Home" method="post">
    <!-- ... -->
</form>

@using (Html.BeginForm("Index", "Home"))
{
    <!-- ... -->
}

HTML 도우미 @Html.AntiForgeryToken에서 태그 도우미를 사용하지 않고 <form> 요소에 위조 방지 토큰을 명시적으로 추가합니다.

<form asp-action="Index" asp-controller="Home" method="post">
    @Html.AntiForgeryToken()

    <!-- ... -->
</form>

위의 각 경우에 ASP.NET Core는 다음 예제와 유사한 숨겨진 폼 필드를 추가합니다.

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">

ASP.NET Core에는 위조 방지 토큰을 사용하기 위한 다음과 같은 세 가지 필터가 포함되어 있습니다.

을 사용하여 위조 방지 AddControllers

AddControllers를 호출해도 위조 방지 토큰이 활성화되지 않습니다. 기본 제공 위조 방지 토큰을 지원하려면 AddControllersWithViews를 호출해야 합니다.

여러 브라우저 탭 및 동기화 장치 토큰 패턴

동기화 장치 토큰 패턴을 사용하면 가장 최근에 로드된 페이지에만 유효한 위조 방지 토큰이 포함됩니다. 여러 탭을 사용하는 것은 문제가 될 수 있습니다. 예를 들어 사용자가 여러 탭을 여는 경우:

  • 가장 최근에 로드된 탭에만 유효한 위조 방지 토큰이 포함되어 있습니다.
  • 이전에 로드된 탭에서 수행된 요청은 Antiforgery token validation failed. The antiforgery cookie token and request token do not match 오류와 함께 실패합니다.

문제가 발생하면 대체 CSRF 보호 패턴을 고려합니다.

AntiforgeryOptions을 사용하여 위조 방지 구성

Program.cs에서 AntiforgeryOptions 사용자 지정:

builder.Services.AddAntiforgery(options =>
{
    // Set Cookie properties using CookieBuilder properties†.
    options.FormFieldName = "AntiforgeryFieldname";
    options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
    options.SuppressXFrameOptionsHeader = false;
});

다음 테이블과 같이 CookieBuilder 클래스의 속성을 사용하여 위조 방지 Cookie 속성을 설정합니다.

옵션 설명
Cookie 위조 방지 cookie를 만드는 데 사용되는 설정을 결정합니다.
FormFieldName 위조 방지 시스템이 뷰에서 위조 방지 토큰을 렌더링하는 데 사용하는 숨겨진 폼 필드의 이름입니다.
HeaderName 위조 방지 시스템에서 사용하는 헤더의 이름입니다. null이면 시스템은 폼 데이터만 고려합니다.
SuppressXFrameOptionsHeader X-Frame-Options 헤더 생성을 표시하지 않을지 여부를 지정합니다. 기본적으로 헤더는 “SAMEORIGIN” 값을 사용하여 생성됩니다. 기본값은 false입니다.

자세한 내용은 CookieAuthenticationOptions를 참조하세요.

IAntiforgery을 사용하여 위조 방지 토큰 생성

IAntiforgery 는 위조 방지 기능을 구성하는 API를 제공합니다. WebApplication.Services을 사용하여 Program.cs에서 IAntiforgery를 요청할 수 있습니다. 다음 예제에서는 앱의 홈페이지에서 미들웨어를 사용하여 위조 방지 토큰을 생성하고 응답에 cookie로 보냅니다.

app.UseRouting();

app.UseAuthorization();

var antiforgery = app.Services.GetRequiredService<IAntiforgery>();

app.Use((context, next) =>
{
    var requestPath = context.Request.Path.Value;

    if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
        || string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
    {
        var tokenSet = antiforgery.GetAndStoreTokens(context);
        context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
            new CookieOptions { HttpOnly = false });
    }

    return next(context);
});

앞의 예제에서는 이름이 XSRF-TOKEN인 cookie을 설정합니다. 클라이언트는 이 cookie를 읽고 해당 값을 AJAX 요청에 연결된 헤더로 제공할 수 있습니다. 예를 들어 Angular에는 기본적으로 XSRF-TOKEN로 명명된 cookie을 읽는 기본 제공 XSRF 보호가 포함되어 있습니다.

위조 방지 유효성 검사 필요

ValidateAntiForgeryToken 작업 필터는 개별 작업, 컨트롤러 또는 전역적으로 적용될 수 있습니다. 이 필터가 적용된 작업에 대한 요청은 유효한 위조 방지 토큰이 포함되지 않으면 차단됩니다.

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
    // ...

    return RedirectToAction();
}

ValidateAntiForgeryToken 특성에는 HTTP GET 요청을 포함하여 특성이 표시하는 작업 메서드에 대한 요청을 위해 토큰이 필요합니다. ValidateAntiForgeryToken 특성을 앱의 컨트롤러에서 적용하는 경우 IgnoreAntiforgeryToken 특성을 사용하여 재정의할 수 있습니다.

안전하지 않은 HTTP 메서드에 대한 위조 방지 토큰의 유효성 자동 검사

ValidateAntiForgeryToken 특성을 광범위하게 적용한 다음, IgnoreAntiforgeryToken 특성을 사용하여 재정의하는 대신, AutoValidateAntiforgeryToken 특성을 사용할 수 있습니다. 이 특성은 ValidateAntiForgeryToken 특성과 동일하게 작동합니다. 단, 다음 HTTP 메서드를 사용하여 생성된 요청의 경우는 토큰이 필요하지 않습니다.

  • GET
  • HEAD
  • OPTIONS
  • TRACE

비 API 시나리오에는 AutoValidateAntiforgeryToken을 광범위하게 사용하는 것이 좋습니다. 이 특성을 통해 POST 작업은 기본적으로 보호됩니다. 대신, ValidateAntiForgeryToken이 개별 작업 메서드에 적용되지 않는 한, 기본적으로 위조 방지 토큰을 무시하는 것이 좋습니다. 이 시나리오에서는 POST 작업 메서드가 실수로 보호되지 않은 상태가 될 수 있으며 이로 인해 앱이 CSRF 공격에 취약해질 수 있습니다. 모든 POST는 위조 방지 토큰을 전송해야 합니다.

API에는 토큰에서 cookie 이외 부분을 보내기 위한 자동 메커니즘이 없습니다. 해당 구현은 클라이언트 코드 구현에 따라 달라질 수 있습니다. 몇 가지 예제는 다음과 같습니다.

클래스 수준 예제:

[AutoValidateAntiforgeryToken]
public class HomeController : Controller

전역적 예제:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});

전역 또는 컨트롤러 위조 방지 특성 재정의

IgnoreAntiforgeryToken 필터는 지정된 작업(또는 컨트롤러)에 대해 위조 방지 토큰이 필요하지 않도록 하는 데 사용됩니다. 적용될 경우 이 필터는 상위 수준(전역적 또는 컨트롤러)에 지정된 ValidateAntiForgeryTokenAutoValidateAntiforgeryToken 필터를 재정의합니다.

[IgnoreAntiforgeryToken]
public IActionResult IndexOverride()
{
    // ...

    return RedirectToAction();
}

인증 후 토큰 새로 고침

사용자를 뷰 또는 Razor Pages 페이지로 리디렉션하여 인증한 후에 토큰을 새로 고쳐야 합니다.

JavaScript, AJAX 및 SPA

기존의 HTML 기반 앱에서 위조 방지 토큰은 숨겨진 폼 필드를 사용하여 서버로 전달됩니다. 최신 JavaScript 기반 앱과 SPA에서 많은 요청은 프로그래밍 방식으로 수행됩니다. 이러한 AJAX 요청은 요청 헤더 또는 cookies와 같은 다른 기술을 사용하여 토큰을 보낼 수 있습니다.

cookie를 사용하여 인증 토큰을 저장하고 서버에서 API 요청을 인증하는 경우 CSRF는 잠재적인 문제가 됩니다. 로컬 스토리지를 사용하여 토큰을 저장하는 경우 로컬 스토리지의 값이 모든 요청에서 서버에 자동으로 전송되지는 않으므로 CSRF 취약성이 완화될 수 있습니다. 로컬 저장소를 사용하여 클라이언트에 위조 방지 토큰을 저장하고 토큰을 요청 헤더로 전송하는 것이 좋습니다.

Blazor

자세한 내용은 ASP.NET Core Blazor 인증 및 권한 부여를 참조하세요.

JavaScript

뷰에서 JavaScript를 사용하면 뷰 내에서 서비스를 사용하여 토큰을 만들 수 있습니다. 뷰에 IAntiforgery 서비스를 삽입하고 다음을 호출 GetAndStoreTokens합니다.

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery

@{
    ViewData["Title"] = "JavaScript";

    var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}

<input id="RequestVerificationToken" type="hidden" value="@requestToken" />

<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>

@section Scripts {
<script>
    document.addEventListener("DOMContentLoaded", () => {
        const resultElement = document.getElementById("result");

        document.getElementById("button").addEventListener("click", async () => {

            const response = await fetch("@Url.Action("FetchEndpoint")", {
                method: "POST",
                headers: {
                    RequestVerificationToken:
                        document.getElementById("RequestVerificationToken").value
                }
            });

            if (response.ok) {
                resultElement.innerText = await response.text();
            } else {
                resultElement.innerText = `Request Failed: ${response.status}`
            }
        });
    });
</script>
}

앞의 예제에서는 JavaScript를 사용하여 AJAX POST 헤더에 대한 숨겨진 필드 값을 읽습니다.

이 접근 방식을 사용하면 서버에서 cookie를 설정하거나 클라이언트에서 읽어오는 작업을 직접 진행하지 않아도 됩니다. 그러나 IAntiforgery 서비스를 삽입할 수 없는 경우 JavaScript를 사용하여 cookie의 토큰에 액세스합니다.

  • 서버에 대한 추가 요청의 액세스 토큰(일반적으로 same-origin)입니다.
  • cookie 콘텐츠를 사용하여 토큰 값이 있는 헤더를 만듭니다.

스크립트가 X-XSRF-TOKEN이라는 헤더에서 토큰을 보내도록 요청하는 경우 X-XSRF-TOKEN 헤더를 찾도록 위조 방지 서비스를 구성합니다.

builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

다음 예제에서는 JavaScript에서 읽을 수 있는 cookie에 요청 토큰을 쓰는 보호된 엔드포인트를 추가합니다.

app.UseAuthorization();
app.MapGet("antiforgery/token", (IAntiforgery forgeryService, HttpContext context) =>
{
    var tokens = forgeryService.GetAndStoreTokens(context);
    context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
            new CookieOptions { HttpOnly = false });

    return Results.Ok();
}).RequireAuthorization();

다음 예제에서는 JavaScript를 사용하여 AJAX 요청을 수행하여 토큰을 가져오고 적절한 헤더를 사용하여 다른 요청을 수행합니다.

var response = await fetch("/antiforgery/token", {
    method: "GET",
    headers: { "Authorization": authorizationToken }
});

if (response.ok) {
    // https://developer.mozilla.org/docs/web/api/document/cookie
    const xsrfToken = document.cookie
        .split("; ")
        .find(row => row.startsWith("XSRF-TOKEN="))
        .split("=")[1];

    response = await fetch("/JavaScript/FetchEndpoint", {
        method: "POST",
        headers: { "X-XSRF-TOKEN": xsrfToken, "Authorization": authorizationToken }
    });

    if (response.ok) {
        resultElement.innerText = await response.text();
    } else {
        resultElement.innerText = `Request Failed: ${response.status}`
    }
} else {    
    resultElement.innerText = `Request Failed: ${response.status}`
}

참고 항목

요청 헤더와 양식 페이로드 모두에서 위조 방지 토큰이 제공되면 헤더의 토큰만 유효성을 검사합니다.

최소 API를 사용하는 위조 방지

AddAntiforgery를 호출하고 UseAntiforgery(IApplicationBuilder) DI에 위조 방지 서비스를 등록합니다. 위조 방지 토큰은 교차 사이트 요청 위조 공격을 완화하는 데 사용됩니다.

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();

app.UseAntiforgery();

app.MapGet("/", () => "Hello World!");

app.Run();

위조 방지 미들웨어:

  • 요청 파이프라인의 나머지 실행을 단락하지 않습니다.
  • 현재 요청의 HttpContext.Features에서 IAntiforgeryValidationFeature를 설정합니다.

위조 방지 토큰은 다음과 같은 경우에만 유효성을 검사합니다.

  • 엔드포인트에는 IAntiforgeryMetadata를 구현하는 메타데이터가 포함되어 있습니다RequiresValidation=true.
  • 엔드포인트와 연결된 HTTP 메서드는 관련 HTTP 메서드입니다. 관련 메서드는 TRACE, OPTIONS, HEAD 및 GET을 제외한 모든 HTTP 메서드 입니다.
  • 요청은 유효한 엔드포인트와 연결됩니다.

참고: 수동으로 사용하도록 설정하면 인증 및 권한 부여 미들웨어 후에 위조 방지 미들웨어를 실행하여 사용자가 인증되지 않은 경우 양식 데이터를 읽지 않도록 해야 합니다.

기본적으로 양식 데이터를 허용하는 최소 API에는 위조 방지 토큰 유효성 검사가 필요합니다.

다음 GenerateForm 방법을 고려합니다.

public static string GenerateForm(string action, 
    AntiforgeryTokenSet token, bool UseToken=true)
{
    string tokenInput = "";
    if (UseToken)
    {
        tokenInput = $@"<input name=""{token.FormFieldName}""
                         type=""hidden"" value=""{token.RequestToken}"" />";
    }

    return $@"
    <html><body>
        <form action=""{action}"" method=""POST"" enctype=""multipart/form-data"">
            {tokenInput}
            <input type=""text"" name=""name"" />
            <input type=""date"" name=""dueDate"" />
            <input type=""checkbox"" name=""isCompleted"" />
            <input type=""submit"" />
        </form>
    </body></html>
";
}

앞의 코드에는 세 개의 인수, 작업, 위조 방지 토큰 및 bool 토큰을 사용해야 하는지 여부를 나타내는 인수가 있습니다.

다음 샘플을 고려합니다.

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();

app.UseAntiforgery();

// Pass token
app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    return Results.Content(MyHtml.GenerateForm("/todo", token), "text/html");
});

// Don't pass a token, fails
app.MapGet("/SkipToken", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    return Results.Content(MyHtml.GenerateForm("/todo",token, false ), "text/html");
});

// Post to /todo2. DisableAntiforgery on that endpoint so no token needed.
app.MapGet("/DisableAntiforgery", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    return Results.Content(MyHtml.GenerateForm("/todo2", token, false), "text/html");
});

app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));

app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
                                                .DisableAntiforgery();

app.Run();

class Todo
{
    public required string Name { get; set; }
    public bool IsCompleted { get; set; }
    public DateTime DueDate { get; set; }
}

public static class MyHtml
{
    public static string GenerateForm(string action, 
        AntiforgeryTokenSet token, bool UseToken=true)
    {
        string tokenInput = "";
        if (UseToken)
        {
            tokenInput = $@"<input name=""{token.FormFieldName}""
                             type=""hidden"" value=""{token.RequestToken}"" />";
        }

        return $@"
        <html><body>
            <form action=""{action}"" method=""POST"" enctype=""multipart/form-data"">
                {tokenInput}
                <input type=""text"" name=""name"" />
                <input type=""date"" name=""dueDate"" />
                <input type=""checkbox"" name=""isCompleted"" />
                <input type=""submit"" />
            </form>
        </body></html>
    ";
    }
}

앞의 코드에서 다음을 게시합니다.

  • /todo 에는 유효한 위조 방지 토큰이 필요합니다.
  • /todo2는 호출되므로 유효한 위조 방지 토큰 DisableAntiforgery 이 필요하지 않습니다.
app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));

app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
                                                .DisableAntiforgery();

게시자:

  • /todo 위조 방지 토큰이 / 유효하기 때문에 엔드포인트에서 생성된 폼에서 성공합니다.
  • /todo 은 위조 방지가 포함되지 않기 때문에 실패에 의해 /SkipToken 생성된 양식에서
  • /todo2 위조 방지가 필요하지 않으므로 엔드포인트에서 생성된 /DisableAntiforgery 폼에서 성공합니다.
app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));

app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
                                                .DisableAntiforgery();

양식이 유효한 위조 방지 토큰 없이 제출되는 경우:

  • 개발 환경에서 예외가 throw됩니다.
  • 프로덕션 환경에서 메시지가 기록됩니다.

Windows 인증 및 위조 방지 cookie

Windows 인증을 사용하는 경우 애플리케이션 엔드포인트는 cookie에 대해 수행되는 것과 동일한 방식으로 CSRF 공격으로부터 보호되어야 합니다. 브라우저는 서버에 인증 컨텍스트를 암시적으로 전송하며 CSRF 공격으로부터 엔드포인트를 보호해야 합니다.

위조 방지 확장

IAntiforgeryAdditionalDataProvider 형식을 사용하면 개발자가 각 토큰의 추가 데이터를 라운드트립하여 CSRF 방지 시스템의 동작을 확장할 수 있습니다. 필드 GetAdditionalData 토큰이 생성될 때마다 메서드가 호출되고 반환 값이 생성된 토큰 내에 포함됩니다. 구현자는 타임스탬프, nonce 또는 기타 값을 반환한 다음 토큰의 유효성을 검사할 때 이 데이터의 유효성을 검사하기 위해 호출 ValidateAdditionalData 할 수 있습니다. 클라이언트의 사용자 이름이 이미 생성된 토큰에 포함되어 있으므로 이 정보를 포함할 필요가 없습니다. 토큰에 보조 데이터가 포함되어 있지만 IAntiForgeryAdditionalDataProvider가 구성되지 않은 경우 보조 데이터가 유효한지 검사되지 않습니다.

추가 리소스

교차 사이트 요청 위조(XSRF 또는 CSRF라고도 함)는 악의적인 웹앱이 클라이언트 브라우저와 해당 브라우저를 신뢰하는 웹앱 간의 상호 작용에 영향을 줄 수 있는 웹 호스팅 앱에 대한 공격입니다. 웹 브라우저에서 웹 사이트에 대한 모든 요청과 함께 특정 형식의 인증 토큰을 자동으로 보내기 때문에 이러한 공격이 가능합니다. 이러한 형태의 악용은 공격이 이전에 인증된 사용자 세션을 활용하기 때문에 ‘원클릭 공격’ 또는 ‘세션 라이딩’이라고도 합니다.

CSRF 공격의 예는 다음과 같습니다.

  1. 사용자가 폼 인증을 사용하여 www.good-banking-site.example.com에 로그인합니다. 서버가 사용자를 인증하고 인증 cookie를 포함하는 응답을 발급합니다. 사이트가 수신하는 모든 요청을 유효한 인증 cookie로 신뢰하기 때문에 공격에 취약합니다.

  2. 사용자가 악의적인 사이트인 www.bad-crook-site.example.com을 방문합니다.

    악의적인 사이트인 www.bad-crook-site.example.com이 다음 예제와 유사한 HTML 폼을 포함합니다.

    <h1>Congratulations! You're a Winner!</h1>
    <form action="https://good-banking-site.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
        <input type="submit" value="Click to collect your prize!" />
    </form>
    

    폼의 action이 악의적인 사이트가 아닌 취약한 사이트에 게시합니다. CSRF의 “교차 사이트” 부분입니다.

  3. 사용자가 제출 단추를 선택합니다. 브라우저가 요청을 만들고 요청된 도메인 www.good-banking-site.example.com에 대한 인증 cookie를 자동으로 포함합니다.

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

사용자가 폼을 제출하는 단추를 선택하는 시나리오 외에도 악의적인 사이트는 다음과 같은 일을 할 수 있습니다.

  • 폼을 자동으로 제출하는 스크립트를 실행합니다.
  • 폼 제출을 AJAX 요청으로 보냅니다.
  • CSS를 사용하여 폼을 숨깁니다.

이러한 대체 시나리오에서는 처음에 악성 사이트를 방문하는 것 외에 사용자의 다른 작업 또는 입력이 필요하지 않습니다.

HTTPS를 사용할 경우 CSRF 공격이 방지되지 않습니다. 악의적인 사이트는 비보안 요청을 보낼 수 있는 것만큼 쉽게 https://www.good-banking-site.com/ 요청을 보낼 수 있습니다.

일부 공격은 GET 요청에 응답하는 엔드포인트를 대상으로 하며, 이 경우 이미지 태그를 사용하여 작업을 수행할 수 있습니다. 이러한 형태의 공격은 이미지를 허용하지만 JavaScript를 차단하는 포럼 사이트에서 일반적입니다. 변수 또는 리소스가 변경되는 GET 요청에 대해 상태를 변경하는 앱은 악의적인 공격에 취약합니다. 상태를 변경하는 GET 요청은 안전하지 않습니다. GET 요청의 상태를 변경하지 않는 것이 가장 좋습니다.

CSRF 공격은 다음과 같은 이유로 인증에 cookie를 사용하는 웹앱에서 가능합니다.

  • 브라우저는 웹앱에서 발급한 cookie를 저장합니다.
  • 저장된 cookie에는 인증된 사용자에 대한 세션 cookie가 포함됩니다.
  • 브라우저는 브라우저에서 앱에 대한 요청이 생성된 방식에 관계 없이 모든 요청에 대해 도메인에 연결된 모든 cookie를 웹앱으로 보냅니다.

그러나 CSRF 공격은 cookie를 악용하는 것만으로 제한되지 않습니다. 예를 들어, 기본 및 다이제스트 인증에도 취약해집니다. 사용자가 기본 또는 다이제스트 인증을 사용하여 로그인한 후에는 세션이 종료될 때까지 브라우저에서 자동으로 자격 증명을 보냅니다.

이 컨텍스트 에서 세션 은 사용자가 인증되는 동안 클라이언트 쪽 세션을 참조합니다. 서버 쪽 세션 또는 ASP.NET Core 세션 미들웨어와는 관련이 없습니다.

사용자는 예방 조치를 취하여 CSRF 취약성으로부터 보호할 수 있습니다.

  • 웹앱 사용을 마치면 웹앱에서 로그아웃합니다.
  • 브라우저의 cookie를 정기적으로 지웁니다.

그러나 CSRF 취약점은 기본적으로 최종 사용자가 아닌 웹앱의 문제입니다.

인증 기본 사항

Cookie 기반 인증은 널리 사용되는 인증 형식입니다. 토큰 기반 인증 시스템은 특히 SPA(단일 페이지 애플리케이션)에서 널리 사용됩니다.

사용자가 사용자 이름 및 암호를 사용하여 인증을 받는 경우 인증 및 권한 부여에 사용할 수 있는 인증 티켓이 포함된 토큰이 발급됩니다. 토큰은 클라이언트에서 수행하는 모든 요청과 함께 전송되는 cookie로 저장됩니다. 이 cookie를 생성하고 유효성을 검사하는 과정은 Cookie 인증 미들웨어에서 수행됩니다. 미들웨어는 사용자 보안 주체를 암호화된 cookie로 serialize합니다. 후속 요청에서 미들웨어는 cookie의 유효성을 검사하고, 보안 주체를 다시 만들고, 보안 주체를 HttpContext.User 속성에 할당합니다.

토큰 기반 인증

사용자가 인증되면 토큰(위조 방지 토큰이 아님)이 발급됩니다. 이 토큰에는 클레임 형식의 사용자 정보 또는 앱에서 유지 관리되는 사용자 상태를 가리키는 참조 토큰이 포함됩니다. 사용자가 인증을 요구하는 리소스에 액세스하려고 하면 토큰은 전달자 토큰의 형태로 추가 권한 부여 헤더를 사용하여 앱으로 전송됩니다. 이 방법은 앱을 비정상 상태로 만듭니다. 각 후속 요청에서 토큰은 서버 쪽 유효성 검사에 대한 요청에 전달됩니다. 이 토큰은 ‘암호화’되지 않고 ‘인코딩됩’니다. 서버에서 토큰은 해당 정보에 액세스하기 위해 디코딩됩니다. 후속 요청에서 토큰을 보내려면 브라우저의 로컬 스토리지에 토큰을 저장합니다. 브라우저 로컬 스토리지에 토큰을 배치하고 검색하고 전달자 토큰으로 사용하면 CSRF 공격에 대한 보호를 제공합니다. 그러나 앱이 XSS 또는 손상된 외부 Javascript 파일을 통한 스크립트 주입에 취약할 경우 공격자는 로컬 스토리지에서 값을 검색하여 자신에게 보낼 수 있습니다. ASP.NET Core는 기본적으로 변수의 모든 서버 쪽 출력을 인코딩하여 XSS의 위험을 줄입니다. 신뢰할 수 없는 입력으로 Html.Raw 또는 사용자 지정 코드를 사용하여 이 동작을 재정의하는 경우 XSS의 위험이 증가할 수 있습니다.

토큰이 브라우저의 로컬 스토리지에 저장된 경우 CSRF 취약성에 대해 걱정하지 마세요. CSRF는 토큰을 cookie에 저장하는 경우에 문제가 됩니다. 자세한 내용은 GitHub 이슈 SPA 코드 샘플이 두 개의 cookie를 추가함을 참조하세요.

여러 앱이 한 도메인에 호스트됨

공유 호스팅 환경은 세션 하이재킹, 로그인 CSRF 및 기타 공격에 취약합니다.

example1.contoso.netexample2.contoso.net은 서로 다른 호스트이지만 *.contoso.net 도메인 아래의 호스트 간에 암시적 트러스트 관계가 있습니다. 이 암시적 트러스트 관계를 사용하면 신뢰할 수 없는 호스트가 서로의 cookie에 영향을 줄 수 있습니다(AJAX 요청을 제어하는 동일 원본 정책이 반드시 HTTP cookie에 적용되는 것은 아님).

동일한 도메인에서 호스트되는 앱 간에 트러스트된 cookie를 활용하는 공격은 도메인을 공유하지 않도록 하여 방지할 수 있습니다. 각 앱이 자체 도메인에서 호스트되는 경우에는 악용할 암시적 cookie 트러스트 관계가 없습니다.

ASP.NET Core 위조 방지

Warning

ASP.NET Core는 ASP.NET Core 데이터 보호를 사용하여 위조 방지를 구현합니다. 데이터 보호 스택을 서버 팜에서 작동하도록 구성해야 합니다. 자세한 내용은 데이터 보호 구성을 참조하세요.

Program.cs에서 다음 API 중 하나가 호출될 때 위조 방지 미들웨어가 종속성 주입 컨테이너에 추가됩니다.

FormTagHelper는 위조 방지 토큰을 HTML 양식 요소에 삽입합니다. Razor 파일의 다음 태그는 위조 방지 토큰을 자동으로 생성합니다.

<form method="post">
    <!-- ... -->
</form>

마찬가지로 양식 IHtmlHelper.BeginForm 의 메서드가 GET이 아닌 경우 기본적으로 위조 방지 토큰을 생성합니다.

HTML 폼 요소에 대한 위조 방지 토큰 자동 생성은 <form> 태그에 method="post" 특성이 포함되어 있고 다음 중 하나에 해당하는 경우에 발생합니다.

  • action 특성이 비어 있는 경우(action="")
  • action 특성이 제공되지 않은 경우(<form method="post">)

다음과 같이 HTML 폼 요소에 대한 위조 방지 토큰 자동 생성을 사용하지 않도록 설정할 수 있습니다.

  • asp-antiforgery 특성을 사용하여 위조 방지 토큰을 명시적으로 사용하지 않도록 설정합니다.

    <form method="post" asp-antiforgery="false">
        <!-- ... -->
    </form>
    
  • 폼 요소는 태그 도우미 ! 옵트아웃 기호을 사용하여 태그 도우미에서 옵트아웃됩니다.

    <!form method="post">
        <!-- ... -->
    </!form>
    
  • 뷰에서 FormTagHelper를 제거합니다. Razor 뷰에 다음 지시문을 추가하여 뷰에서 FormTagHelper를 제거할 수 있습니다.

    @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
    

참고 항목

Razor Pages는 XSRF/CSRF에서 자동으로 보호됩니다. 자세한 내용은 XSRF/CSRF 및 Razor Pages를 참조하세요.

CSRF 공격으로부터 보호하는 가장 일반적인 방법은 STP(‘동기화 장치 토큰 패턴’)를 사용하는 것입니다. 사용자가 폼 데이터를 사용하여 페이지를 요청하면 STP가 사용됩니다.

  1. 서버는 현재 사용자의 ID와 연결된 토큰을 클라이언트에 보냅니다.
  2. 클라이언트에서 확인을 위해 토큰을 서버에 다시 보냅니다.
  3. 서버가 인증된 사용자의 ID와 일치하지 않는 토큰을 받으면 요청이 거부됩니다.

토큰은 고유하며 예측할 수 없습니다. 토큰을 사용하여 일련의 요청을 적절하게 시퀀싱할 수도 있습니다. 예를 들어 페이지 1 > 페이지 2 > 페이지 3 요청 시퀀스 보장할 수 있습니다. ASP.NET Core MVC 및 Razor Pages 템플릿의 모든 폼은 위조 방지 토큰을 생성합니다. 다음 뷰 예제 쌍은 위조 방지 토큰을 생성합니다.

<form asp-action="Index" asp-controller="Home" method="post">
    <!-- ... -->
</form>

@using (Html.BeginForm("Index", "Home"))
{
    <!-- ... -->
}

HTML 도우미 @Html.AntiForgeryToken에서 태그 도우미를 사용하지 않고 <form> 요소에 위조 방지 토큰을 명시적으로 추가합니다.

<form asp-action="Index" asp-controller="Home" method="post">
    @Html.AntiForgeryToken()

    <!-- ... -->
</form>

위의 각 경우에 ASP.NET Core는 다음 예제와 유사한 숨겨진 폼 필드를 추가합니다.

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">

ASP.NET Core에는 위조 방지 토큰을 사용하기 위한 다음과 같은 세 가지 필터가 포함되어 있습니다.

을 사용하여 위조 방지 AddControllers

AddControllers를 호출해도 위조 방지 토큰이 활성화되지 않습니다. 기본 제공 위조 방지 토큰을 지원하려면 AddControllersWithViews를 호출해야 합니다.

여러 브라우저 탭 및 동기화 장치 토큰 패턴

동기화 장치 토큰 패턴을 사용하면 가장 최근에 로드된 페이지에만 유효한 위조 방지 토큰이 포함됩니다. 여러 탭을 사용하는 것은 문제가 될 수 있습니다. 예를 들어 사용자가 여러 탭을 여는 경우:

  • 가장 최근에 로드된 탭에만 유효한 위조 방지 토큰이 포함되어 있습니다.
  • 이전에 로드된 탭에서 수행된 요청은 Antiforgery token validation failed. The antiforgery cookie token and request token do not match 오류와 함께 실패합니다.

문제가 발생하면 대체 CSRF 보호 패턴을 고려합니다.

AntiforgeryOptions을 사용하여 위조 방지 구성

Program.cs에서 AntiforgeryOptions 사용자 지정:

builder.Services.AddAntiforgery(options =>
{
    // Set Cookie properties using CookieBuilder properties†.
    options.FormFieldName = "AntiforgeryFieldname";
    options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
    options.SuppressXFrameOptionsHeader = false;
});

다음 테이블과 같이 CookieBuilder 클래스의 속성을 사용하여 위조 방지 Cookie 속성을 설정합니다.

옵션 설명
Cookie 위조 방지 cookie를 만드는 데 사용되는 설정을 결정합니다.
FormFieldName 위조 방지 시스템이 뷰에서 위조 방지 토큰을 렌더링하는 데 사용하는 숨겨진 폼 필드의 이름입니다.
HeaderName 위조 방지 시스템에서 사용하는 헤더의 이름입니다. null이면 시스템은 폼 데이터만 고려합니다.
SuppressXFrameOptionsHeader X-Frame-Options 헤더 생성을 표시하지 않을지 여부를 지정합니다. 기본적으로 헤더는 “SAMEORIGIN” 값을 사용하여 생성됩니다. 기본값은 false입니다.

자세한 내용은 CookieAuthenticationOptions를 참조하세요.

IAntiforgery을 사용하여 위조 방지 토큰 생성

IAntiforgery 는 위조 방지 기능을 구성하는 API를 제공합니다. WebApplication.Services을 사용하여 Program.cs에서 IAntiforgery를 요청할 수 있습니다. 다음 예제에서는 앱의 홈페이지에서 미들웨어를 사용하여 위조 방지 토큰을 생성하고 응답에 cookie로 보냅니다.

app.UseRouting();

app.UseAuthorization();

var antiforgery = app.Services.GetRequiredService<IAntiforgery>();

app.Use((context, next) =>
{
    var requestPath = context.Request.Path.Value;

    if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
        || string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
    {
        var tokenSet = antiforgery.GetAndStoreTokens(context);
        context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
            new CookieOptions { HttpOnly = false });
    }

    return next(context);
});

앞의 예제에서는 이름이 XSRF-TOKEN인 cookie을 설정합니다. 클라이언트는 이 cookie를 읽고 해당 값을 AJAX 요청에 연결된 헤더로 제공할 수 있습니다. 예를 들어 Angular에는 기본적으로 XSRF-TOKEN로 명명된 cookie을 읽는 기본 제공 XSRF 보호가 포함되어 있습니다.

위조 방지 유효성 검사 필요

ValidateAntiForgeryToken 작업 필터는 개별 작업, 컨트롤러 또는 전역적으로 적용될 수 있습니다. 이 필터가 적용된 작업에 대한 요청은 유효한 위조 방지 토큰이 포함되지 않으면 차단됩니다.

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
    // ...

    return RedirectToAction();
}

ValidateAntiForgeryToken 특성에는 HTTP GET 요청을 포함하여 특성이 표시하는 작업 메서드에 대한 요청을 위해 토큰이 필요합니다. ValidateAntiForgeryToken 특성을 앱의 컨트롤러에서 적용하는 경우 IgnoreAntiforgeryToken 특성을 사용하여 재정의할 수 있습니다.

안전하지 않은 HTTP 메서드에 대한 위조 방지 토큰의 유효성 자동 검사

ValidateAntiForgeryToken 특성을 광범위하게 적용한 다음, IgnoreAntiforgeryToken 특성을 사용하여 재정의하는 대신, AutoValidateAntiforgeryToken 특성을 사용할 수 있습니다. 이 특성은 ValidateAntiForgeryToken 특성과 동일하게 작동합니다. 단, 다음 HTTP 메서드를 사용하여 생성된 요청의 경우는 토큰이 필요하지 않습니다.

  • GET
  • HEAD
  • OPTIONS
  • TRACE

비 API 시나리오에는 AutoValidateAntiforgeryToken을 광범위하게 사용하는 것이 좋습니다. 이 특성을 통해 POST 작업은 기본적으로 보호됩니다. 대신, ValidateAntiForgeryToken이 개별 작업 메서드에 적용되지 않는 한, 기본적으로 위조 방지 토큰을 무시하는 것이 좋습니다. 이 시나리오에서는 POST 작업 메서드가 실수로 보호되지 않은 상태가 될 수 있으며 이로 인해 앱이 CSRF 공격에 취약해질 수 있습니다. 모든 POST는 위조 방지 토큰을 전송해야 합니다.

API에는 토큰에서 cookie 이외 부분을 보내기 위한 자동 메커니즘이 없습니다. 해당 구현은 클라이언트 코드 구현에 따라 달라질 수 있습니다. 몇 가지 예제는 다음과 같습니다.

클래스 수준 예제:

[AutoValidateAntiforgeryToken]
public class HomeController : Controller

전역적 예제:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});

전역 또는 컨트롤러 위조 방지 특성 재정의

IgnoreAntiforgeryToken 필터는 지정된 작업(또는 컨트롤러)에 대해 위조 방지 토큰이 필요하지 않도록 하는 데 사용됩니다. 적용될 경우 이 필터는 상위 수준(전역적 또는 컨트롤러)에 지정된 ValidateAntiForgeryTokenAutoValidateAntiforgeryToken 필터를 재정의합니다.

[IgnoreAntiforgeryToken]
public IActionResult IndexOverride()
{
    // ...

    return RedirectToAction();
}

인증 후 토큰 새로 고침

사용자를 뷰 또는 Razor Pages 페이지로 리디렉션하여 인증한 후에 토큰을 새로 고쳐야 합니다.

JavaScript, AJAX 및 SPA

기존의 HTML 기반 앱에서 위조 방지 토큰은 숨겨진 폼 필드를 사용하여 서버로 전달됩니다. 최신 JavaScript 기반 앱과 SPA에서 많은 요청은 프로그래밍 방식으로 수행됩니다. 이러한 AJAX 요청은 다른 기술(예: 요청 헤더 또는 cookie)을 사용하여 토큰을 보낼 수 있습니다.

cookie를 사용하여 인증 토큰을 저장하고 서버에서 API 요청을 인증하는 경우 CSRF는 잠재적인 문제가 됩니다. 로컬 스토리지를 사용하여 토큰을 저장하는 경우 로컬 스토리지의 값이 모든 요청에서 서버에 자동으로 전송되지는 않으므로 CSRF 취약성이 완화될 수 있습니다. 로컬 저장소를 사용하여 클라이언트에 위조 방지 토큰을 저장하고 토큰을 요청 헤더로 전송하는 것이 좋습니다.

JavaScript

뷰에서 JavaScript를 사용하면 뷰 내에서 서비스를 사용하여 토큰을 만들 수 있습니다. 뷰에 IAntiforgery 서비스를 삽입하고 다음을 호출 GetAndStoreTokens합니다.

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery

@{
    ViewData["Title"] = "JavaScript";

    var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}

<input id="RequestVerificationToken" type="hidden" value="@requestToken" />

<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>

@section Scripts {
<script>
    document.addEventListener("DOMContentLoaded", () => {
        const resultElement = document.getElementById("result");

        document.getElementById("button").addEventListener("click", async () => {

            const response = await fetch("@Url.Action("FetchEndpoint")", {
                method: "POST",
                headers: {
                    RequestVerificationToken:
                        document.getElementById("RequestVerificationToken").value
                }
            });

            if (response.ok) {
                resultElement.innerText = await response.text();
            } else {
                resultElement.innerText = `Request Failed: ${response.status}`
            }
        });
    });
</script>
}

앞의 예제에서는 JavaScript를 사용하여 AJAX POST 헤더에 대한 숨겨진 필드 값을 읽습니다.

이 접근 방식을 사용하면 서버에서 cookie를 설정하거나 클라이언트에서 읽어오는 작업을 직접 진행하지 않아도 됩니다. 그러나 IAntiforgery 서비스를 삽입할 수 없는 경우 JavaScript를 사용하여 cookie의 토큰에 액세스합니다.

  • 서버에 대한 추가 요청의 액세스 토큰(일반적으로 same-origin)입니다.
  • cookie 콘텐츠를 사용하여 토큰 값이 있는 헤더를 만듭니다.

스크립트가 X-XSRF-TOKEN이라는 헤더에서 토큰을 보내도록 요청하는 경우 X-XSRF-TOKEN 헤더를 찾도록 위조 방지 서비스를 구성합니다.

builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

다음 예제에서는 JavaScript에서 읽을 수 있는 cookie에 요청 토큰을 쓰는 보호된 엔드포인트를 추가합니다.

app.UseAuthorization();
app.MapGet("antiforgery/token", (IAntiforgery forgeryService, HttpContext context) =>
{
    var tokens = forgeryService.GetAndStoreTokens(context);
    context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
            new CookieOptions { HttpOnly = false });

    return Results.Ok();
}).RequireAuthorization();

다음 예제에서는 JavaScript를 사용하여 AJAX 요청을 수행하여 토큰을 가져오고 적절한 헤더를 사용하여 다른 요청을 수행합니다.

var response = await fetch("/antiforgery/token", {
    method: "GET",
    headers: { "Authorization": authorizationToken }
});

if (response.ok) {
    // https://developer.mozilla.org/docs/web/api/document/cookie
    const xsrfToken = document.cookie
        .split("; ")
        .find(row => row.startsWith("XSRF-TOKEN="))
        .split("=")[1];

    response = await fetch("/JavaScript/FetchEndpoint", {
        method: "POST",
        headers: { "X-XSRF-TOKEN": xsrfToken, "Authorization": authorizationToken }
    });

    if (response.ok) {
        resultElement.innerText = await response.text();
    } else {
        resultElement.innerText = `Request Failed: ${response.status}`
    }
} else {    
    resultElement.innerText = `Request Failed: ${response.status}`
}

참고 항목

요청 헤더와 양식 페이로드 모두에서 위조 방지 토큰이 제공되면 헤더의 토큰만 유효성을 검사합니다.

최소 API를 사용하는 위조 방지

Minimal APIs는 포함된 필터(ValidateAntiForgeryToken, AutoValidateAntiforgeryToken, IgnoreAntiforgeryToken)의 사용을 지원하지 않습니다. 그러나 IAntiforgery는 요청의 유효성을 검사하는 데 필요한 API를 제공합니다.

다음 예제에서는 위조 방지 토큰의 유효성을 검사하는 필터를 만듭니다.

internal static class AntiForgeryExtensions
{
    public static TBuilder ValidateAntiforgery<TBuilder>(this TBuilder builder) where TBuilder : IEndpointConventionBuilder
    {
        return builder.AddEndpointFilter(routeHandlerFilter: async (context, next) =>
        {
            try
            {
                var antiForgeryService = context.HttpContext.RequestServices.GetRequiredService<IAntiforgery>();
                await antiForgeryService.ValidateRequestAsync(context.HttpContext);
            }
            catch (AntiforgeryValidationException)
            {
                return Results.BadRequest("Antiforgery token validation failed.");
            }

            return await next(context);

        });
    }
}

그런 다음, 필터를 엔드포인트에 적용할 수 있습니다.

app.MapPost("api/upload", (IFormFile name) => Results.Accepted())
    .RequireAuthorization()
    .ValidateAntiforgery();

Windows 인증 및 위조 방지 cookie

Windows 인증을 사용하는 경우 애플리케이션 엔드포인트는 cookie에 대해 수행되는 것과 동일한 방식으로 CSRF 공격으로부터 보호되어야 합니다. 브라우저는 서버에 인증 컨텍스트를 암시적으로 전송하며 CSRF 공격으로부터 엔드포인트를 보호해야 합니다.

위조 방지 확장

IAntiforgeryAdditionalDataProvider 형식을 사용하면 개발자가 각 토큰의 추가 데이터를 라운드트립하여 CSRF 방지 시스템의 동작을 확장할 수 있습니다. 필드 GetAdditionalData 토큰이 생성될 때마다 메서드가 호출되고 반환 값이 생성된 토큰 내에 포함됩니다. 구현자는 타임스탬프, nonce 또는 기타 값을 반환한 다음 토큰의 유효성을 검사할 때 이 데이터의 유효성을 검사하기 위해 호출 ValidateAdditionalData 할 수 있습니다. 클라이언트의 사용자 이름이 이미 생성된 토큰에 포함되어 있으므로 이 정보를 포함할 필요가 없습니다. 토큰에 보조 데이터가 포함되어 있지만 IAntiForgeryAdditionalDataProvider가 구성되지 않은 경우 보조 데이터가 유효한지 검사되지 않습니다.

추가 리소스

교차 사이트 요청 위조(XSRF 또는 CSRF라고도 함)는 악의적인 웹앱이 클라이언트 브라우저와 해당 브라우저를 신뢰하는 웹앱 간의 상호 작용에 영향을 줄 수 있는 웹 호스팅 앱에 대한 공격입니다. 웹 브라우저에서 웹 사이트에 대한 모든 요청과 함께 특정 형식의 인증 토큰을 자동으로 보내기 때문에 이러한 공격이 가능합니다. 이러한 형태의 악용은 공격이 이전에 인증된 사용자 세션을 활용하기 때문에 ‘원클릭 공격’ 또는 ‘세션 라이딩’이라고도 합니다.

CSRF 공격의 예는 다음과 같습니다.

  1. 사용자가 폼 인증을 사용하여 www.good-banking-site.example.com에 로그인합니다. 서버가 사용자를 인증하고 인증 cookie를 포함하는 응답을 발급합니다. 사이트가 수신하는 모든 요청을 유효한 인증 cookie로 신뢰하기 때문에 공격에 취약합니다.

  2. 사용자가 악의적인 사이트인 www.bad-crook-site.example.com을 방문합니다.

    악의적인 사이트인 www.bad-crook-site.example.com이 다음 예제와 유사한 HTML 폼을 포함합니다.

    <h1>Congratulations! You're a Winner!</h1>
    <form action="https://good-banking-site.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
        <input type="submit" value="Click to collect your prize!" />
    </form>
    

    폼의 action이 악의적인 사이트가 아닌 취약한 사이트에 게시합니다. CSRF의 “교차 사이트” 부분입니다.

  3. 사용자가 제출 단추를 선택합니다. 브라우저가 요청을 만들고 요청된 도메인 www.good-banking-site.example.com에 대한 인증 cookie를 자동으로 포함합니다.

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

사용자가 폼을 제출하는 단추를 선택하는 시나리오 외에도 악의적인 사이트는 다음과 같은 일을 할 수 있습니다.

  • 폼을 자동으로 제출하는 스크립트를 실행합니다.
  • 폼 제출을 AJAX 요청으로 보냅니다.
  • CSS를 사용하여 폼을 숨깁니다.

이러한 대체 시나리오에서는 처음에 악성 사이트를 방문하는 것 외에 사용자의 다른 작업 또는 입력이 필요하지 않습니다.

HTTPS를 사용할 경우 CSRF 공격이 방지되지 않습니다. 악의적인 사이트는 비보안 요청을 보낼 수 있는 것만큼 쉽게 https://www.good-banking-site.com/ 요청을 보낼 수 있습니다.

일부 공격은 GET 요청에 응답하는 엔드포인트를 대상으로 하며, 이 경우 이미지 태그를 사용하여 작업을 수행할 수 있습니다. 이러한 형태의 공격은 이미지를 허용하지만 JavaScript를 차단하는 포럼 사이트에서 일반적입니다. 변수 또는 리소스가 변경되는 GET 요청에 대해 상태를 변경하는 앱은 악의적인 공격에 취약합니다. 상태를 변경하는 GET 요청은 안전하지 않습니다. GET 요청의 상태를 변경하지 않는 것이 가장 좋습니다.

CSRF 공격은 다음과 같은 이유로 인증에 cookie를 사용하는 웹앱에서 가능합니다.

  • 브라우저는 웹앱에서 발급한 cookie를 저장합니다.
  • 저장된 cookie에는 인증된 사용자에 대한 세션 cookie가 포함됩니다.
  • 브라우저는 브라우저에서 앱에 대한 요청이 생성된 방식에 관계 없이 모든 요청에 대해 도메인에 연결된 모든 cookie를 웹앱으로 보냅니다.

그러나 CSRF 공격은 cookie를 악용하는 것만으로 제한되지 않습니다. 예를 들어, 기본 및 다이제스트 인증에도 취약해집니다. 사용자가 기본 또는 다이제스트 인증을 사용하여 로그인한 후에는 세션이 종료될 때까지 브라우저에서 자동으로 자격 증명을 보냅니다.

이 컨텍스트 에서 세션 은 사용자가 인증되는 동안 클라이언트 쪽 세션을 참조합니다. 서버 쪽 세션 또는 ASP.NET Core 세션 미들웨어와는 관련이 없습니다.

사용자는 예방 조치를 취하여 CSRF 취약성으로부터 보호할 수 있습니다.

  • 웹앱 사용을 마치면 웹앱에서 로그아웃합니다.
  • 브라우저의 cookie를 정기적으로 지웁니다.

그러나 CSRF 취약점은 기본적으로 최종 사용자가 아닌 웹앱의 문제입니다.

인증 기본 사항

Cookie 기반 인증은 널리 사용되는 인증 형식입니다. 토큰 기반 인증 시스템은 특히 SPA(단일 페이지 애플리케이션)에서 널리 사용됩니다.

사용자가 사용자 이름 및 암호를 사용하여 인증을 받는 경우 인증 및 권한 부여에 사용할 수 있는 인증 티켓이 포함된 토큰이 발급됩니다. 토큰은 클라이언트에서 수행하는 모든 요청과 함께 전송되는 cookie로 저장됩니다. 이 cookie를 생성하고 유효성을 검사하는 과정은 Cookie 인증 미들웨어에서 수행됩니다. 미들웨어는 사용자 보안 주체를 암호화된 cookie로 serialize합니다. 후속 요청에서 미들웨어는 cookie의 유효성을 검사하고, 보안 주체를 다시 만들고, 보안 주체를 HttpContext.User 속성에 할당합니다.

토큰 기반 인증

사용자가 인증되면 토큰(위조 방지 토큰이 아님)이 발급됩니다. 이 토큰에는 클레임 형식의 사용자 정보 또는 앱에서 유지 관리되는 사용자 상태를 가리키는 참조 토큰이 포함됩니다. 사용자가 인증을 요구하는 리소스에 액세스하려고 하면 토큰은 전달자 토큰의 형태로 추가 권한 부여 헤더를 사용하여 앱으로 전송됩니다. 이 방법은 앱을 비정상 상태로 만듭니다. 각 후속 요청에서 토큰은 서버 쪽 유효성 검사에 대한 요청에 전달됩니다. 이 토큰은 ‘암호화’되지 않고 ‘인코딩됩’니다. 서버에서 토큰은 해당 정보에 액세스하기 위해 디코딩됩니다. 후속 요청에서 토큰을 보내려면 브라우저의 로컬 스토리지에 토큰을 저장합니다. 토큰이 브라우저의 로컬 스토리지에 저장된 경우 CSRF 취약성에 대해 걱정하지 마세요. CSRF는 토큰을 cookie에 저장하는 경우에 문제가 됩니다. 자세한 내용은 GitHub 이슈 SPA 코드 샘플이 두 개의 cookie를 추가함을 참조하세요.

여러 앱이 한 도메인에 호스트됨

공유 호스팅 환경은 세션 하이재킹, 로그인 CSRF 및 기타 공격에 취약합니다.

example1.contoso.netexample2.contoso.net은 서로 다른 호스트이지만 *.contoso.net 도메인 아래의 호스트 간에 암시적 트러스트 관계가 있습니다. 이 암시적 트러스트 관계를 사용하면 신뢰할 수 없는 호스트가 서로의 cookie에 영향을 줄 수 있습니다(AJAX 요청을 제어하는 동일 원본 정책이 반드시 HTTP cookie에 적용되는 것은 아님).

동일한 도메인에서 호스트되는 앱 간에 트러스트된 cookie를 활용하는 공격은 도메인을 공유하지 않도록 하여 방지할 수 있습니다. 각 앱이 자체 도메인에서 호스트되는 경우에는 악용할 암시적 cookie 트러스트 관계가 없습니다.

ASP.NET Core 위조 방지

Warning

ASP.NET Core는 ASP.NET Core 데이터 보호를 사용하여 위조 방지를 구현합니다. 데이터 보호 스택을 서버 팜에서 작동하도록 구성해야 합니다. 자세한 내용은 데이터 보호 구성을 참조하세요.

Program.cs에서 다음 API 중 하나가 호출될 때 위조 방지 미들웨어가 종속성 주입 컨테이너에 추가됩니다.

FormTagHelper는 위조 방지 토큰을 HTML 양식 요소에 삽입합니다. Razor 파일의 다음 태그는 위조 방지 토큰을 자동으로 생성합니다.

<form method="post">
    <!-- ... -->
</form>

마찬가지로 양식 IHtmlHelper.BeginForm 의 메서드가 GET이 아닌 경우 기본적으로 위조 방지 토큰을 생성합니다.

HTML 폼 요소에 대한 위조 방지 토큰 자동 생성은 <form> 태그에 method="post" 특성이 포함되어 있고 다음 중 하나에 해당하는 경우에 발생합니다.

  • action 특성이 비어 있는 경우(action="")
  • action 특성이 제공되지 않은 경우(<form method="post">)

다음과 같이 HTML 폼 요소에 대한 위조 방지 토큰 자동 생성을 사용하지 않도록 설정할 수 있습니다.

  • asp-antiforgery 특성을 사용하여 위조 방지 토큰을 명시적으로 사용하지 않도록 설정합니다.

    <form method="post" asp-antiforgery="false">
        <!-- ... -->
    </form>
    
  • 폼 요소는 태그 도우미 ! 옵트아웃 기호을 사용하여 태그 도우미에서 옵트아웃됩니다.

    <!form method="post">
        <!-- ... -->
    </!form>
    
  • 뷰에서 FormTagHelper를 제거합니다. Razor 뷰에 다음 지시문을 추가하여 뷰에서 FormTagHelper를 제거할 수 있습니다.

    @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
    

참고 항목

Razor Pages는 XSRF/CSRF에서 자동으로 보호됩니다. 자세한 내용은 XSRF/CSRF 및 Razor Pages를 참조하세요.

CSRF 공격으로부터 보호하는 가장 일반적인 방법은 STP(‘동기화 장치 토큰 패턴’)를 사용하는 것입니다. 사용자가 폼 데이터를 사용하여 페이지를 요청하면 STP가 사용됩니다.

  1. 서버는 현재 사용자의 ID와 연결된 토큰을 클라이언트에 보냅니다.
  2. 클라이언트에서 확인을 위해 토큰을 서버에 다시 보냅니다.
  3. 서버가 인증된 사용자의 ID와 일치하지 않는 토큰을 받으면 요청이 거부됩니다.

토큰은 고유하며 예측할 수 없습니다. 토큰을 사용하여 일련의 요청을 적절하게 시퀀싱할 수도 있습니다. 예를 들어 페이지 1 > 페이지 2 > 페이지 3 요청 시퀀스 보장할 수 있습니다. ASP.NET Core MVC 및 Razor Pages 템플릿의 모든 폼은 위조 방지 토큰을 생성합니다. 다음 뷰 예제 쌍은 위조 방지 토큰을 생성합니다.

<form asp-action="Index" asp-controller="Home" method="post">
    <!-- ... -->
</form>

@using (Html.BeginForm("Index", "Home"))
{
    <!-- ... -->
}

HTML 도우미 @Html.AntiForgeryToken에서 태그 도우미를 사용하지 않고 <form> 요소에 위조 방지 토큰을 명시적으로 추가합니다.

<form asp-action="Index" asp-controller="Home" method="post">
    @Html.AntiForgeryToken()

    <!-- ... -->
</form>

위의 각 경우에 ASP.NET Core는 다음 예제와 유사한 숨겨진 폼 필드를 추가합니다.

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">

ASP.NET Core에는 위조 방지 토큰을 사용하기 위한 다음과 같은 세 가지 필터가 포함되어 있습니다.

AddControllers를 사용하는 위조 방지

AddControllers를 호출해도 위조 방지 토큰이 활성화되지 않습니다. 기본 제공 위조 방지 토큰을 지원하려면 AddControllersWithViews를 호출해야 합니다.

여러 브라우저 탭 및 동기화 장치 토큰 패턴

동기화 장치 토큰 패턴을 사용하면 가장 최근에 로드된 페이지에만 유효한 위조 방지 토큰이 포함됩니다. 여러 탭을 사용하는 것은 문제가 될 수 있습니다. 예를 들어 사용자가 여러 탭을 여는 경우:

  • 가장 최근에 로드된 탭에만 유효한 위조 방지 토큰이 포함되어 있습니다.
  • 이전에 로드된 탭에서 수행된 요청은 Antiforgery token validation failed. The antiforgery cookie token and request token do not match 오류와 함께 실패합니다.

문제가 발생하면 대체 CSRF 보호 패턴을 고려합니다.

AntiforgeryOptions을 사용하여 위조 방지 구성

Program.cs에서 AntiforgeryOptions 사용자 지정:

builder.Services.AddAntiforgery(options =>
{
    // Set Cookie properties using CookieBuilder properties†.
    options.FormFieldName = "AntiforgeryFieldname";
    options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
    options.SuppressXFrameOptionsHeader = false;
});

다음 테이블과 같이 CookieBuilder 클래스의 속성을 사용하여 위조 방지 Cookie 속성을 설정합니다.

옵션 설명
Cookie 위조 방지 cookie를 만드는 데 사용되는 설정을 결정합니다.
FormFieldName 위조 방지 시스템이 뷰에서 위조 방지 토큰을 렌더링하는 데 사용하는 숨겨진 폼 필드의 이름입니다.
HeaderName 위조 방지 시스템에서 사용하는 헤더의 이름입니다. null이면 시스템은 폼 데이터만 고려합니다.
SuppressXFrameOptionsHeader X-Frame-Options 헤더 생성을 표시하지 않을지 여부를 지정합니다. 기본적으로 헤더는 “SAMEORIGIN” 값을 사용하여 생성됩니다. 기본값은 false입니다.

자세한 내용은 CookieAuthenticationOptions를 참조하세요.

IAntiforgery을 사용하여 위조 방지 토큰 생성

IAntiforgery 는 위조 방지 기능을 구성하는 API를 제공합니다. WebApplication.Services을 사용하여 Program.cs에서 IAntiforgery를 요청할 수 있습니다. 다음 예제에서는 앱의 홈페이지에서 미들웨어를 사용하여 위조 방지 토큰을 생성하고 응답에 cookie로 보냅니다.

app.UseRouting();

app.UseAuthorization();

var antiforgery = app.Services.GetRequiredService<IAntiforgery>();

app.Use((context, next) =>
{
    var requestPath = context.Request.Path.Value;

    if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
        || string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
    {
        var tokenSet = antiforgery.GetAndStoreTokens(context);
        context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
            new CookieOptions { HttpOnly = false });
    }

    return next(context);
});

앞의 예제에서는 이름이 XSRF-TOKEN인 cookie을 설정합니다. 클라이언트는 이 cookie를 읽고 해당 값을 AJAX 요청에 연결된 헤더로 제공할 수 있습니다. 예를 들어 Angular에는 기본적으로 XSRF-TOKEN로 명명된 cookie을 읽는 기본 제공 XSRF 보호가 포함되어 있습니다.

위조 방지 유효성 검사 필요

ValidateAntiForgeryToken 작업 필터는 개별 작업, 컨트롤러 또는 전역적으로 적용될 수 있습니다. 이 필터가 적용된 작업에 대한 요청은 유효한 위조 방지 토큰이 포함되지 않으면 차단됩니다.

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
    // ...

    return RedirectToAction();
}

ValidateAntiForgeryToken 특성에는 HTTP GET 요청을 포함하여 특성이 표시하는 작업 메서드에 대한 요청을 위해 토큰이 필요합니다. ValidateAntiForgeryToken 특성을 앱의 컨트롤러에서 적용하는 경우 IgnoreAntiforgeryToken 특성을 사용하여 재정의할 수 있습니다.

안전하지 않은 HTTP 메서드에 대한 위조 방지 토큰의 유효성 자동 검사

ValidateAntiForgeryToken 특성을 광범위하게 적용한 다음, IgnoreAntiforgeryToken 특성을 사용하여 재정의하는 대신, AutoValidateAntiforgeryToken 특성을 사용할 수 있습니다. 이 특성은 ValidateAntiForgeryToken 특성과 동일하게 작동합니다. 단, 다음 HTTP 메서드를 사용하여 생성된 요청의 경우는 토큰이 필요하지 않습니다.

  • GET
  • HEAD
  • OPTIONS
  • TRACE

비 API 시나리오에는 AutoValidateAntiforgeryToken을 광범위하게 사용하는 것이 좋습니다. 이 특성을 통해 POST 작업은 기본적으로 보호됩니다. 대신, ValidateAntiForgeryToken이 개별 작업 메서드에 적용되지 않는 한, 기본적으로 위조 방지 토큰을 무시하는 것이 좋습니다. 이 시나리오에서는 POST 작업 메서드가 실수로 보호되지 않은 상태가 될 수 있으며 이로 인해 앱이 CSRF 공격에 취약해질 수 있습니다. 모든 POST는 위조 방지 토큰을 전송해야 합니다.

API에는 토큰에서 cookie 이외 부분을 보내기 위한 자동 메커니즘이 없습니다. 해당 구현은 클라이언트 코드 구현에 따라 달라질 수 있습니다. 몇 가지 예제는 다음과 같습니다.

클래스 수준 예제:

[AutoValidateAntiforgeryToken]
public class HomeController : Controller

전역적 예제:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});

전역 또는 컨트롤러 위조 방지 특성 재정의

IgnoreAntiforgeryToken 필터는 지정된 작업(또는 컨트롤러)에 대해 위조 방지 토큰이 필요하지 않도록 하는 데 사용됩니다. 적용될 경우 이 필터는 상위 수준(전역적 또는 컨트롤러)에 지정된 ValidateAntiForgeryTokenAutoValidateAntiforgeryToken 필터를 재정의합니다.

[IgnoreAntiforgeryToken]
public IActionResult IndexOverride()
{
    // ...

    return RedirectToAction();
}

인증 후 토큰 새로 고침

사용자를 뷰 또는 Razor Pages 페이지로 리디렉션하여 인증한 후에 토큰을 새로 고쳐야 합니다.

JavaScript, AJAX 및 SPA

기존의 HTML 기반 앱에서 위조 방지 토큰은 숨겨진 폼 필드를 사용하여 서버로 전달됩니다. 최신 JavaScript 기반 앱과 SPA에서 많은 요청은 프로그래밍 방식으로 수행됩니다. 이러한 AJAX 요청은 다른 기술(예: 요청 헤더 또는 cookie)을 사용하여 토큰을 보낼 수 있습니다.

cookie를 사용하여 인증 토큰을 저장하고 서버에서 API 요청을 인증하는 경우 CSRF는 잠재적인 문제가 됩니다. 로컬 스토리지를 사용하여 토큰을 저장하는 경우 로컬 스토리지의 값이 모든 요청에서 서버에 자동으로 전송되지는 않으므로 CSRF 취약성이 완화될 수 있습니다. 로컬 저장소를 사용하여 클라이언트에 위조 방지 토큰을 저장하고 토큰을 요청 헤더로 전송하는 것이 좋습니다.

JavaScript

뷰에서 JavaScript를 사용하면 뷰 내에서 서비스를 사용하여 토큰을 만들 수 있습니다. 뷰에 IAntiforgery 서비스를 삽입하고 다음을 호출 GetAndStoreTokens합니다.

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery

@{
    ViewData["Title"] = "JavaScript";

    var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}

<input id="RequestVerificationToken" type="hidden" value="@requestToken" />

<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>

@section Scripts {
<script>
    document.addEventListener("DOMContentLoaded", () => {
        const resultElement = document.getElementById("result");

        document.getElementById("button").addEventListener("click", async () => {

            const response = await fetch("@Url.Action("FetchEndpoint")", {
                method: "POST",
                headers: {
                    RequestVerificationToken:
                        document.getElementById("RequestVerificationToken").value
                }
            });

            if (response.ok) {
                resultElement.innerText = await response.text();
            } else {
                resultElement.innerText = `Request Failed: ${response.status}`
            }
        });
    });
</script>
}

앞의 예제에서는 JavaScript를 사용하여 AJAX POST 헤더에 대한 숨겨진 필드 값을 읽습니다.

이 접근 방식을 사용하면 서버에서 cookie를 설정하거나 클라이언트에서 읽어오는 작업을 직접 진행하지 않아도 됩니다. 그러나 IAntiforgery 서비스를 삽입할 수 없는 경우 JavaScript는 서버(일반적으로 same-origin)에 대한 추가 요청에서 가져온 cookie의 토큰에 액세스하고 cookie의 콘텐츠를 사용하여 토큰 값으로 헤더를 만들 수도 있습니다.

스크립트가 X-XSRF-TOKEN이라는 헤더에서 토큰을 보내도록 요청하는 경우 X-XSRF-TOKEN 헤더를 찾도록 위조 방지 서비스를 구성합니다.

builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

다음 예제에서는 JavaScript에서 읽을 수 있는 cookie에 요청 토큰을 쓰는 보호된 엔드포인트를 추가합니다.

app.UseAuthorization();
app.MapGet("antiforgery/token", (IAntiforgery forgeryService, HttpContext context) =>
{
    var tokens = forgeryService.GetAndStoreTokens(context);
    context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
            new CookieOptions { HttpOnly = false });

    return Results.Ok();
}).RequireAuthorization();

다음 예제에서는 JavaScript를 사용하여 AJAX 요청을 수행하여 토큰을 가져오고 적절한 헤더를 사용하여 다른 요청을 수행합니다.

var response = await fetch("/antiforgery/token", {
    method: "GET",
    headers: { "Authorization": authorizationToken }
});

if (response.ok) {
    // https://developer.mozilla.org/docs/web/api/document/cookie
    const xsrfToken = document.cookie
        .split("; ")
        .find(row => row.startsWith("XSRF-TOKEN="))
        .split("=")[1];

    response = await fetch("/JavaScript/FetchEndpoint", {
        method: "POST",
        headers: { "X-XSRF-TOKEN": xsrfToken, "Authorization": authorizationToken }
    });

    if (response.ok) {
        resultElement.innerText = await response.text();
    } else {
        resultElement.innerText = `Request Failed: ${response.status}`
    }
} else {    
    resultElement.innerText = `Request Failed: ${response.status}`
}

Windows 인증 및 위조 방지 cookie

Windows 인증을 사용하는 경우 애플리케이션 엔드포인트는 cookie에 대해 수행되는 것과 동일한 방식으로 CSRF 공격으로부터 보호되어야 합니다. 브라우저는 서버에 인증 컨텍스트를 암시적으로 전송하며 CSRF 공격으로부터 엔드포인트를 보호해야 합니다.

위조 방지 확장

IAntiforgeryAdditionalDataProvider 형식을 사용하면 개발자가 각 토큰의 추가 데이터를 라운드트립하여 CSRF 방지 시스템의 동작을 확장할 수 있습니다. 필드 GetAdditionalData 토큰이 생성될 때마다 메서드가 호출되고 반환 값이 생성된 토큰 내에 포함됩니다. 구현자는 타임스탬프, nonce 또는 기타 값을 반환한 다음 토큰의 유효성을 검사할 때 이 데이터의 유효성을 검사하기 위해 호출 ValidateAdditionalData 할 수 있습니다. 클라이언트의 사용자 이름이 이미 생성된 토큰에 포함되어 있으므로 이 정보를 포함할 필요가 없습니다. 토큰에 보조 데이터가 포함되어 있지만 IAntiForgeryAdditionalDataProvider가 구성되지 않은 경우 보조 데이터가 유효한지 검사되지 않습니다.

추가 리소스

교차 사이트 요청 위조(XSRF 또는 CSRF라고도 함)는 악의적인 웹앱이 클라이언트 브라우저와 해당 브라우저를 신뢰하는 웹앱 간의 상호 작용에 영향을 줄 수 있는 웹 호스팅 앱에 대한 공격입니다. 웹 브라우저에서 웹 사이트에 대한 모든 요청과 함께 특정 형식의 인증 토큰을 자동으로 보내기 때문에 이러한 공격이 가능합니다. 이러한 형태의 악용은 공격이 이전에 인증된 사용자 세션을 활용하기 때문에 ‘원클릭 공격’ 또는 ‘세션 라이딩’이라고도 합니다.

CSRF 공격의 예는 다음과 같습니다.

  1. 사용자가 폼 인증을 사용하여 www.good-banking-site.example.com에 로그인합니다. 서버가 사용자를 인증하고 인증 cookie를 포함하는 응답을 발급합니다. 사이트가 수신하는 모든 요청을 유효한 인증 cookie로 신뢰하기 때문에 공격에 취약합니다.

  2. 사용자가 악의적인 사이트인 www.bad-crook-site.example.com을 방문합니다.

    악의적인 사이트인 www.bad-crook-site.example.com이 다음 예제와 유사한 HTML 폼을 포함합니다.

    <h1>Congratulations! You're a Winner!</h1>
    <form action="https://good-banking-site.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
        <input type="submit" value="Click to collect your prize!" />
    </form>
    

    폼의 action이 악의적인 사이트가 아닌 취약한 사이트에 게시합니다. CSRF의 “교차 사이트” 부분입니다.

  3. 사용자가 제출 단추를 선택합니다. 브라우저가 요청을 만들고 요청된 도메인 www.good-banking-site.example.com에 대한 인증 cookie를 자동으로 포함합니다.

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

사용자가 폼을 제출하는 단추를 선택하는 시나리오 외에도 악의적인 사이트는 다음과 같은 일을 할 수 있습니다.

  • 폼을 자동으로 제출하는 스크립트를 실행합니다.
  • 폼 제출을 AJAX 요청으로 보냅니다.
  • CSS를 사용하여 폼을 숨깁니다.

이러한 대체 시나리오에서는 처음에 악성 사이트를 방문하는 것 외에 사용자의 다른 작업 또는 입력이 필요하지 않습니다.

HTTPS를 사용할 경우 CSRF 공격이 방지되지 않습니다. 악의적인 사이트는 비보안 요청을 보낼 수 있는 것만큼 쉽게 https://www.good-banking-site.com/ 요청을 보낼 수 있습니다.

일부 공격은 GET 요청에 응답하는 엔드포인트를 대상으로 하며, 이 경우 이미지 태그를 사용하여 작업을 수행할 수 있습니다. 이러한 형태의 공격은 이미지를 허용하지만 JavaScript를 차단하는 포럼 사이트에서 일반적입니다. 변수 또는 리소스가 변경되는 GET 요청에 대해 상태를 변경하는 앱은 악의적인 공격에 취약합니다. 상태를 변경하는 GET 요청은 안전하지 않습니다. GET 요청의 상태를 변경하지 않는 것이 가장 좋습니다.

CSRF 공격은 다음과 같은 이유로 인증에 cookie를 사용하는 웹앱에서 가능합니다.

  • 브라우저는 웹앱에서 발급한 cookie를 저장합니다.
  • 저장된 cookie에는 인증된 사용자에 대한 세션 cookie가 포함됩니다.
  • 브라우저는 브라우저에서 앱에 대한 요청이 생성된 방식에 관계 없이 모든 요청에 대해 도메인에 연결된 모든 cookie를 웹앱으로 보냅니다.

그러나 CSRF 공격은 cookie를 악용하는 것만으로 제한되지 않습니다. 예를 들어, 기본 및 다이제스트 인증에도 취약해집니다. 사용자가 기본 또는 다이제스트 인증을 사용하여 로그인한 후에는 세션이 종료될 때까지 브라우저에서 자동으로 자격 증명을 보냅니다.

이 컨텍스트 에서 세션 은 사용자가 인증되는 동안 클라이언트 쪽 세션을 참조합니다. 서버 쪽 세션 또는 ASP.NET Core 세션 미들웨어와는 관련이 없습니다.

사용자는 예방 조치를 취하여 CSRF 취약성으로부터 보호할 수 있습니다.

  • 웹앱 사용을 마치면 웹앱에서 로그아웃합니다.
  • 브라우저의 cookie를 정기적으로 지웁니다.

그러나 CSRF 취약점은 기본적으로 최종 사용자가 아닌 웹앱의 문제입니다.

인증 기본 사항

Cookie 기반 인증은 널리 사용되는 인증 형식입니다. 토큰 기반 인증 시스템은 특히 SPA(단일 페이지 애플리케이션)에서 널리 사용됩니다.

사용자가 사용자 이름 및 암호를 사용하여 인증을 받는 경우 인증 및 권한 부여에 사용할 수 있는 인증 티켓이 포함된 토큰이 발급됩니다. 토큰은 클라이언트에서 수행하는 모든 요청과 함께 전송되는 cookie로 저장됩니다. 이 cookie를 생성하고 유효성을 검사하는 과정은 Cookie 인증 미들웨어에서 수행됩니다. 미들웨어는 사용자 보안 주체를 암호화된 cookie로 serialize합니다. 후속 요청에서 미들웨어는 cookie의 유효성을 검사하고, 보안 주체를 다시 만들고, 보안 주체를 HttpContext.User 속성에 할당합니다.

토큰 기반 인증

사용자가 인증되면 토큰(위조 방지 토큰이 아님)이 발급됩니다. 이 토큰에는 클레임 형식의 사용자 정보 또는 앱에서 유지 관리되는 사용자 상태를 가리키는 참조 토큰이 포함됩니다. 사용자가 인증을 요구하는 리소스에 액세스하려고 하면 토큰은 전달자 토큰의 형태로 추가 권한 부여 헤더를 사용하여 앱으로 전송됩니다. 이 방법은 앱을 비정상 상태로 만듭니다. 각 후속 요청에서 토큰은 서버 쪽 유효성 검사에 대한 요청에 전달됩니다. 이 토큰은 ‘암호화’되지 않고 ‘인코딩됩’니다. 서버에서 토큰은 해당 정보에 액세스하기 위해 디코딩됩니다. 후속 요청에서 토큰을 보내려면 브라우저의 로컬 스토리지에 토큰을 저장합니다. 토큰이 브라우저의 로컬 스토리지에 저장된 경우 CSRF 취약성에 대해 걱정하지 마세요. CSRF는 토큰을 cookie에 저장하는 경우에 문제가 됩니다. 자세한 내용은 GitHub 이슈 SPA 코드 샘플이 두 개의 cookie를 추가함을 참조하세요.

여러 앱이 한 도메인에 호스트됨

공유 호스팅 환경은 세션 하이재킹, 로그인 CSRF 및 기타 공격에 취약합니다.

example1.contoso.netexample2.contoso.net은 서로 다른 호스트이지만 *.contoso.net 도메인 아래의 호스트 간에 암시적 트러스트 관계가 있습니다. 이 암시적 트러스트 관계를 사용하면 신뢰할 수 없는 호스트가 서로의 cookie에 영향을 줄 수 있습니다(AJAX 요청을 제어하는 동일 원본 정책이 반드시 HTTP cookie에 적용되는 것은 아님).

동일한 도메인에서 호스트되는 앱 간에 트러스트된 cookie를 활용하는 공격은 도메인을 공유하지 않도록 하여 방지할 수 있습니다. 각 앱이 자체 도메인에서 호스트되는 경우에는 악용할 암시적 cookie 트러스트 관계가 없습니다.

ASP.NET Core 위조 방지 구성

Warning

ASP.NET Core는 ASP.NET Core 데이터 보호를 사용하여 위조 방지를 구현합니다. 데이터 보호 스택을 서버 팜에서 작동하도록 구성해야 합니다. 자세한 내용은 데이터 보호 구성을 참조하세요.

Startup.ConfigureServices에서 다음 API 중 하나가 호출될 때 위조 방지 미들웨어가 종속성 주입 컨테이너에 추가됩니다.

ASP.NET Core 2.0 이상에서 FormTagHelper는 위조 방지 토큰을 HTML 폼 요소에 삽입합니다. Razor 파일의 다음 태그는 위조 방지 토큰을 자동으로 생성합니다.

<form method="post">
    ...
</form>

마찬가지로 양식 IHtmlHelper.BeginForm 의 메서드가 GET이 아닌 경우 기본적으로 위조 방지 토큰을 생성합니다.

HTML 폼 요소에 대한 위조 방지 토큰 자동 생성은 <form> 태그에 method="post" 특성이 포함되어 있고 다음 중 하나에 해당하는 경우에 발생합니다.

  • action 특성이 비어 있는 경우(action="")
  • action 특성이 제공되지 않은 경우(<form method="post">)

다음과 같이 HTML 폼 요소에 대한 위조 방지 토큰 자동 생성을 사용하지 않도록 설정할 수 있습니다.

  • asp-antiforgery 특성을 사용하여 위조 방지 토큰을 명시적으로 사용하지 않도록 설정합니다.

    <form method="post" asp-antiforgery="false">
        ...
    </form>
    
  • 폼 요소는 태그 도우미 ! 옵트아웃 기호을 사용하여 태그 도우미에서 옵트아웃됩니다.

    <!form method="post">
        ...
    </!form>
    
  • 뷰에서 FormTagHelper를 제거합니다. Razor 뷰에 다음 지시문을 추가하여 뷰에서 FormTagHelper를 제거할 수 있습니다.

    @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
    

참고 항목

Razor Pages는 XSRF/CSRF에서 자동으로 보호됩니다. 자세한 내용은 XSRF/CSRF 및 Razor Pages를 참조하세요.

CSRF 공격으로부터 보호하는 가장 일반적인 방법은 STP(‘동기화 장치 토큰 패턴’)를 사용하는 것입니다. 사용자가 폼 데이터를 사용하여 페이지를 요청하면 STP가 사용됩니다.

  1. 서버는 현재 사용자의 ID와 연결된 토큰을 클라이언트에 보냅니다.
  2. 클라이언트에서 확인을 위해 토큰을 서버에 다시 보냅니다.
  3. 서버가 인증된 사용자의 ID와 일치하지 않는 토큰을 받으면 요청이 거부됩니다.

토큰은 고유하며 예측할 수 없습니다. 토큰을 사용하여 일련의 요청을 적절하게 시퀀싱할 수도 있습니다. 예를 들어 페이지 1 > 페이지 2 > 페이지 3 요청 시퀀스 보장할 수 있습니다. ASP.NET Core MVC 및 Razor Pages 템플릿의 모든 폼은 위조 방지 토큰을 생성합니다. 다음 뷰 예제 쌍은 위조 방지 토큰을 생성합니다.

<form asp-controller="Todo" asp-action="Create" method="post">
    ...
</form>

@using (Html.BeginForm("Create", "Todo"))
{
    ...
}

HTML 도우미 @Html.AntiForgeryToken에서 태그 도우미를 사용하지 않고 <form> 요소에 위조 방지 토큰을 명시적으로 추가합니다.

<form action="/" method="post">
    @Html.AntiForgeryToken()
</form>

위의 각 경우에 ASP.NET Core는 다음 예제와 유사한 숨겨진 폼 필드를 추가합니다.

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">

ASP.NET Core에는 위조 방지 토큰을 사용하기 위한 다음과 같은 세 가지 필터가 포함되어 있습니다.

위조 방지 옵션

Startup.ConfigureServices에서 AntiforgeryOptions 사용자 지정:

services.AddAntiforgery(options => 
{
    options.FormFieldName = "AntiforgeryFieldname";
    options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
    options.SuppressXFrameOptionsHeader = false;
});

다음 테이블과 같이 CookieBuilder 클래스의 속성을 사용하여 위조 방지 Cookie 속성을 설정합니다.

옵션 설명
Cookie 위조 방지 cookie를 만드는 데 사용되는 설정을 결정합니다.
FormFieldName 위조 방지 시스템이 뷰에서 위조 방지 토큰을 렌더링하는 데 사용하는 숨겨진 폼 필드의 이름입니다.
HeaderName 위조 방지 시스템에서 사용하는 헤더의 이름입니다. null이면 시스템은 폼 데이터만 고려합니다.
SuppressXFrameOptionsHeader X-Frame-Options 헤더 생성을 표시하지 않을지 여부를 지정합니다. 기본적으로 헤더는 “SAMEORIGIN” 값을 사용하여 생성됩니다. 기본값은 false입니다.

자세한 내용은 CookieAuthenticationOptions를 참조하세요.

IAntiforgery를 사용하여 위조 방지 기능 구성

IAntiforgery 는 위조 방지 기능을 구성하는 API를 제공합니다. IAntiforgeryStartup 클래스의 Configure 메서드에서 요청할 수 있습니다.

다음 예제에서

  • 앱의 홈페이지에서 미들웨어를 사용하여 위조 방지 토큰을 생성하고 응답으로 cookie으로 보냅니다.
  • 요청 토큰은 AngularJS 섹션에 설명된 기본 Angular 명명 규칙을 사용하여 JavaScript에서 읽을 수 있는 cookie것으로 전송됩니다.
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
    app.Use(next => context =>
    {
        string path = context.Request.Path.Value;

        if (string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
        {
            var tokens = antiforgery.GetAndStoreTokens(context);
            context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, 
                new CookieOptions() { HttpOnly = false });
        }

        return next(context);
    });
}

위조 방지 유효성 검사 필요

ValidateAntiForgeryToken은 개별 작업, 컨트롤러 또는 전역적으로 적용될 수 있는 작업 필터입니다. 이 필터가 적용된 작업에 대한 요청은 유효한 위조 방지 토큰이 포함되지 않으면 차단됩니다.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel account)
{
    ManageMessageId? message = ManageMessageId.Error;
    var user = await GetCurrentUserAsync();

    if (user != null)
    {
        var result = 
            await _userManager.RemoveLoginAsync(
                user, account.LoginProvider, account.ProviderKey);

        if (result.Succeeded)
        {
            await _signInManager.SignInAsync(user, isPersistent: false);
            message = ManageMessageId.RemoveLoginSuccess;
        }
    }

    return RedirectToAction(nameof(ManageLogins), new { Message = message });
}

ValidateAntiForgeryToken 특성에는 HTTP GET 요청을 포함하여 특성이 표시하는 작업 메서드에 대한 요청을 위해 토큰이 필요합니다. ValidateAntiForgeryToken 특성을 앱의 컨트롤러에서 적용하는 경우 IgnoreAntiforgeryToken 특성을 사용하여 재정의할 수 있습니다.

참고 항목

ASP.NET Core는 GET 요청에 위조 방지 토큰을 자동으로 추가하는 것을 지원하지 않습니다.

안전하지 않은 HTTP 메서드에 대한 위조 방지 토큰의 유효성 자동 검사

ASP.NET Core 앱은 안전한 HTTP 메서드(GET, HEAD, OPTIONS 및 TRACE)에 대해 위조 방지 토큰을 생성하지 않습니다. ValidateAntiForgeryToken 특성을 광범위하게 적용한 다음, IgnoreAntiforgeryToken 특성을 사용하여 재정의하는 대신, AutoValidateAntiforgeryToken 특성을 사용할 수 있습니다. 이 특성은 ValidateAntiForgeryToken 특성과 동일하게 작동합니다. 단, 다음 HTTP 메서드를 사용하여 생성된 요청의 경우는 토큰이 필요하지 않습니다.

  • GET
  • HEAD
  • OPTIONS
  • TRACE

비 API 시나리오에는 AutoValidateAntiforgeryToken을 광범위하게 사용하는 것이 좋습니다. 이 특성을 통해 POST 작업은 기본적으로 보호됩니다. 대신, ValidateAntiForgeryToken이 개별 작업 메서드에 적용되지 않는 한, 기본적으로 위조 방지 토큰을 무시하는 것이 좋습니다. 이 시나리오에서는 POST 작업 메서드가 실수로 보호되지 않은 상태가 될 수 있으며 이로 인해 앱이 CSRF 공격에 취약해질 수 있습니다. 모든 POST는 위조 방지 토큰을 전송해야 합니다.

API에는 토큰에서 cookie 이외 부분을 보내기 위한 자동 메커니즘이 없습니다. 해당 구현은 클라이언트 코드 구현에 따라 달라질 수 있습니다. 몇 가지 예제는 다음과 같습니다.

클래스 수준 예제:

[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{

전역적 예제:

services.AddControllersWithViews(options =>
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));

전역 또는 컨트롤러 위조 방지 특성 재정의

IgnoreAntiforgeryToken 필터는 지정된 작업(또는 컨트롤러)에 대해 위조 방지 토큰이 필요하지 않도록 하는 데 사용됩니다. 적용될 경우 이 필터는 상위 수준(전역적 또는 컨트롤러)에 지정된 ValidateAntiForgeryTokenAutoValidateAntiforgeryToken 필터를 재정의합니다.

[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{
    [HttpPost]
    [IgnoreAntiforgeryToken]
    public async Task<IActionResult> DoSomethingSafe(SomeViewModel model)
    {
        // no antiforgery token required
    }
}

인증 후 토큰 새로 고침

사용자를 뷰 또는 Razor Pages 페이지로 리디렉션하여 인증한 후에 토큰을 새로 고쳐야 합니다.

JavaScript, AJAX 및 SPA

기존의 HTML 기반 앱에서 위조 방지 토큰은 숨겨진 폼 필드를 사용하여 서버로 전달됩니다. 최신 JavaScript 기반 앱과 SPA에서 많은 요청은 프로그래밍 방식으로 수행됩니다. 이러한 AJAX 요청은 다른 기술(예: 요청 헤더 또는 cookie)을 사용하여 토큰을 보낼 수 있습니다.

cookie를 사용하여 인증 토큰을 저장하고 서버에서 API 요청을 인증하는 경우 CSRF는 잠재적인 문제가 됩니다. 로컬 스토리지를 사용하여 토큰을 저장하는 경우 로컬 스토리지의 값이 모든 요청에서 서버에 자동으로 전송되지는 않으므로 CSRF 취약성이 완화될 수 있습니다. 로컬 저장소를 사용하여 클라이언트에 위조 방지 토큰을 저장하고 토큰을 요청 헤더로 전송하는 것이 좋습니다.

JavaScript

뷰에서 JavaScript를 사용하면 뷰 내에서 서비스를 사용하여 토큰을 만들 수 있습니다. 뷰에 IAntiforgery 서비스를 삽입하고 다음을 호출 GetAndStoreTokens합니다.

@{
    ViewData["Title"] = "AJAX Demo";
}
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
    public string GetAntiXsrfRequestToken()
    {
        return Xsrf.GetAndStoreTokens(Context).RequestToken;
    }
}

<input type="hidden" id="RequestVerificationToken" 
       name="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">

<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<div class="row">
    <p><input type="button" id="antiforgery" value="Antiforgery"></p>
    <script>
        var xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange = function() {
            if (xhttp.readyState == XMLHttpRequest.DONE) {
                if (xhttp.status == 200) {
                    alert(xhttp.responseText);
                } else {
                    alert('There was an error processing the AJAX request.');
                }
            }
        };

        document.addEventListener('DOMContentLoaded', function() {
            document.getElementById("antiforgery").onclick = function () {
                xhttp.open('POST', '@Url.Action("Antiforgery", "Home")', true);
                xhttp.setRequestHeader("RequestVerificationToken", 
                    document.getElementById('RequestVerificationToken').value);
                xhttp.send();
            }
        });
    </script>
</div>

이 접근 방식을 사용하면 서버에서 cookie를 설정하거나 클라이언트에서 읽어오는 작업을 직접 진행하지 않아도 됩니다.

앞의 예제에서는 JavaScript를 사용하여 AJAX POST 헤더에 대한 숨겨진 필드 값을 읽습니다.

JavaScript는 cookie에서 토큰에 액세스하고 cookie의 내용을 사용하여 토큰 값을 사용하여 헤더를 만들 수도 있습니다.

context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken, 
    new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = false });

스크립트가 X-CSRF-TOKEN이라는 헤더에서 토큰을 보내도록 요청하는 경우 X-CSRF-TOKEN 헤더를 찾도록 위조 방지 서비스를 구성합니다.

services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");

다음 예제에서는 JavaScript를 사용하여 적절한 헤더로 AJAX 요청을 만듭니다.

function getCookie(cname) {
    var name = cname + "=";
    var decodedCookie = decodeURIComponent(document.cookie);
    var ca = decodedCookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) === ' ') {
            c = c.substring(1);
        }
        if (c.indexOf(name) === 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}

var csrfToken = getCookie("CSRF-TOKEN");

var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
    if (xhttp.readyState === XMLHttpRequest.DONE) {
        if (xhttp.status === 204) {
            alert('Todo item is created successfully.');
        } else {
            alert('There was an error processing the AJAX request.');
        }
    }
};
xhttp.open('POST', '/api/items', true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.setRequestHeader("X-CSRF-TOKEN", csrfToken);
xhttp.send(JSON.stringify({ "name": "Learn C#" }));

AngularJS

AngularJS는 규칙을 사용하여 CSRF를 처리합니다. 서버에서 이름이 XSRF-TOKEN인 cookie를 보내면 AngularJS$http 서비스는 서버에 요청을 보낼 때 헤더에 cookie 값을 추가합니다. 이 프로세스는 자동으로 진행됩니다. 클라이언트는 헤더를 명시적으로 설정할 필요가 없습니다. 헤더 이름은 X-XSRF-TOKEN입니다. 서버에서 이 헤더를 검색하고 해당 내용이 유효한지 검사해야 합니다.

ASP.NET Core API가 애플리케이션 시작 시 이 규칙을 따르도록 하려면 다음을 수행합니다.

  • XSRF-TOKEN라는 cookie에서 토큰을 제공하도록 앱을 구성합니다.
  • XSRF 토큰을 보내기 위한 Angular 기본 헤더 이름인 X-XSRF-TOKEN라는 헤더를 찾도록 위조 방지 서비스를 구성합니다.
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
    app.Use(next => context =>
    {
        string path = context.Request.Path.Value;

        if (
            string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
        {
            var tokens = antiforgery.GetAndStoreTokens(context);
            context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, 
                new CookieOptions() { HttpOnly = false });
        }

        return next(context);
    });
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
}

참고 항목

요청 헤더와 양식 페이로드 모두에서 위조 방지 토큰이 제공되면 헤더의 토큰만 유효성을 검사합니다.

Windows 인증 및 위조 방지 cookie

Windows 인증을 사용하는 경우 애플리케이션 엔드포인트는 cookie에 대해 수행되는 것과 동일한 방식으로 CSRF 공격으로부터 보호되어야 합니다. 브라우저는 서버에 인증 컨텍스트를 암시적으로 전송하며 CSRF 공격으로부터 엔드포인트를 보호해야 합니다.

위조 방지 확장

IAntiforgeryAdditionalDataProvider 형식을 사용하면 개발자가 각 토큰의 추가 데이터를 라운드트립하여 CSRF 방지 시스템의 동작을 확장할 수 있습니다. 필드 GetAdditionalData 토큰이 생성될 때마다 메서드가 호출되고 반환 값이 생성된 토큰 내에 포함됩니다. 구현자는 타임스탬프, nonce 또는 기타 값을 반환한 다음 토큰의 유효성을 검사할 때 이 데이터의 유효성을 검사하기 위해 호출 ValidateAdditionalData 할 수 있습니다. 클라이언트의 사용자 이름이 이미 생성된 토큰에 포함되어 있으므로 이 정보를 포함할 필요가 없습니다. 토큰에 보조 데이터가 포함되어 있지만 IAntiForgeryAdditionalDataProvider가 구성되지 않은 경우 보조 데이터가 유효한지 검사되지 않습니다.

추가 리소스