Перше правило, яке слід запам’ятати будь-якому веб-розробнику (не тільки на PHP): ніколи не довіряй даним, які надійшли ззовні. Абсолютно завжди необхідно перевіряти всі дані користувача. У цій статті мова піде про перевірку і убезпечення вхідних даних. Спробуємо відповісти на ці три питання:
- Як визначити методи вводу даних?
- Яким чином кожен метод може бути використаним зловмисником?
- Яким чином кожен тип вводу можна перевірити з метою запобігання проблем із безпекою?
У цій частині розглянемо перші два питання.
Проблема вводу
З покон віків програмісти на PHP мали доступ до вхідних даних за допомогою вбудованого механізму “register globals” (реєстрація глобальних змінних). Цей логічний засіб дозволяє отримати легкий доступ через глобальні змінні до однойменних полів форм чи параметрів, переданих через адресну строку. Наприклад: URL script.php?foo=bar утворює у глобальному просторі імен змінну $foo ініціалізовану значенням ‘bar’.
Проблема ж тут полягає у можливому конфлікті імен. Дані до скрипта можуть надходити із декількох джерел: GET-запити, POST-запити, cookies, змінні середовища сервера, змінні системного середовища. Якщо надходить декілька змінних із однаковим іменем з різних джерел - це призведе до втрати даних, адже PHP просто об’єднає дані із всіх цих джерел у один простір - глобальний. Щоправда, є можливість контролювати порядок пріоритетності цих джерел. Для цього існує 2 директиви у php.ini:
- старий gpc_order - визначає порядок для GET, POST та cookie.
- новий variables_order - визначає порядок для всіх джерел: системне середовище, GET, POST, cookie та середовище сервера.
Типово, порядок є визначений таким чином - EGPCS (system Environment, GET, POST, cookie, Server environment), тобто, якщо прийде дві змінні з однаковим іменем з GET та cookie - перевага буде надана даним із cookie. При чому, немає можливості знати про втрату даних із скрипта. Можливий вихід - призначати для змінних із різних джерел різні префікси, наприклад, за допомогою функції import_request_variables. Але це можливо лише тоді, коли ви маєте повний контроль над процесом написання коду.
Для розуміння проблеми ознайомимось із наступним прикладом:
<?php if (isAdmin()) { $admin = true; } if ($admin) { //контент для авторизованого користувача } ?>
Коли увімкнена директива register_globals, і скрипту буде переданий параметр “admin” наприклад через GET - PHP створить у глобальному просторі імен змінну $admin, ініціалізовану переданим значенням, оскільки у другій умові немає суворого порівняння (оператор ===), зловмисник зможе легко отримати доступ до закритої частинисайту. У випадку, якщо немає можливості вимкнути register_globals, треба щонайменше ініціалізувати всі необхідні змінні перед їхнім використанням.
Нажаль, у PHP немає ніякого “суворого” (strict) режиму як у Perl чи зауважень компілятора як у С. Такі засоби могли б показати розробнику всі проблемні місця у коді. Єдиний шлях побачити місця використання неініцілізованих змінних - перемкнути рівень виведених помилок на E_ALL. Наприклад так:
<?php error_reporting(E_ALL); ?>
у коді чи
error_reporting=E_ALL
у php.ini
Це може дуже допомогти у процесі розробки, однак не відловить всі можливі проблемні місця. Наприклад, не побачить неініціалізованих масивів, оскільки PHP автоматично створює масив при спробі записати туди нового елемента (незалежно від його порядку). Тому масиви також слід ініціалізовувати перед їх використанням. Наприклад так:
<?php $vector = array(); $vector[] = '25'; ?>
Альтернатива
Фактично, register_globals - найпоширеніша причина дірок у безпеці програм на PHP. У самому PHP цей механізм вже досить багато часу має статус застарілого (deprecated). Але не дивлячись на це, дуже багато програмістів продовжують її використовувати замість нового кращого засобу доступу до даних вводу - суперглобальні масиви.
У PHP 4.1 з’явились масиви $_GET, $_POST, $_COOKIE, $_SERVER та $_ENV. Вони надають глобальний доступ до всіх вхідних даних без конфлікту імен у будь-якій частині скрипта та дають однозначну відповідь на питання про походження даних. Нажаль, багато хостингів досі вмикають register_globals для підтримки старих програм, тому розробники мають створювати свої програми, пам’ятаючи про це.
Найпростіший метод захисту від register_globals - використання констант, оскільки механізм реєстрації глобальних змінних не має прямого впливу на константи. Ось таким чином можна уникнути проблем у поганому серверному середовищі:
<?php define('ADMIN', isAdmin()); if (ADMIN) { //показати прихований контент } ?>
Але при деяких обставинах константи самі можуть стати причиною небезпеки. Справа в тому, що неініціалізована константа є строкою, що зберігає ім’я константи (це витікає з того, що PHP є мовою із не суворою типізацією). Таким чином можливий наступний сценарій:
<?php if (isAdmin()) { define('ADMIN', true); } if (ADMIN) { //умова завжди буде істинною - або bool(true), коли визначене, або string('ADMIN'), коли - ні. //показати прихований контент } ?>
Кращим за константи підходом є вже загадане суворе порівняння (===) - окрім рівності, воно перевіряє, чи однакові типи даних зліва і зправа. Всі вхідні дані мают строковий тип або є масивами строк. Тому порівняння наприклад із булівським типом завжди буде невдалим при спробі підміни даних:
<?php if (isAdmin()) { $admin = true; } if ($admin === true) { //показати прихований контент } ?>
Попри все сказане, найкращий метод захисту від register_globals - вимкнути цей механізм, якщо можливо. Це можна зробити у трьох місцях: php.ini, httpd.conf, .htaccess. Найгнучкіший метод - останній, адже його можна включити у дистрибуцію вашої програми, але слід пам’ятати, що не всі використовують Apache та не всі його версії це підтримують. Тому необхідно намагатись писати універсально-безпечний код.
Ще кілька слів про останній суперглобальний масив $_REQUEST. Його було додано разом із введенням інших масивів з метою більш легкого переходу зі старого коду. Цей масив комбінує у собі дані з GET, POST та cookie заради легкості використання. Але його проблема знову ж таки у можливій втраті даних, адже їх походження ніяк не відокремлюється.
Фактично, для безпечного використання цього масиву необхідно перевіряти, з якого суперглобального масиву прийшли дані, але це нівелює всю зручність засобу. Тож легше використовувати звичайні суперглобальні масиви.
На сьогодні це все. У наступній частині я розповім про найважливіше - перевірку різних типів зовнішніх даних, тож чекайте скоро на новий запис. ![]()




















