2015-02-13 4 views
4

Я реализую API без состояния, и моя организация говорит, что мне нужно защитить от атак CSRF.Точка CSRF в заголовке и cookie не соответствует запросам

Я нашел решение этого парня в Интернете и решил попробовать реализовать на стороне клиента только подход: http://blog.jdriven.com/2014/10/stateless-spring-security-part-1-stateless-csrf-protection/

Вот что говорится на сайте, чтобы сделать для лица без решения (в случае, если сайт идет вниз):

  1. КЛИЕНТСКИЕ БОКОВЫЕ ПОКРЫТИЯ CSRF-TOKENS. Попросите клиентов сгенерировать и отправить одно и то же уникальное секретное значение как в Cookie, так и в пользовательский заголовок HTTP . Учитывая, что веб-сайт разрешен только для чтения/записи Cookie для собственного домена, только реальный сайт может отправлять такое же значение в обоих заголовках . Используя этот подход, все, что нужно сделать вашему серверу, это проверить, равны ли значения , равные для лиц без гражданства на запрос!

К сожалению, он не работает. Значение моего заголовка никогда не совпадает с моим значением cookie, и в некоторых случаях кажется, что мой заголовок - это всего лишь один запрос, соответствующий совпадению значения cookie.

Вот мой Угловой код:

app.config(['$httpProvider', function ($httpProvider) { 
    //fancy random token 
    function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e16]+1e16).replace(/[01]/g,b)}; 

    $httpProvider.defaults.xsrfHeaderName = 'X-CSRF-TOKEN'; 
    $httpProvider.defaults.xsrfCookieName = 'CSRF-TOKEN'; 

    $httpProvider.interceptors.push(function() { 
     return { 
      'request': function (config) { 
       document.cookie = 'CSRF-TOKEN=' + b(); 
       return config 
      } 
     }; 
    }); 
}]); 

Вот некоторые примеры значений CSRF, которые посылаются.

CSRF-TOKEN=d25cf03a985d575ad48a863eac91467666 
X-CSRF-TOKEN:fa1f165df8b27195a90f5e7841108f4e42 

CSRF-TOKEN=d25cf03a985d575ad48a863eac91467666 
X-CSRF-TOKEN:fa1f165df8b27195a90f5e7841108f4e42 

CSRF-TOKEN=9c8dd46ed06c250b707ac0cb80a08a23ac 
X-CSRF-TOKEN:d25cf03a985d575ad48a863eac91467666 

CSRF-TOKEN=eb407a0303c21173fe4d0ae03c97eaea6d 
X-CSRF-TOKEN:0cf066bf83e50b5c74cb932ab8a47c94e8 

CSRF-TOKEN=506355a940a2ac5b48f363712b34570d73 
X-CSRF-TOKEN:eb407a0303c21173fe4d0ae03c97eaea6d 

Что здесь происходит? Я чувствую, что делаю все, что есть в решении этого парня, но в итоге получаю странные результаты.

+0

> Учитывая, что веб-сайт разрешен только для чтения/записи Cookie для собственного домена, только реальный сайт может отправлять одно и то же значение в обоих заголовках. Что происходит, когда несколько веб-сайтов используют один и тот же домен? Разве это не позволяет вашему сайту подвергнуться нападению со стороны одного из других? Я полагаю, что если вы точно знаете, что ваш сайт будет единственным в домене, этот подход может работать. –

ответ

0

В результате я не отправил случайный токен, который был создан клиентской стороной с каждым запросом. Я не мог заставить его работать.

Так вот как я решил мою проблему (вид):

(1) По каждому запросу (включая первый), я отправить обратно из моего API куки в заголовках ответа с именем «XSRF -TOKEN "и рандомизированное значение, связанное с ним. Это имя, которое AngularJS ищет по умолчанию при использовании своей защиты CSRF.

(2) Что происходит в запросе после получения этого токена, является то, что AngularJS использует значение этого токена при отправке файла cookie в заголовки запроса под названием «XSRF-TOKEN», а также заголовок «X-XSRF-TOKEN», с этим значением токена.

Так что мой API обрабатывает рандомизацию токена XSRF, и мое приложение по-прежнему не имеет гражданства. Я использую Web API и использую глобальный фильтр для обработки этого маркера XSRF. Ниже приведен мой код (на C#) для этого. Я больше не имею какой-либо код обработку этого в интерфейсе (как это не кажется это будет необходимо):

public class ValidateAntiForgeryToken : ActionFilterAttribute 
{ 
    private const string XsrfCookieName = "XSRF-TOKEN"; 

    private const string XsrfHeaderName = "X-XSRF-TOKEN"; 

    private const string CsrfTokenSalt = "RANDOM SALT"; 


    public override void OnActionExecuting(HttpActionContext filterContext) 
    { 
     string requestMethod = filterContext.Request.Method.Method; 

     Boolean isValid = true; 

     if (requestMethod != "GET") 
     { 
      var headerToken = filterContext.Request.Headers.Where(x => x.Key.Equals(XsrfHeaderName, StringComparison.OrdinalIgnoreCase)) 
       .Select(x => x.Value).SelectMany(x => x).FirstOrDefault(); 

      var cookieToken = filterContext.Request.Headers.GetCookies().Select(x => x[XsrfCookieName]).FirstOrDefault(); 

      // check for missing cookie or header 
      if (cookieToken == null || headerToken == null) 
      { 
       isValid = false; 
      } 

      // ensure that the cookie matches the header 
      if (isValid && !String.Equals(headerToken, cookieToken.Value, StringComparison.OrdinalIgnoreCase)) 
      { 
       isValid = false; 
      } 

      if (!isValid) 
      { 
       filterContext.Response = filterContext.Request.CreateResponse(HttpStatusCode.Unauthorized); 
       filterContext.Response.ReasonPhrase = "Unauthorized to make that request."; 
       return; 
      } 
     } 

     base.OnActionExecuting(filterContext); 
    } 


    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) 
    { 
     string textToHash = RandomStringGeneration(); 
     string cookieText = HashService.HashText(textToHash, CsrfTokenSalt); 

     var cookie = new CookieHeaderValue(XsrfCookieName, HttpUtility.UrlEncode(cookieText)); 

     /* don't use this flag if you're not using HTTPS */ 
     cookie.Secure = true;  
     cookie.HttpOnly = false; // javascript needs to be able to get this in order to pass it back in the headers in the next request 

     /* if you have different environments on the same domain (which I did in one application using this code) make sure you set the path to be ApplicationPath of the request. Case sensitivity does matter in Chrome and IE, so be wary of that. */ 
     cookie.Path = "/"; 

     actionExecutedContext.Response.Headers.AddCookies(new[] { cookie }); 

     base.OnActionExecuted(actionExecutedContext); 
    } 
} 

Вот мой HashService.HashText (код):

public class HashService 
{ 
    public static string HashText(string text, string salt) 
    { 
     SHA512Managed hashString = new SHA512Managed(); 

     byte[] textWithSaltBytes = Encoding.UTF8.GetBytes(string.Concat(text, salt)); 
     byte[] hashedBytes = hashString.ComputeHash(textWithSaltBytes); 

     hashString.Clear(); 

     return Convert.ToBase64String(hashedBytes); 
    } 
} 

Надеется, что это помогает кто-то в будущем в приложениях без гражданства. К сожалению, токен, отправленный обратно, сравнивается только с самим собой в значении cookie и значении заголовка. Это единственный способ проверить это прямо сейчас (что я считаю довольно безопасным).Я могу создать целую новую таблицу для защиты XSRF и использовать ее для проверки того, что токен действительно тот, который должен использовать пользователь. Это единственный способ, с помощью которого я могу сосредоточиться, сохраняя свой API без гражданства.

Я наткнулся на это решение после прочтения HTTP документации $ для AngularJS, который диктует:

Запрос Cross Site Подделка (XSRF) Защита: XSRF является метод, с помощью который неавторизованный сайт может получить личные данные пользователя. Угловой обеспечивает механизм противодействия XSRF. При выполнении запросов XHR служба $ http считывает токен из файла cookie (по умолчанию XSRF-TOKEN) и устанавливает его как HTTP-заголовок (X-XSRF-TOKEN). Поскольку только JavaScript , который работает в вашем домене, может читать файл cookie, ваш сервер может быть заверил, что XHR получен из JavaScript, запущенного в вашем домене. Заголовок не будет установлен для междоменных запросов.

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

Название заголовков может быть задана с помощью xsrfHeaderName и xsrfCookieName свойства либо $ httpProvider.defaults на конфигурации времени, $ http.defaults во время выполнения, или каждого запроса конфигурации объект.

Смежные вопросы