Защита веб-приложения на Phalcon + AngularJS от CSRF атак

    Привет всем! Не так давно столкнулся с проблемой защиты веб-приложения написанного на Phalcon PHP Framework вместе с AngularJS. Проблема заключалась в том что на странице есть несколько форм, которые посылают AJAX-запросы на сервер. Как подружить два фреймворка в вопросах безопасности, централизованного решения я не нашёл, пришлось его собирать по кусочкам из разных источников. И в этой статье я бы хотел предложить всем кто столкнулся, или столкнётся с такой проблемой, готовое рабочее решение.

    Генерируем токен в meta-теге


    К сожалению источника я сейчас не помню, но не раз замечал что между тегов частенько в meta лежали токены на крупных сайтах. Если вы посмотрите документацию Phalcon, то увидите что генерация токена происходит в форме. Вот так по-умолчанию генерируется токен в форме:

    <input type="hidden" name="<?php echo $this->security->getTokenKey() ?>" value="<?php echo $this->security->getToken() ?>"/>
    

    А что делать если формы две? На форуме было решение, но оно было с использованием сторонней библиотеки, что в моём случае не было хорошим решением, поэтому ещё немного порыскав на форуме, я нашёл решение генерировать токен в meta-теге.
    <meta name="token" title="<?= $this->security->getTokenKey() ?>" content="<?= $this->security->getToken() ?>"/>
    


    Крепим токен ко всем AJAX запросам


    Прочитав документацию AngularJS по работе с токенами, там предлагается передавать токен в заголовке с именем X-XSRF-TOKEN, но увы в Phalcon нужно было писать отдельную библиотеку для обработки таких токенов. У меня нет на это времени, я ленивый, поэтому пришлось найти другое более просто решение.

    var app = angular.module('selfmd', [], function ($httpProvider) 
    {
        $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
    
        $httpProvider.defaults.transformRequest = [function (data) {
            if (data === undefined) {
                data = {};
            }
            var token = $('meta[name=token]');
            data[token.attr('title')] = token.attr('content');
            return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data;
        }];
    
    }
    

    Помимо простоты, оно ещё и гибкое, ибо абсолютно все AJAX запросы, отправленные с помощью функции $http защищены токеном, который Phalcon легко и удобно воспринимает стандартными средствами.

     if ($this->security->checkToken()) 
    {
            //Токен верный
     }
    


    Возможные проблемы и решения


    1. Если человек захочет посмотреть исходный код страницы (не через firebug, а в отдельном окне), то при загрузке с генерируется новый токен, и вернувшись на страницу ни один запрос не будет обработан. Возможно это и хорошо, нечего лезть в исходный код.
    2. Если вдруг на странице не будет favicon, или же будет пустой background-image: url(""); то браузер запрашивая эти данные, с генерирует незаметно для нас новый токен. Я дня два или три потратил на то чтобы найти причину отказа токену, и меньше всего я мог подозревать блок с пустым background-image: url("");
    3. А как защитить формы без AJAX, и без $http? Очень просто!
    В контроллере добавляем новые скоупы:

        var token = $('meta[name=token]');
    
        $scope.token_id = token.attr('title');
        $scope.token_val = token.attr('content');
    

    А в вьюхе выводим их в скрытом поле:

    <input type="hidden" name="{{token_id}}" value="{{token_val}}"/>
    

    Теперь все формы защищены, Phalcon доволен привычным ему данным, и не задействует лишних библиотек, а Angular без проблем цепляем ко всем $http токен.

    Послесловие


    Я далеко не специалист по безопасности, я не гуру PHP, я просто решаю задачи, которые возникают когда я занимаюсь любимым делом. Я не нашёл такого же удобного и понятного решения, именно поэтому мне захотелось им поделится. Это решение я использую в реальном проекте, пока не было замечено проблем. Скорее всего вы в комментариях укажите мне на ошибки, и я буду вам благодарен. С Phalcon и AngularJS я работаю с августа этого года, до этого я работал с CodeIgniter и jQuery, поэтому сильно не судите за такое решение задачи, если оно оказалось не таким крутым, как мне казалось.

    Спасибо за внимание. Если вам интересно почитать про Phalcon, подписывайтесь, у меня есть ещё несколько полезных решений проблем при работе с ним.
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 21
      –2
      Интересно, человек предложил решение, а ему наставили минусов. За что?
        +2
        Я не граммар наци, но, возможно, за это и подобное:
        Ложим токен в meta данные

        Или за:
        А что делать если формы две? Идите на форум, ищите решение,

        Я лично, надеялся увидеть решение проблем Phalcon'овских токенов, а меня либо отправляют обратно на форум, либо констатируют известные проблемы с перегенерацией токенов в новом окне и т.п.

        А в целом, могу предложить автору (да и всем желающим) от себя редактуру текстов на общественных началах. Обращайтесь)

          –1
          Спасибо за фидбэк, не думал что тут всё так строго. Поправил текст.
            0
            Я не граммар наци, но, возможно, за это и подобное:

            замечание ни тебе лично, а так к слову…
            желательно было открыто или лично указать на недочеты в статье, а не минусовать без объяснений.
          0
          Работа с DOM-элементами в transformRequest? Уже столько об этом сказано, но нет, одни и те же грабли…
            0
            Простите, ни разу не сталкивался с этими граблями.
              +2
              Принято разделять отображение данных и работу с ними. Я имею в виду, что Ваш токен должен быть внутри модели, а Вы в методе бизнес-логики (transformRequest) выполняете поиск DOM-элемента, и берете данные из него. Следуя такому подходу мы получаем в итоге кашу из селекторов, кода ajax-запросов и т.д.
              Вы ведь используете Angular, разве не обратили внимание в туториалах как разделяется приложение на компоненты, контроллеры отдельно, шаблоны отдельно, русурсы отдельно.
                0
                Я согласен что нужно всё это разделять, но у меня много контроллеров, и моделей, в разных страницах используются разные, и чтобы везде это работало, нужно было дублировать код, а так я разместил в одном месте этот код, и всё, больше нигде ничего не надо.
                  +2
                  В приложении вы можете иметь глобальную configModel, с конфигурациями приложения. И в transformRequest уже из модели получать токен. Данные для этой модели брать от сервера или через тот же data-binding, если уж Вам хочется хранить токен в тэге.
                    –1
                    Спасибо, поставил бы плюсик, да кармы не хватает.
            0
            1. Если человек захочет посмотреть исходный код страницы (не через firebug, а в отдельном окне), то при загрузке с генерируется новый токен, и вернувшись на страницу ни один запрос не будет обработан. Возможно это и хорошо, нечего лезть в исходный код.


            да я думаю просто, если пользователь откроет ту же (а может даже и любую) страницу вашего приложения в новом табе, то в старом табе уже AJAX запросы работать не будут, верно?
              0
              Да верно, токен хранится в сессии пользователя.
                0
                А вы не ошибаетесь? Сессия не зря так названа, это серия запросов от одного пользователя, неважно из какого количества вкладок одного браузера (не считая режима «инкогнито»). Если бы токен основывался на сессии, то он был бы идентичен на протяжении всего сеанса пользования приложения.
                Поправьте, если ошибаюсь я.
              0
              Поправьте меня, пожалуйста, если я не прав. Мне кажется, что в AngularJS не спроста нету CSRF из коробки. Потому что когда вы делаете API, делать в нем аутентификацию при помощи кук это как-то странно ИМХО. Я думаю, лучше этот токен хранить где-то в Local Storage, например, и посылать его со всеми запросами в заголовках, тогда никакие CSRF будут не страшны.
                +1
                меня давно интересует эта проблема (без привязки к фреймеворку или языку)
                лучшее что я нашел auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/

                  0
                  Немного оффтоп, но все же. У вас есть серьезные причины не использовать шаблонизатор? Просто

                  <?php echo $this->blablaObject->getBlablaId(); ?>
                  

                  смотрится не очень в шаблоне.
                    0
                    Да есть причина. У AngularJS и шаблонизатора Volt Phalcon одинаковый синтаксис. Они оба используют {{переменные}} в двойных скобках. На форуме было решение, но мне проще использовать чистый PHP, да и он побыстрее будет ;)
                      0
                      Хм, по идее шаблонизатор должен уметь компилироваться и настраиваться.
                        0
                        angular.module('application').config(function ($interpolateProvider) {
                            $interpolateProvider.startSymbol('{[{');
                            $interpolateProvider.endSymbol('}]}');
                        });
                          0
                          angular.module('application').config(function ($interpolateProvider) {
                              $interpolateProvider.startSymbol('[[');
                              $interpolateProvider.endSymbol(']]');
                          });
                          


                          Делаю так, проще и приятнее глазу. Но это нюансы.
                      0
                      а почему не рассматривается решение через стандартный метод AngularJS?

                      https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection

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

                      Самое читаемое