Маркер cookie анти-подделки и маркер поля формы не совпадают при использовании WebApi

у меня есть одностраничное приложение (пользователь загружает кучу HTML/JS, а затем делает AJAX - запросы без другого вызова MVC-только через WebAPI). В WebAPI у меня есть следующее:

public sealed class WebApiValidateAntiForgeryTokenAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(
        System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        if (actionContext == null)
        {
            throw new ArgumentNullException(nameof(actionContext));
        }
        if (actionContext.Request.Method.Method == "POST")
        {
            string requestUri = actionContext.Request.RequestUri.AbsoluteUri.ToLower();
            if (uriExclusions.All(s => !requestUri.Contains(s, StringComparison.OrdinalIgnoreCase))) // place some exclusions here if needed
            {
                HttpRequestHeaders headers = actionContext.Request.Headers;

                CookieState tokenCookie = headers
                    .GetCookies()
                    .Select(c => c[AntiForgeryConfig.CookieName]) // __RequestVerificationToken
                    .FirstOrDefault();

                string tokenHeader = string.Empty;
                if (headers.Contains("X-XSRF-Token"))
                {
                    tokenHeader = headers.GetValues("X-XSRF-Token").FirstOrDefault();
                }

                AntiForgery.Validate(!string.IsNullOrEmpty(tokenCookie?.Value) ? tokenCookie.Value : null, tokenHeader);
            }

        }
        base.OnActionExecuting(actionContext); // this is where it throws
    }
}

зарегистрирован в Global.асакс:

    private static void RegisterWebApiFilters(HttpFilterCollection filters)
    {
        filters.Add(new WebApiValidateAntiForgeryTokenAttribute());
        filters.Add(new AddCustomHeaderFilter());
    }

иногда, я вижу The anti-forgery cookie token and form field token do not match ошибка в моих журналах. Когда это происходит, оба tokenCookie.value и tokenHeader не равны нулю.

Clientside, все мои запросы AJAX используют следующее:

beforeSend: function (request) {
     request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
},

С генерацией бритвы токен один раз на моей странице SPA:

@Html.AntiForgeryToken()

у меня есть ключ моей машины в Интернете.конфиг.

что может быть причиной этого?

обновление Я только что проверил журналы, и я вижу это иногда:

предоставленный токен защиты от подделки был предназначен для пользователя"", но текущий пользователь "someuser@domain.com". несколько секунд назад

это происходит, когда пользователь обновляет свой экземпляр SPA во время входа в систему. Затем спа-центр по какой-то причине опускает их на целевую страницу вместо внутренней страницы (User.Identity.IsAuthenticated is true) - тогда они не могут войти в систему из-за этой ошибки. Освежающий тянет их обратно внутрь. Не уверен, что это значит, но я решил, что больше информации не повредит.

приложение https://security.stackexchange.com/questions/167064/is-csrf-protection-useless-with-ajax/167076#167076

2 ответов


мой ответ будет рекомендовать не пытаться использовать защиту CSRF на основе токенов в вызовах AJAX, а полагаться на собственные функции CORS веб-браузера.

в принципе, любой вызов AJAX из браузера на серверный сервер будет проверять происхождение домена (он же домен, из которого был загружен скрипт). Если Домены совпадают (домен хостинга JS = = целевой домен сервера AJAX), вызовы AJAX выполняются нормально, в противном случае возвращается null.

Если злоумышленник пытается разместить вредоносный запрос AJAX на своем собственном сервере, он потерпит неудачу, если ваш сервер не имеет политики CORS, позволяющей ему это сделать (что имеет место по умолчанию).

Так, изначально, защита CSRF бесполезна в вызовах AJAX, а вы можете понизьте свой технический долг, просто не пытаясь справиться с этим.

подробнее о CORS-Mozilla Foundation

пример кода-используйте консоль инспектор!

<html>
<script>
function reqListener () {
  console.log(this.responseText);
}

var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "http://www.reuters.com/");
oReq.send();
</script>
</html>

запустите его и посмотрите на ошибку безопасности:

заблокирован запрос Cross-Origin: та же политика Origin запрещает чтение удаленный ресурс на http://www.reuters.com/. (Причина: заголовок CORS "Access-Control-Allow-Origin" отсутствует).

Mozilla довольно ясно, что касается Кросс-сайт XMLHttpRequest реализация:

современный браузеры поддерживают межсайтовые запросы, реализуя Web Управление доступом рабочей группы к приложениям (WebApps) для межсайтовых сетей Запросы стандартные.

пока сервер настроен на разрешение запросов из вашего интернета источник приложения, XMLHttpRequest будет работать. В противном случае INVALID_ACCESS_ERR создается исключение.


я пытаюсь дать ответ то же самое, также если в комментариях мы обмениваемся, ваш кажется не связанным сценарием с моим..

такой тип проблемы может быть из-за XMLHttpRequest.setRequestHeader() поведения, потому что эта функция "совместная " значения заголовка, который уже был назначен в контексте http-запроса, как указано MDN и WHATWG как средство:

если этот метод вызывается несколько раз с одним и тем же заголовком, значения объединяются в один заголовок запроса.

Итак, если у нас есть SPA например, который выполняет все ajax POSTs установка заданного HTTP-заголовка, в вашем случае:

beforeSend: function (request) {
     request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
}

первый ajax POST запрос устанавливает четкий заголовок ("X-XSRF-Token") и так, на стороне сервера, вы должны иметь "действительное" значение заголовка для сравнения.

но, в отсутствие обновления страницы или нового GET запрос, все последующие ajax POSTs, а также указано в MDN и WHATWG как средство документация, сделает грязное назначение того же заголовка ("X-XSRF-Token"), потому что они комбината новые значения с olds.

чтобы избежать этой проблемы, вы можете попробовать сбросить "X-XSRF-Token" значение (но на это не так много документации, и это кажется ненадежным решение...)

beforeSend: function (request) {
     request.setRequestHeader("X-XSRF-Token", null);      //depends on user agents..
     //OR.. request.setRequestHeader("X-XSRF-Token", ''); //other user agents..
     //OR.. request.setRequestHeader("X-XSRF-Token");     //other user agents..
     request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
}

другие решения могут полагаться на некоторый механизм передачи состояния на стороне клиента, который вы должны реализовать самостоятельно, потому что невозможно получить значения или доступ к состоянию заголовков http-запроса (доступны только заголовки ответов).

обновление - редакция следующего текста: Итак, если у нас есть SPA например, который выполняет все ajax POSTs переработка объекта XMLHttpRequest для каждого вызова и установки заданного HTTP-заголовка в вашем случае: ...