재우니의 블로그

 

ASP.NET MVC 의 WEB API 에서 CSRF (Cross Site Request Forgery) 공격에 대한 대응 방안을 알아보고자 합니다.

먼저 FORM 태그 내부에 TOKEN 을 생성해야 합니다. Html.AntiForgeryToken() 함수를 통해 얻을 수 있습니다.

<form method="post" id="contact" novalidate name="contactForm">
  
@Html.AntiForgeryToken()

JQUERY 를 통해 ASP.NET MVC 의 WEB API 를 호출하고자 할 경우, 바인딩 된 TOKEN 을 headers 에 할당하여 전송합니다.

        function AutoSignIn() {

            var url = "/api/Login";
            var csrfToken = $("input[name='__RequestVerificationToken']").val();

            jQuery.ajax({
                headers: { __RequestVerificationToken: csrfToken },
                dataType: "json",
                url: url,
                async: false,
                type: 'POST',
                contentType: 'application/json; charset=utf-8'
            }).success(function (data) {
                location = "/Home/Index";
            }).fail(function (jqXHR, textStatus) {
                alert(jqXHR.statusText);
            });
        }

 

이제 web api 를 호출하기 앞서서, filter 를 만들어 validation 을 체크 하는게 controller 역할과 filter 역할을 나눠서 코딩하면 깔끔해 집니다.

아래는 filter 를 custom 화 하여 구현한 부분입니다. token 키값을 통해 값을 받아 존재 여부를 확인하고 AntiForgery.Validate() 함수에 값을 할당하면 됩니다. 없으면 에러 반환을 하도록 아래와 같이 구현합니다.


    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
    public sealed class ValidateAjaxAntiForgeryToken : ActionFilterAttribute
    {
        private const string HeaderTokenName = "__RequestVerificationToken";

        public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            if (actionContext == null)
            {
                throw new ArgumentNullException("actionContext");
            }

            var headers = actionContext.Request.Headers;

            IEnumerable<string> tokens;

            try
            {
                if (headers.TryGetValues(HeaderTokenName, out tokens))
                {
                    var headerToken = tokens.FirstOrDefault();
                    var cookie = headers.GetCookies().Select(c => c[AntiForgeryConfig.CookieName]).FirstOrDefault();
                    var cookieToken = cookie != null ? cookie.Value : null;
                    AntiForgery.Validate(cookieToken, headerToken);
                }
                else
                {
                    string msg = HttpStatusCode.ExpectationFailed.ToString() + " - " + HeaderTokenName;
                    throw new InvalidOperationException(msg);
                }
            }
            catch
            {
                actionContext.Response = new HttpResponseMessage
                {
                    RequestMessage = actionContext.ControllerContext.Request,
                    StatusCode = HttpStatusCode.Forbidden,
                    ReasonPhrase = "Token 값이 없습니다."
                };
            }

            base.OnActionExecuting(actionContext);
        }
    }

 

구현한 filter 에 대해 아래와 같이 atrribute 를 명시해서 실행합니다. 문제 없으면 아래 함수의 내용을 실행하겠죠.

[AllowAnonymous]
[Route("api/Login")]
[ValidateAjaxAntiForgeryToken]
public async Task<bool> LoginUser()
{

}