Angular против Asp.Net WebApi, внедрить CSRF на сервере

Я реализую веб-сайт в Angular.js, который попадает в бэкэнд ASP.NET WebAPI.

Angular.js имеет встроенные функции для защиты от анти-csrf. В каждом HTTP-запросе он будет искать cookie под названием "XSRF-TOKEN" и отправит его в виде заголовка "X-XSRF-TOKEN".

Это зависит от того, как веб-сервер может установить cookie XSRF-TOKEN после аутентификации пользователя, а затем проверить заголовок X-XSRF-TOKEN для входящих запросов.

В документации Angular указано:

Чтобы воспользоваться этим, вашему серверу необходимо установить токен в JavaScript-читаемом сеансовом cookie под названием XSRF-TOKEN при первом запросе HTTP GET. При последующих не-GET-запросах сервер может проверить, соответствует ли файл cookie HTTP-заголовку X-XSRF-TOKEN, и поэтому убедитесь, что только JavaScript, работающий в вашем домене, мог прочитать токен. Маркер должен быть уникальным для каждого пользователя и должен быть проверен сервером (чтобы JavaScript не создавал свои собственные токены). Мы рекомендуем, чтобы токен являлся дайджестом cookie проверки подлинности вашего сайта с солью для дополнительной безопасности.

Я не мог найти хороших примеров этого для ASP.NET WebAPI, поэтому я применил свой собственный с помощью различных источников. Мой вопрос: может ли кто-нибудь увидеть что-то не так с кодом?

Сначала я определил простой вспомогательный класс:

public class CsrfTokenHelper
{
    const string ConstantSalt = "<ARandomString>";

    public string GenerateCsrfTokenFromAuthToken(string authToken)
    {
        return GenerateCookieFriendlyHash(authToken);
    }

    public bool DoesCsrfTokenMatchAuthToken(string csrfToken, string authToken) 
    {
        return csrfToken == GenerateCookieFriendlyHash(authToken);
    }

    private static string GenerateCookieFriendlyHash(string authToken)
    {
        using (var sha = SHA256.Create())
        {
            var computedHash = sha.ComputeHash(Encoding.Unicode.GetBytes(authToken + ConstantSalt));
            var cookieFriendlyHash = HttpServerUtility.UrlTokenEncode(computedHash);
            return cookieFriendlyHash;
        }
    }
}

Тогда у меня есть следующий метод в моем контроллере авторизации, и я вызываю его после вызова FormsAuthentication.SetAuthCookie():

    // http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-(csrf)-attacks
    // http://docs.angularjs.org/api/ng.$http
    private void SetCsrfCookie()
    {
        var authCookie = HttpContext.Current.Response.Cookies.Get(".ASPXAUTH");
        Debug.Assert(authCookie != null, "authCookie != null");
        var csrfToken = new CsrfTokenHelper().GenerateCsrfTokenFromAuthToken(authCookie.Value);
        var csrfCookie = new HttpCookie("XSRF-TOKEN", csrfToken) {HttpOnly = false};
        HttpContext.Current.Response.Cookies.Add(csrfCookie);
    }

Затем у меня есть пользовательский атрибут, который я могу добавить в контроллеры, чтобы заставить их проверить заголовок csrf:

public class CheckCsrfHeaderAttribute : AuthorizeAttribute
{
    //  http://stackoverflow.com/info/11725988/problems-implementing-validatingantiforgerytoken-attribute-for-web-api-with-mvc
    protected override bool IsAuthorized(HttpActionContext context)
    {
        // get auth token from cookie
        var authCookie = HttpContext.Current.Request.Cookies[".ASPXAUTH"];
        if (authCookie == null) return false;
        var authToken = authCookie.Value;

        // get csrf token from header
        var csrfToken = context.Request.Headers.GetValues("X-XSRF-TOKEN").FirstOrDefault();
        if (String.IsNullOrEmpty(csrfToken)) return false;

        // Verify that csrf token was generated from auth token
        // Since the csrf token should have gone out as a cookie, only our site should have been able to get it (via javascript) and return it in a header. 
        // This proves that our site made the request.
        return new CsrfTokenHelper().DoesCsrfTokenMatchAuthToken(csrfToken, authToken);
    }
}

Наконец, я очищаю токен Csrf, когда пользователь выходит из системы:

HttpContext.Current.Response.Cookies.Remove("XSRF-TOKEN");

Можно ли выявить какие-либо очевидные (или не столь очевидные) проблемы с этим подходом?

Ответ 1

У меня не было проблем с кодом, поэтому я рассматриваю вопрос.

Ответ 2

Ваш код выглядит нормально. Единственное, что вам больше не нужно, так как web.api запускает "сверху" asp.net mvc, а последний имеет поддержку токенов анти-подделки.

В комментариях dbrunning и ccorrin выражают озабоченность тем, что вы можете использовать только сборки в токенах AntiForgery только при использовании помощников HTML MVC. Это не верно. Помощники могут просто выставить пару токенов, основанных на сеансе, которые вы можете проверить друг против друга. Подробнее см. Ниже.

UPDATE:

В AntiForgery можно использовать два метода:

  • AntiForgery.GetTokens использует два параметра для возврата маркера cookie и токена формы

  • AntiForgery.Validate(cookieToken, formToken) проверяет правильность пары токенов

Вы полностью можете перепрофилировать эти два метода и использовать formToken как headerToken и cookieToken как фактический cookieToken. Затем просто вызовите подтверждение для обоих атрибутов.

Другим решением является использование JWT (например, MembershipReboot)

Эта ссылка показывает, как использовать встроенные антиблокировочные токены с помощью ajax:

<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>


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);
}

Также взгляните на этот вопрос AngularJS не может найти файл cookie XSRF-TOKEN

Ответ 3

Я думаю, что ваш код испорчен. Вся идея предотвращения CSRF заключается в том, чтобы предотвратить уникальный токен на каждом запросе, а не на каждом сеансе. Если токен анти-подделки является сохраненным значением сеанса, способность выполнять CSRF по-прежнему сохраняется. Вам нужно предоставить уникальный токен по каждому запросу...

Ответ 4

Это решение небезопасно, поскольку атаки CSRF все еще возможны, если файл cookie Auth действителен. Как auth, так и xsrf cookie будут отправлены на сервер, когда злоумышленник заставит вас выполнить запрос через другой сайт, и поэтому вы все еще уязвимы, пока пользователь не выполнит "жесткий" выход из системы.

Каждый запрос или сеанс должен иметь свой собственный уникальный токен, чтобы действительно предотвращать атаки CRSF. Но, вероятно, лучшим решением является не использование аутентификации на основе файлов cookie, а аутентификации на основе токенов, таких как OAuth. Это не позволяет другим веб-сайтам использовать ваши файлы cookie для выполнения нежелательных запросов, поскольку маркеры используются в заголовках http вместо файлов cookie. И заголовки http не отправляются автоматически.

Эти превосходные сообщения в блоге содержат информацию о том, как реализовать OAuth для WebAPI. Сообщения в блоге также содержат большую информацию о том, как интегрировать его с AngularJS.

Другим решением может быть отключить CORS и принимать только входящие запросы из белых доменов. Однако это не будет работать для приложений, не относящихся к веб-сайтам, таких как мобильные и/или настольные клиенты. Рядом с тем, как только ваш сайт уязвим для атаки XSS, злоумышленник все равно сможет подделать запросы на ваше поведение.