Какой аспект является решающим для большинства заказчиков? С большой долей вероятности можно сказать, что это стоимость услуги.
Если исполнители называют цену в районе 200-300 долларов, а потом возникает кто-то и берёт ту же работу за 100 долларов, заказчик тает и радостно соглашается. Мало того, после работы ещё оставит хвалебный отзыв и не забудет попенять остальным, мол, вот какие жадные, в 3 раза больше денег просили, нажиться хотели, сволочи.
Но так ли всё просто на самом деле?
Несколько лет назад вкратце рассказывал об основных видах инъекций: PHP, SQL и XSS. Они актуальны и по сей день, даже в бо́льшей степени — количество людей, пытающихся постичь таинственный мир программинга, растёт. Повальное желание обучаться по видео ведёт к низкому качеству кода. Беда не в том, что пишут «не красиво», беда в другом: отсутствие представления о принципах работы в целом.
Простой пример: приложение получает параметр из формы. Пусть это будет число (возраст) из текстового поля. В строке браузера запрос выглядит примерно так:
domain/index.php?age=15
Допустим, мы ожидаем людей, старше 16 лет. Поэтому в единственном поле оставляем возраст — 15, и пишем текст с предложением «подрасти».
Как эту задачу обработает не опытный программист? Скорее всего, в лоб:
$age = $_GET['age'];
После чего выведет значение «как есть» в текстовое поле:
<input name="age" value="<?= $age ?>">
Всё хорошо? Допустим. Теперь изменим запрос к скрипту на такой:
domain/index.php?age="><script>alert(‘hello’);</alert>
HTML-код будет выглядеть так:
<input name="age" value=""><script>alert('hello');</alert>">
Здравствуй, XSS! В данном случае ничего страшного не происходит — выводится только сообщение hello в диалоге. Но код может быть не такой безобидный, например, текущие значения Cookie будут отсылаться на удалённый адрес. В худшем случае злоумышленних получает данные администратора и, как следствие, доступ к админ-панели.
«Ага, значит, данные нужно как-то защитить» — смекнёт начинающий программист. Спросив у Гугла, почти наверняка найдёт функцию addslashes(), которая экранирует кавычки и обратный слеш. Можно спать спокойно? Да фиг там! Изменяем запрос на такой:
<input name="age" value=""><script>alert(String.fromCharCode(104,101,108,108,111));</alert>">
Кавычек нет, следовательно, функция бесполезна. «Стоп!», — скажет внимательный читатель, — «вон же кавычки, в начале». Да, прошу прощения. Но, в данном случае, это никакого влияния на форму не окажет. Вывод будет таким:
<input name="age" value="\"><script>alert(String.fromCharCode(104,101,108,108,111));</alert>">
В IE и Chrome внедрена защита от подобных атак. Потенциально опасные внедрения браузеры отсекают. Между тем, периодически появляются репорты, как с помощью определённых последовательностей символов обойти фильтры. Посему, программист обязан самостоятельно фильтровать все поступающие данные.
«Хорошо», — ответит любознательный читатель, — «а я, с помощью JavaScript, запрещу вводить неправильные символы». Идея хорошая, но что мешает злоумышленнику посмотреть исходный код и отправить на сервер прямой запрос, введя данные в адресную строку браузера? Верный ответ — ничего.
Что делать в подобном случае? На мой взгляд, лучше приводить данные к тому типу, что ожидается, если уж PHP такой возможности не представляет автоматически. Элементарный пример:
$age = empty($_GET['age']) ? 0 : (int) $_GET['age'];
Одной строкой мы отсекли все потенциально опасные варианты, преобразовав переменную к целому типу. Не беда, если в поле будет введён текст — он преобразуется к значению 0 (ноль), а пользователю пишем предупреждение, что введены некорректные данные.
Это же справедливо и для других данных. Не важно, что ожидается — вещественное число, дата, что угодно — либо приводите тип к ожидаемому, либо пишите сообщение об ошибке, что данные поступили в «неизвестном» формате.
Как быть с текстом? Его же нужно вывести «как есть».
Специально для подобных случаев используем замечательную функцию htmlspecialchars() (не при получении параметра, а непосредственно при выводе пользователю!). Она заменяет спец-символы на соответствующие html-мнемоники:
< → <
> → >
" → "
& → &
Но! Эта обработка для вывода текста, содержащего HTML, и для записи в базу подобный трюк не нужен. Используйте функции, работающие на основе mysql_real_escape_string(). Имеющаяся в PHP addslashes() для экранирования не подходит.
Стремитесь получать данные в неизменном виде. Далее, в зависимости от контекста, производите нужные действия. Отправляете строку в базу данных — используйте mysql_real_escape_string(), для вывода в коде HTML-страницы подойдёт htmlspecialchars().
Часто у начинающих можно встретить такое:
$age = $_GET['age'];
$age = addslashes($age);
$age = htmlspecialchars($age);
// здесь код, работающий с переменной
// при записи в базу могут ещё раз накатить addslashes()
// вывод на страницу, но из-за того, что выводится не то, можеть быть такое:
echo $_GET['age'];
Как уже рассмотрели выше, от уязвимостей подобное не защищает, зато ведёт к потенциальным ошибкам при обработке входных данных. Повторю слова, которые должны восприниматься как истина: никаким данным, пришедшим от пользователей, доверять нельзя! Это касается и user-agent (идентификатор браузера), и зафильтрованной джава скриптом страницы. Браузеру можно не позволить отправить какие-то данные со страницы, но человек, знающий основы HTTP, составит запрос вручную (или программно) и, будьте уверены, скрипт не сумеет отличить подмены.
днём интернета
шоколадкой для работы мозга
коробочкой ароматного чая для бодрости
продлением хостинга на +1 месяц