Безопасность ajax скриптов от прямого доступа

Рейтинг: 1Ответов: 4Опубликовано: 10.04.2015

Я параноик и для обеспечения дополнительной безопасности ajax запросов делаю следующее:

  • При заходе на страницу у пользователя генерируется сессия sshID в которую добавляется случайный sha1 хэш, хэш также сохраняется в переменную
  • Вместе с любым ajax запросом отправляется эта переменная
  • На сервере в каждом ajax скрипте проверяется соответствие полученной переменной _POST['sID'] с тем, что записано в сессию - sshID

Если значения совпадают - всё отлично, выполняем скрипт. Если нет - то сразу выкидываем пользователя. Я понимаю, что это весьма посредственная защита, но всё же, мне приятно, что любой кто просто попытается вызвать скрипт по адресу /ajax/название_скрипта.php будет отправлен куда подальше.

Проблема возникает в связи с тем, что сессии время от времени очищаются. Да, я могу поставить время жизни сессий около 24 часов и вероятность того, что кто-то просидит на сайте 24 часа, не обновит страницу, а потом решит отправить ajax запрос - "крайне мала" (Immolate Improved!), но, она есть.

Какие есть варианты выхода из этой проблемы? И может есть альтернативные пути как можно обезопасить свои скрипты от прямого доступа?

Ответы

▲ 1Принят

Чтобы осложнить XSS и CSRF атаки, в качестве секретного токена вы можете использовать nonce и хранить его не в сессии, а в базе. Такая техника, например, заложена в WordPress. На серверной стороне вам понадобятся функции генерации и проверки токена. Каждый токен относится только к какому-то одному действию, например, к сохранению сообщения или к аутентификации и к конкретному пользователю. А также имеет метку времени чтобы проверить на устаревание.
Интересно, что в теории nonce должен срабатывать только раз, но WP ослабили требования, в нем можно использовать сколько угодно раз но не дольше 24 часов.
Это упрощает работу с AJAX, где страница с пре-генеренным токеном может не обновляться после обращения к серверу. Иначе придется придумывать как получить новый токен вместо использованного.

Пусть у нас есть таблица токенов:

CREATE TABLE nonces(
  user_action VARCHAR(40) NOT NULL,
  token VARCHAR(40) NOT NULL,
  gen_time INT NOT NULL,
  PRIMARY KEY(user_action, token)
)

Мой псевдокод (простите за global, это только для краткости):

define('TIME12H', (12*60*60));

function new_nonce($action)
{
    global $db, $secret, $user_id;
    $time = intval(time() / TIME12H) * TIME12H; // округляем время до 12ч
    $token = sha1($secret.$action.$time);
    $db->query("REPLACE INTO nonces(user_action, token, gen_time) VALUES(?,?,?)", $action.'-'.$user_id, $token, $time);
    return $token;
}

function check_nonce($action, $nonce)
{
    global $db, $secret, $user_id;
    $time = $db->query("SELECT time FROM nonces WHERE user_action=? AND token=?", $action.'-'.$user_id, $nonce)->result();
    if (empty($time) || $time < time() - 2*TIME12H) {
        return false;
    }
    // здесь в теории должно быть удаление токена из базы
    return true;
}

Материалы для чтения:
http://en.wikipedia.org/wiki/Cross-site_request_forgery#Prevention https://codex.wordpress.org/WordPress_Nonces

▲ 1

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

Как защитить файлы от прямого запуска:

Все запросы в вашем приложении должны перекидываться на главный index, который в свою очередь будет подключать модули для get, post, ajax запросов в зависимости от поступающего запроса. Подобное перенаправление реализуется средствами htaccess. В этом индексе должна быть объявлена переменная или константа, не важно, которая должна проверяться в каждом модуле в самом начале.

Index:

define('included', true);

if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') 
{
    $method = 'ajax';
}
elseif($_SERVER['REQUEST_METHOD'] == 'POST')
{
    $method = 'post';
}
else
{
    $method = 'get';
}

include_once("нужный модуль");

Модуль аякса:

if(!defined('included'))
{
    die('Direct access not permitted');
}

Таким образом ваши модули-обработчики будут защищены от прямого запуска.

▲ 1

Единственный выход - как-то эту сессию обновлять. Самый простой и незатратный в плане разработки способ: невидимый iframe на страничку у которой установлено автообновление раз в 20-30 мин. Самый красивый и быстрый - то же самое, только через Ajax, приятным бонусом вы получите то, что сможете загружать какие-нибудь события с сервера для пользователя, например: "новые сообщения".

И немного добавлю по вашей защите. Лучше не писать пользователю, что доступ закрыт, лучше выводить заглушку, что все ок, все выполнилось. Так злоумышленник будет считать что он на правильном пути и скорее всего не станет проверять необходимость загрузки странички. Но это применимо только к скриптам, которые работают только через Ajax и не выводятся напрямую для пользователя.

▲ 1

Только комплекс мер может помочь в защите.

  1. Проверяйте Referer
  2. Записывайте данные в сессию
  3. Проверяйте каким методом приходит запрос
  4. Сверяйте user_agent
  5. Ограничивайте по времени следующий запрос ajax
  6. Посмотрите в сторону mod_unique.