Бесплатные CMS > Установка и расширения PunBB > Подделка HTTP_REFERER

Функция confirm_referrer в PunBB

Общая информация: функция confirm_referrer делает дополнительную проверку откуда пришли данные.
В движке есть несколько десятков мест где она используется — это обработчики POST форм. Функция использует переменную $_SERVER['HTTP_REFERER'].

Недостатки: HTTP_REFERER успешно подделывается хакерами. На форумах с ЧПУ возникают сложности с указанием скрипта для проверки, т.к. есть варианты типа edit/99 или edit.php?id=99.

Решение: классики советуют использовать некое случайно генерируемое поле в самой форме. Обработчик посылки должен сравнивать значение этого поля с ожидаемым.

В PunBB 1.3 для этого используют поле csrf_token. Оно генерируется при авторизации пользователя и сохраняется в таблице online. Можно сказать, что csrf_token это некий идентификатор текущей сессии пользователя. Сессия и token действительны пока пользователь не сделает logout или по таймауту (15мин бездействия). Значение этого поля подставляется во все POST формы вот так:

<input type="hidden" name="csrf_token" value="'.$pun_user['csrf_token'].'" />

Недостаток этого подхода в том, что достаточно как-то украсть это значение в любом месте (через JS-внедрение) и можно подделать любую другую форму, пока текущая сессия не сэкспарилась. Например, злопыхатель воспользовался дыркой в ЛС послав админу письмо с javascript и тутже вызывает от его имени скрипты admin_* — поле csrf_token одинаково в любом месте.

Я предлагаю усилить защиту за счет генерации уникального поля для каждой формы. В проверяемую форму вставляет такой код:

<input type="hidden" name="csrf_hash" value="<?php echo csrf_hash() ?>"

Функция csrf_hash() использует $pun_user['csrf_token'] и адрес скрипта $_SERVER['SCRIPT_NAME']. Адрес скрипта неизменный в ЧПУ. Таким образом мы убиваем трех зайцев: скрываем online.csrf_token, делаем привязку к конкретной POST форме и оставляем все вызовы confirm_referer() как они есть

Код функции:

// Make sure that form posted from right place
//
function confirm_referrer($script)
{
global $pun_user, $lang_common, $base_url;

if (isset($_POST['csrf_hash']))
$hash = $_POST['csrf_hash'];
else if (isset($_GET['csrf_hash']))
$hash = $_GET['csrf_hash'];

// Hash not found
else
message($lang_common['Bad referrer']);

eregi('^(https?\://)(www\.)?(.*)$', $base_url, $regs);
$script = $regs[3].'/'.$script;
$new_hash = pun_hash($pun_user['csrf_token'].$script);

// Hash is wrong
if ($new_hash != $hash)
message($lang_common['Bad referrer']);
}

//
// Produce token for post validation
//
function csrf_hash()
{
global $pun_user;

eregi('^(www\.)?(.*)$', $_SERVER['HTTP_HOST'], $regs);
$script = $regs[2].$_SERVER['SCRIPT_NAME'];

return pun_hash($pun_user['csrf_token'].$script);
}

Example of Usage:

// in POST ot GET form, for ex. profile.php
<input type="hidden" name="csrf_hash" value="<?php echo csrf_hash() ?>" />
...
// in target script
confirm_referrer('profile.php');

Кстати, можно смело заменить $pun_user['csrf_token'] на вызов session_id(), тогда не понадобится заводить новое поле в таблице online (но тогда надо где-то в common.php стартовать сессию по session_start() - читайте мануалы PHP)

В 1.3 новый алгоритм csrf_token

Используется не открытый csrf_token (другими словами id сессии), а некий ключ, производный от адреса страницы и id сессии. Это гарантирует, что украденная и сохраненная страница не может служить отмычкой ко всем остальным страницам.

Есть принципиальное отличие от пары csrf_hash()+confirm_referrer(): используется адрес страницы-получателя, а не страницы-отправителя. Это позволяет ему оставить проверку в одном месте - в common.php и при этом все вроде довольно секьюрно получается!


Меню сайта

Функция confirm_referrer в PunBB
Функция confirm_referrer в PunBB