Материал предназначен в основном для начинающих веб-программистов.

Введение. Часто ко мне обращаются клиенты, у которых установлены самописные CMS или модули, написанные начинающими веб-программистами, которые не понимают, что нужно для защиты данных и зачастую копируют функции фильтрации, не задумываясь о том как они работают и что именно нужно с ними делать.

Здесь я постараюсь описать как можно подробнее частые ошибки при фильтрации данных в PHP скрипте и дать простые советы как правильно выполнить фильтрацию данных.

В сети много статей по поводу фильтрации данных, но они как правильно не полные и без подробные примеров.

Разбор полетов.Фильтрация. Ошибка №1 Для числовых переменных используется такая проверка:
$number = $_GET["input_number"]; if (intval($number)) { ... выполняем SQL запрос... }
Почему она приведет к SQL инъекции? Дело в том, что пользователь может указать в переменной input_number значение:
1"+UNION+SELECT
В таком случаи проверка будет успешно пройдена, т.к. функция intval получает целочисленное значение переменной, т.е. 1, но в самой переменной $number ничего не изменилось, поэтому весь вредоносный код будет передан в SQL запрос.
Правильная фильтрация:
$number = intval($_GET["input_number"]); if ($number) { ... выполняем SQL запрос... }
Конечно, условие может меняться, например если вам нужно получить только определенный диапазон:
if ($number >= 32 AND $number dir = MAIN_DIR . "/template/" . $config["skin"];
В данном случаи можно подменить значение переменной $_COOKIE["skin"] и вызвать ошибку, в результате которой вы увидите абсолютный путь до папки сайта.
Если вы используете значение куков для сохранения в базу, то используйте одну из выше описанных фильтраций, тоже касается и переменной $_SERVER .Фильтрация. Ошибка №5. Включена директива register_globals . Обязательно выключите её, если она включена.
В некоторых ситуациях можно передать значение переменной, которая не должна была передаваться, например, если на сайте есть группы, то группе 2 переменная $group должна быть пустой или равняться 0, но достаточно подделать форму, добавив код:

В PHP скрипте переменная $group будет равна 5, если в скрипте она не была объявлена со значением по умолчанию.Фильтрация. Ошибка №6. Проверяйте загружаемые файлы.
Выполняйте проверку по следующим пунктам:
  • Расширение файла. Желательно запретить загрузку файлов с расширениями: php, php3, php4, php5 и т.п.
  • Загружен ли файл на сервер move_uploaded_file
  • Размер файла
  • Проверка. Ошибка №1. Сталкивался со случаями, когда для AJAX запроса (например: повышение репутации) передавалось имя пользователя или его ID (кому повышается репутация), но в самом PHP не было проверки на существование такого пользователя.
    Например:
    $user_id = intval($_REQUEST["user_id"]); ... INSERT INTO REPLOG SET uid = "{$user_id}", plus = "1" ... ... UPDATE Users SET reputation = reputation+1 WHERE user_id = "{$user_id}" ...
    Получается мы создаем запись в базе, которая совершенно бесполезна нам.Проверка. Ошибка №2. При выполнении различного рода действий (добавление, редактирование, удаление) с данными не забывайте проверять права пользователя на доступ к данной функции и дополнительные возможности (использование html тегов или возможность опубликовать материал без проверки).

    Давно исправлял в одном модуле форума подобную ошибку, когда любой пользователь мог отредактировать сообщение администрации.

    Проверка. Ошибка №3. При использовании нескольких php файлов сделайте простую проверку.
    В файле index.php (или в любом другом главном файле) напишите такую строчку перед подключением других php файлов:
    define ("READFILE", true);
    В начале других php файлов напишите:
    if (! defined ("READFILE")) { exit ("Error, wrong way to file.
    Go to main."); }
    Так вы ограничите доступ к файлам.Проверка. Ошибка №4. Используйте хеши для пользователей. Это поможет предотвратить вызов той или иной функции путём XSS.
    Пример составления хеша для пользователей:
    $secret_key = md5(strtolower("http://site.ru/" . $member["name"] . sha1($password) . date("Ymd"))); // $secret_key - это наш хеш
    Далее во все важные формы подставляйте инпут со значением текущего хеша пользователя:

    Во время выполнения скрипта осуществляйте проверку:
    if ($_POST["secret_key"] !== $secret_key) { exit ("Error: secret_key!"); } Проверка. Ошибка №5. При выводе SQL ошибок сделайте простое ограничение к доступу информации. Например задайте пароль для GET переменной:
    if ($_GET["passsql"] == "password") { ... вывод SQL ошибки... } else { ... Просто информация об ошибке, без подробностей... }
    Это позволит скрыть от хакера информацию, которая может ему помочь во взломе сайта.Проверка. Ошибка №5. Старайтесь не подключать файлы, получая имена файлов извне.
    Например:
    if (isset($_GET["file_name"])) { include $_GET["file_name"] .".php"; }
    Используйте переключатель

    Но еще HTML поддерживает работу с Фильтрами . Применяя различные Фильтры к тексту, можно добиться интересных эффектов. Но будьте осторожны, не все браузеры одинаково отображают эффекты которые должны давать фильтры , некоторые браузеры откровенно игнорируют фильтры. Поэтому тестируйте свои web-странички в различных браузерах. Все Фильтры корректно работают Internet Exhlorer. Итак давайте рассмотрим работу фильтров.

    Например мы хотим выделить фразу: "Доброго времени суток!!! " Давайте попробуем поизвращать эту фразу:-) , применяя к ней различные фильтры.

    Фильтр Mask.

    Выделяет текст, точнее фон на котором написан текст, как будто бы Вы выделили текст мышью.

    Синтаксис фильтра: STYLE="filter:Mask(Color="Color")

    Color - цвет выделения в шестнадцатиричном виде (напр. #000FFF) или название цвета на английском, например, Red, Blue, Green. Такое определение цвета применяется во всех фильтрах, поэтому дальше это повторяться не будет.

    Листинг 19.1.

    Фильтр DropShadow.

    Добавляет тень к тексту.

    Синтаксис фильтра:
    STYLE="filter:DropShadow(Color="Color", OffX="Offx", OffY="Offy", Positive="Positive")"

    Color - Цвет тени
    OffX - Смещение тени по X
    OffY - Смещение тени по Y
    Positive - Тень слева или справа (0 или 1 соответственно)

    Листинг 19.3.

    Вот как это смотрится web-странице:

    Фильтр FlipV.

    Переворачивает текст вертикально.

    Синтаксис фильтра: STYLE="filter:FlipV"

    Листинг 19.5.

    Вот как это смотрится web-странице:

    Фильтр Wave.

    Делает текст волнистым.

    Синтаксис фильтра: STYLE="filter: Wave(Freq="Freq", Add="Add", LightStrength="LightStrength", Phase="Phase", Strength="Strength")"

    Freq - колличество волн
    Add - показать/скрыть окантовку (1 или 0 соответственно)
    LightStrength - сила волн
    Phase - угол волн
    Strength - интенсивность волн

    Листинг 19.7.

    Доброго времени суток!!!


    Вот как это смотрится web-странице:

    Фильтр Blur.

    Размывает текст в определенную сторону.

    Синтаксис фильтра:
    STYLE="filter:Blur(Add="Add", Direction="Direction", Strength="Strength")"

    Add - умеренное или сильное размытие (соответственно 1 или 0)
    Direction - в какую сторону произойдет размытие (oт 0 до 315)
    Strength - смещение размытия

    Наконец, я нашел время и хочу представить вашему вниманию свой новый скрипт, который позволяет осуществить фильтрацию данных в html-таблицах.

    Скрипт поддерживает следующие типы фильтров:

    • Текстовое поле
    • Выпадающий список
    • Радио-кнопки
    • Чекбоксы

    Скрипт имеет малый размер и достаточно прост в подключении, а так же неплохо комбинируется со скриптом сортировщиком таблиц скачать его вместе с примерами подключения и настройки можно в моем репозитории: bitbucket.org

    Фильтр таблицы демо: Символы Текст Цифры Цифры Текст
    A B C --- 1 2 3 - 1 2 3
    B Арбуз 2 3 Фанат
    B Стрелок 1 2 Арба
    C Фанат 3 1 Стрелок
    C Стрелок 2 1 Фантомас
    B Стрелок 1 2 Арбуз
    C Фанат 3 3 Стрелок
    A Арбуз 2 2 Арбуз
    A Фанат 1 1 Стрелочник
    C Фанат 3 3 Арбуз
    B Фанат 2 3 Фантик
    C Стрелок 1 1 Арбуз
    C Фанат 3 2 Стрелка

    Концептуально скрипт состоит из двух частей: объектов-фильтров filterTable.Filter и собственно из функции filterTable(...) , которая привязывает эти объекты-фильтры к html-таблице.

    Объект-фильтр имеет следующий конструктор:

    /** * Объект фильтр. * @param HTMLInputElement | HTMLSelect HTMLElementRef | - Ссылка, или массив ссылок * на html-элементы, служащие фильтрами. * @param Function callback - ф-ция обратного вызова. Вызывается когда скрипт * производит валидацию содержимого ячейки. Ф-ция вызывается для каждой строки таблицы, для * каждой ячейки столбца, для которого назначен фильтр. * Функции будут переданы 3 параметра: callback(value, filters, i) где: * String value - значение ячейки таблицы, проверяемой на момент вызова ф-ции * HTMLElements filters - массив HTML-элементов назначенных фильтрами для проверяемого столбца. * Number i - индекс элемента фильтра в массиве filters который является * валидатором для текущего вызова. Т.е. filters[i] внутри ф-ции * обратного вызова будет содержать элемент, с которым провзаимодействовал * пользователь, в результате чего был запущен процесс валидации. * @param String eventName - название события привязанного к фильтру, по которому будет * запускаться валидация (onkeyup | onclick | onblur | onchange и т.п.) * @constructor */ filterTable.Filter = function (HTMLElementRef, callback, eventName) Первый аргумент: HTMLElement HTMLElementRef

    Второй аргумент: Function callback

    Функция: callback(value, filters, i) где:
    String value - значение ячейки таблицы, проверяемой на момент вызова ф-ции
    HTMLElements filters - массив HTML-элементов назначенных фильтрами для проверяемого столбца.
    Number i - индекс элемента фильтра в массиве filters который является валидатором для текущего вызова. Т.е. filters[i] внутри ф-ции обратного вызова будет содержать элемент, с которым провзаимодействовал пользователь, в результате чего был запущен процесс валидации. Функция должна возвращать true, или false в зависимости от того проходит фильтрацию пришедшее значение value при установленном значении фильтра filters[i] согласно вашей задумке, или нет.

    Третий аргумент: String eventName

    Название события привязанного к фильтру, по которому будет запускаться валидация (onkeyup | onclick | onblur | onchange и т.п.) onchange - значение по-умолчанию

    Сама же функция filterTable имеет следующую сигнатуру:

    таблицы * @param Object filters - объект-конфигурация фильтров: { N: FILTER[, N: FILTER] } * * Где: * NUM - это натуральное число - номер столбца таблицы, обслуживаемого * фильтром. Этот номер может принимать значения от 0 до кол-во * столбцов таблицы - 1. Номера можно задавать не по порядку. * * FILTER - это ссылка на HTML-элемент представляющий собой элемент * HTML-формы и имеющий атрибут value (select в том числе), либо * объект типа tableKit.Filter */ filterTable(HTMLTBodyRef, aFilters)

    Выглядит достаточно запутанно, но давайте разберем на примере. Для начала нам необходим html - каркас таблицы. Заметьте, что фильтры - это просто элементы html-формы они кстати имеют уникальные атрибуты id по которым мы их будем выбирать для передачи в конструктор filterTable.Filter

    Символы Текст Цифры Цифры Текст
    A B C --- 1 2 3 - 1 2 3
    BАрбуз23Фанат
    BСтрелок12Арба
    CФанат31Стрелок
    CСтрелок21Фантомас
    BСтрелок12Арбуз
    CФанат33Стрелок
    AАрбуз22Арбуз
    AФанат11Стрелочник
    CФанат33Арбуз
    BФанат23Фантик
    CСтрелок11Арбуз
    CФанат32Стрелка
    Стоит отметить, что такие типы фильтров, как текстовое поле, или выпадающий список - являются для скрипта "родными" и для того чтобы их реализовать не требуется даже прибегать к вызову filterTable.Filter - достаточно просто передать ссылку на сам html-элемент.

    Итак, каркас у нас есть. Фильтры в нём есть. Осталось все это связать воедино. Но давайте для начала рассмотрим простейший вариант подключения, и разберем лишь фильтры для 2-го и 3-го столбцов, потому, как там используются текстовое поле и выпадающий список значений, которые скрипт-фильтр понимает "нативно" без нужды создания filterTable.Filter Я нарочно закомментировал элементы 0, 3, 4 и пока обозначил их реализацию как "..." что бы преждевременно не отпугнуть слабонервных:)

    Обратите внимание, что второй аргумент ф-ции filterTable(..., {...} ) - это объект-конфигурация фильтров, у которого свойства имеют имена-цифры, начиная от 0 и до КОЛ-ВО_СТОЛБЦОВ_ТАБЛИЦЫ-1 Значением каждого такого свойства должен стать фильтр:

    таблицы */ document.getElementById("target"), /* Объект-конфигурация фильтров: */ { /* Фильтр для первого столбца чекбоксы: 0: ..., */ /* Фильтр для второго столбца текстовое поле - только точное совпадение: */ 1: document.getElementById("text"), /* Фильтр для третьего столбца выпадающий список: */ 2: document.getElementById("digits"), /* Фильтр для четвертого столбца радио кнопки: 3: ... , /* Фильтр для пятого столбца Постепенный ввод слова: 4: ... */ });

    Если кому то понадобится весь функционал, как показанный в демо примере, например фильтры с радио-кнопками, или фильтры с чекбоксами, то подключение становится немного сложнее потому, как по сути это фильтры, состоящие из набора html-элементов, и при валидации нужно проверять значения всего набора такого фильтра. Тут в действие вступают callback-функции. Ниже в листинге я постарался подробно прокомментировать этот момент.

    Опять же обратите внимание, что для текстового поля и выпадающего списка достаточно просто передать ссылку на html-элемент. А так же обратите внимание на подключение последнего фильтра. Вы спросите почему - ведь в последнем столбце фильтр - это текстовое поле! Да это так, но если вы внимательно поработали с демо примером, то заметили, что первый фильтр - тестовое поле срабатывает только после того как вы ввели значение полностью и нажали кнопку "Enter", или кликнули в любое другое место страницы т.е. фильтр срабатывает по дефолтному событию "onchange"! А вот фильтр-текстовое- поле последнего столбца - срабатывает моментально как только вы вводите какой-либо символ. Вот для реализации этого поведения пришлось создавать фильтр по всем правилам с filterTable.Filter а так же понадобилось задавать callback функцию и плюс к этому необходимо было передать имя события "onkeyup" по которому будет инициироваться процесс фильтрации. Вот так. Надеюсь я вас не запутал окончательно.

    FilterTable(/* Ссылка на элемент таблицы */ document.getElementById("target"), /* Объект-конфигурация фильтров: */ { /* Фильтр для первого столбца чекбоксы: */ 0: new filterTable.Filter([ /* Элементы фильтра */ document.getElementById("charA"), document.getElementById("charB"), document.getElementById("charC") ], /* Коллбэк ф-ция валидации */ function (value, filters, i) { /* Если чекбокс не отмечен - его значение не учавствует в валидации поэтому мы обязаны вернуть true */ if (false === filters[i].checked) return true; /* Далее, при проверке, мы должны одновременно проверять значения всех элементов набора при условии чекбокс отмечен */ return filters.checked && filters.value === value || filters.checked && filters.value === value || filters.checked && filters.value === value; }), /* Фильтр для второго столбца текстовое поле - только точное совпадение: */ 1: document.getElementById("text"), /* Фильтр для третьего столбца выпадающий список: */ 2: document.getElementById("digits"), /* Фильтр для четвертого столбца радио кнопки: */ 3: new filterTable.Filter(, /* Коллбэк ф-ция валидации */ function (value, filters, i) { /* В filters - у нас радио кнопка "Не выбрано", если она установлена фильтр не участвует в валидации и мы обязаны вернуть true */ if (true === filters.checked) return true; /* Если какая то радио-кнопка отмечена и содержимое проверяемой ячейки совпало то вернем true */ return filters.checked && filters.value === value || filters.checked && filters.value === value || filters.checked && filters.value === value; }), /* Фильтр для пятого столбца Постепенный ввод слова: */ 4: new filterTable.Filter(document.getElementById("regexp"), /* Коллбэк ф-ция валидации */ function (value, filters, i) { return value.indexOf(filters[i].value) === 0; }, /* Будем вызывать валидацию по событию onkeyup фильтра */ "onkeyup") });

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

    /** * Привязать фильтры к таблице. * @param HTMLTableSectionElement HTMLTBodyRef - ссылка на элемент таблицы * @param Object filters - объект-конфигурация фильтров: { N: FILTER[, N: FILTER] } * * Где: * NUM - это натуральное число - номер столбца таблицы, обслуживаемого * фильтром. Этот номер может принимать значения от 0 до кол-во * столбцов таблицы - 1. Номера можно задавать не по порядку. * * FILTER - это ссылка на HTML-элемент представляющий собой элемент * HTML-формы и имеющий атрибут value (select в том числе), либо * объект типа tableKit.Filter */ var filterTable = function (HTMLTBodyRef, aFilters) { var rows = HTMLTBodyRef.getElementsByTagName("TR"), filters = {}, n, walkThrough = function (rows) { var tr, i, f; for (i = 0; i < rows.length; i += 1) { tr = rows.item(i); for(f in filters) { if (filters.hasOwnProperty(f)) { if (false === filters[f].validate(tr.children[f].innerText)) { tr.style.display = "none"; break; } else { tr.style.display = ""; } } } } }; for(n in aFilters) { if (aFilters.hasOwnProperty(n)) { if (aFilters[n] instanceof filterTable.Filter) { filters[n] = aFilters[n]; } else { filters[n] = new filterTable.Filter(aFilters[n]); } filters[n]._setAction("onchange", function () {walkThrough(rows);}); } } } /** * Объект фильтр. * @param HTMLInputElement | HTMLSelect HTMLElementRef | - Ссылка, или массив ссылок * на html-элементы, служащие фильтрами. * @param Function callback - ф-ция обратного вызова. Вызывается когда скрипт * производит валидацию содержимого ячейки. Ф-ция вызывается для каждой строки таблицы, для * каждой ячейки столбца, для которого назначен фильтр. * Функции будут переданы 3 параметра: callback(value, filters, i) где: * String value - значение ячейки таблицы, проверяемой на момент вызова ф-ции * HTMLElements filters - массив HTML-элементов назначенных фильтрами для проверяемого столбца. * Number i - индекс элемента фильтра в массиве filters который является * валидатором для текущего вызова. Т.е. filters[i] внутри ф-ции * обратного вызова будет содержать элемент, с которым провзаимодействовал * пользователь, в результате чего был запущен процесс валидации. * @param String eventName - название события привязанного к фильтру, по которому будет * запускаться валидация (onkeyup | onclick | onblur | onchange и т.п.) * @constructor */ filterTable.Filter = function (HTMLElementRef, callback, eventName) { /* Если ф-цию вызвали не как конструктор фиксим этот момент: */ if (!(this instanceof arguments.callee)) { return new arguments.callee(HTMLElementRef, callback, eventName); } /* Выравниваем пришедший аргумент к массиву */ this.filters = {}.toString.call(HTMLElementRef) == "" ? HTMLElementRef: ; /** * Шаблонный метод вызывается для каждой строки таблицы, для соответствующей * ячейки. Номер ячейки задается в объекте-конфигурации фильтров ф-ции * filterTable (См. параметр 2 ф-ции tableFilter) * @param String cellValue - строковое значение ячейки * @returns {boolean} */ this.validate = function (cellValue) { for (var i = 0; i < this.filters.length; i += 1) { if (false === this.__validate(cellValue, this.filters[i], i)) { return false; } } } this.__validate = function (cellValue, filter, i) { /* Если фильтр был создан явно и явно указана функция валидации: */ if (typeof callback !== "undefined") { return callback(cellValue, this.filters, i); } /* Если в фильтр напихали пробелов, или другой непечатной фигни - удаляем: */ filter.value = filter.value.replace(/^\s+$/g, ""); /* "Фильтр содержит значение и оно совпало со значением ячейки" */ return !filter.value || filter.value == cellValue; } this._setAction = function (anEventName, callback) { for (var i = 0; i < this.filters.length; i += 1) { this.filters[i] = callback; } } };

    Фильтр диапазона значений "ОТ" и "ДО"

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

    ... ... ...
    ОТ --- 1 2 3 ДО --- 1 2 3

    А в вызове скрипта прописываем фильтр (для третьего столбца) для краткости я сократил листинг оставив только определение фильтра "от" и "до":

    FilterTable(/* Ссылка на элемент таблицы */ document.getElementById("target"), /* Объект-конфигурация фильтров: */ { /* Фильтр для первого столбца чекбоксы: */ 0: ... , /* Фильтр для второго столбца текстовое поле - только точное совпадение: */ 1: ... , /* ФИЛЬТР диапазон значений ОТ и ДО */ 2: new filterTable.Filter([ document.getElementById("digits-from"), document.getElementById("digits-to") ], function (value, filters, i) { var accept = true; value = parseInt(value,10) if (filters.value) { accept = (value >= parseInt(filters.value,10)); } if (accept && filters.value) { accept = (value =0; }; } })(jQuery);




    Close