Перейти к содержимому

Данные от посетителя - экранирование, проверка, сеанс

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

Решение

Экранируем при выводе:

echo htmlspecialcharsbx($item['NAME']); // в текст страницы
echo '<a title="' . htmlspecialcharsbx($title) . '">'; // и в атрибут тоже
echo \Bitrix\Main\Text\HtmlFilter::encode($comment); // то же самое в D7
// экранируют на выводе: в базе значение остаётся таким, каким его ввели

Экранируют при выводе, а не при сохранении. Значение в базе остаётся исходным, и его можно показать в письме, отдать в выгрузку и сравнить с приехавшим из обмена без обратного разбора.

Подставляем значения в запрос безопасно:

$rows = SyncTable::getList(['filter' => ['=CODE' => $code]])->fetchAll(); // ORM сама
$id = (int)$request->get('id'); // числа приводим
$helper = \Bitrix\Main\Application::getConnection()->getSqlHelper();
$sql = "SELECT * FROM b_vendor WHERE CODE = '" . $helper->forSql($code) . "'";

Значения подставляют средствами платформы, а не склейкой строк. Фильтр ORM готовит значение сам, а в редком случае прямого запроса его пропускают через подготовку и приводят числа к числам.

Проверяем признак сеанса у формы:

// в форме
echo bitrix_sessid_post();
// в обработчике
if (!check_bitrix_sessid()) {
ShowError('Форма устарела, обновите страницу');
return;
}

Своя форма проверяет признак сеанса сама. Без него страницу с чужого сайта можно заставить отправить запрос от имени вошедшего сотрудника, и он об этом не узнает.

Проверяем загружаемый файл:

$file = $request->getFile('doc');
$mime = mime_content_type($file['tmp_name']); // тип по содержимому
if (!in_array($mime, ['application/pdf', 'image/jpeg'], true) || $file['size'] > 5_000_000) {
return 'Такой файл принять нельзя';
}
// имя файла и его расширение задаёт посетитель: доверять им нельзя

Тип файла определяют по содержимому, а не по имени. Расширение приходит от посетителя, и картинка с чужим скриптом внутри отличается от настоящей только содержимым.

Данные от посетителя проверяют на сервере, даже если их проверил браузер. Проверка на странице - это удобство для человека, а не защита: запрос отправляют и мимо формы.

HTML от контент-менеджера и HTML от покупателя - разные вещи. Первому его иногда разрешают, второму - никогда, и оба случая решаются на выводе, а не на входе.

Ошибку проверки стоит показывать понятной строкой. Молчаливый отказ приводит человека в поддержку, а «недопустимое значение поля» без имени поля - туда же.

Типичные проблемы

На странице появились чужие символы вместо кавычек.

Значение экранировано при сохранении и ещё раз при выводе. Экранирование делают только на выводе, и больше нигде в остальном коде проекта.

Фильтр по числу возвращает не то.

Значение пришло строкой и подставлено в запрос как есть. Числа приводят к числам сразу при чтении запроса.

Форма срабатывает с чужого сайта.

Признак сеанса в самой форме обработчиком никак и не проверяется вовсе нигде. Проверку признака сеанса ставят до всякой работы с пришедшими от посетителя данными.

В каталоге загрузок оказался скрипт.

Тип файла проверен по расширению из его имени. Имя задаёт посетитель, а тип определяют по содержимому.

Значение ломает вёрстку в атрибуте.

Вывод в атрибут сделан вообще без экранирования кавычек внутри самого этого значения. В атрибуте значение экранируют ровно так же, как и в обычном тексте.

Частые вопросы

Экранировать при сохранении или при выводе?

При выводе: в базе значение должно остаться исходным. Иначе оно испортится в письмах и выгрузках.

Что делать с HTML от контент-менеджера?

Разрешать точечно и только доверенным группам. Для покупателя HTML не разрешают вовсе.

Нужна ли проверка сеанса на запросах из браузера?

Да, для всего, что меняет данные. Штатные контроллеры делают это сами, свой файл - нет.

Достаточно ли проверки на странице?

Нет: запрос легко отправить мимо формы. Проверка на сервере обязательна всегда.

Смежное

Первоисточник