Статьи

Тестування веб-орієнтованих додатків. Частина 4: безпека в PHP.

  1. Все контролюємо самі, вручну, не покладаючись на випадок
  2. Одна точка входу на сайт
  3. Приховуємо вразливі дані
  4. Будь-які дані, отримані від користувача, перевіряємо, перевіряємо і ще раз перевіряємо
  5. Пам'ятаємо про продуктивність
  6. Адаптація до змін
  7. Налаштування: стежимо уважно
  8. Полегшуючи розробку, які не полегшуйте злом
  9. ведемо логи
  10. SQL-injections
  11. Закриваємо обхідні шляхи
  12. PS

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

Отже: PHP, безпеку, її забезпечення і тестування.

Все контролюємо самі, вручну, не покладаючись на випадок

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

До цього ж пункту належить думка про те, що немає нічого кращого свого коду, написаного "від і до своїми руками" (руками колег), на відміну від готових бібліотек сумнівного походження.

Одна точка входу на сайт

Охороняти одні двері простіше, ніж сто дверей. Так закладіть всі, крім однієї, цеглою. Тобто залиште одну точку входу на сайт, перехоплюючи звернення до БУДЬ-ЯКОМУ URL'у, а потім обробляючи запит "своїми силами". Затратно? Ніяк не затратнее усунення наслідків порушення безпеки. І на продуктивність впливає не так сильно, як могло б здаватися.

Як це зробити:

а) У .htaccess

RewriteEngine On
RewriteBase /
RewriteRule. * Index.php? Url = $ 0 [QSA, L]

б) У скрипті, анализирующем запитаний URL, фільтруємо його, тобто наводимо в нижній регістр і прибираємо послідовності виду% hh; що йдуть підряд дві точки ( ".."); все, що не є буквою англійського алфавіту, цифрою або знаками "_., /".

$ Url = preg_replace ( "/% \ d {2} | \. \. | [^ Az \ d _ \. \ /] /", "", Strtolower ($ url));
if (preg_match ( "/% \ d {2} | \. \. | [^ az \ d _ \. \ /] /", $ url) == 1)
{
// знайшовся хтось занадто розумний, посилаємо подалі
}

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

в) Якщо запитаний URL не закінчується на слеш, отримуємо повне ім'я запитаного файлу в вигляді "шлях до програми" + URL. Перевіряємо, чи можна з нашого сайту запитувати такі файли (по розширенню). Якщо так, то перевіряємо, чи є такий файл в файловій системі. Якщо так, то виставляємо правильний MIME-тип в заголовку "Content-type" і віддаємо файл функцією fpassthru (), після чого завершуємо скрипт.

Якщо такого файлу немає, можливо, відвідувач запросив не файл, а сторінку сайту (перевіримо в подальшій роботі движка).

Якщо файли з таким розширенням можна запитувати, логіруем помилку і припиняємо роботу.

WARNING! Зловмисник може передати змінну url через GET-запит в обхід mod_rewrite. Якщо це дуже нас напружує, можна брати інформацію про запрошенням ресурсі з $ _SERVER [ 'REQUEST_URI'] і аналізувати. Однак, передане зловмисником все одно потрапить в $ _GET [ 'url'] і буде проаналізовано, як показано вище.

WARNING_2! Пам'ятайте, що зловмисник може спеціально передати якийсь шкідливий код (наприклад, на JavaScript) в надії, що він десь запротоколює, а потім виконається при перегляді логів. Також може бути переданий, наприклад, PHP-код в надії про-include'іть його потім і виконати якимось нетривіальним способом. Висновок: дані, що потрапляють в лог, теж повинні піддаватися ретельному фільтрації і обробці.

Якщо з якоїсь причини створити одну точку входу на сайт можна або небажано, ретельно перевіряйте у всіх скриптах, чи дійсно вони викликані належним чином (увійшов користувач в систему, чи достатньо у нього прав для виконання необхідної операції, чи не намагається хтось то викликати скрипт в обхід системи авторизації або іншого механізму забезпечення безпеки). В контексті цього примітки хочеться дати пораду "дуже початківцям": якщо ви не впевнені, що механізм захисту системи адміністрування вашого сайту досить надійний, використовуйте HTTP-авторизацію засобами .htaccess на доступ до всього каталогу, в якому лежать скрипти системи адміністрування.

Приховуємо вразливі дані

У переважній більшості випадків ваш додаток буде встановлено в певний підкаталог домашнього каталогу користувача (тобто, наприклад, в / users / mycoolsiteuser / public_html /). Це означає, що у вас є як мінімум один рівень ієрархії файлової системи, доступний вашим крипти, але свідомо недоступний по HTTP. Має сенс розмістити файли з паролями до БД та іншими важливими даними в цьому "захищеному від доступу по HTTP" каталозі.

До цього ж пункту належить думка про те, що думати треба не тільки про те, як ведуть себе ваші скрипти, але і про середовище, в якій вони працюють. Так, операційних систем дуже багато, і специфіка всюди своя, але перевірити права доступу на ті чи інші каталоги і файли можна (і потрібно!) Всюди.

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

І ще одне чудове правило: НІЯКИХ ВІДНОСНИХ ШЛЯХІВ в файлової системі. Ваша програма при інсталяції має прописати собі в конфігураційний файл шляху до всіх своїх каталогів (головному, з шаблонами, з обработчиками і т.п.) і ОБОВ'ЯЗКОВО додавати їх в початок шляхів до відповідних файлів, а потім ще й обробляти отримане функцією realpath () .

Будь-які дані, отримані від користувача, перевіряємо, перевіряємо і ще раз перевіряємо

а) Будь-який текст (будь то частина URL'а, поле форми, куки або щось інше) перевіряємо на наявність неприпустимих символів, тегів і т.п.

if (preg_match ( "/ ПЛОХІЕСІМВОЛИ /", $ text) == 1)
{
// посилаємо подалі
}

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

mb_strlen ( 'Ляляля', 'UFT-8')! == strlen ( 'Ляляля')

в) Числа перевіряємо на розрядність. Якщо число витягується з рядка, спочатку перевіряємо його довжину в кількості символів, щоб нам не передали "10-мегабайтную число", яке гарантовано не поміститься ні в який числовий тип даних.

if (strlen ($ num)> 8)
{
echo "Ну сказано же: від 0 до 99999999";
}

г) Числа також перевіряємо на нерівність нулю і неотрицательность (якщо ці дві властивості важливі в смисловому контексті числа).

д) Якщо ми збираємося передати дані в БД, то перевіряємо їх особливо ретельно: неприпустимі символи, довжини, кодування, формат і т.п. Про це - трохи нижче.

е) Якщо ми отримали файл, має сенс перевірити його розмір, ім'я (тільки латиниця, нижній регістр, букви, цифри, знак підкреслення, одна точка, обов'язкове розширення зі списку допустимих). Якщо ім'я не відповідає необхідному - його можна привести до такого. А деякі розробники рекомендують не перекручуватися, а генерувати випадкове ім'я: вихідне ім'я в такій ситуації, при необхідності, можна зберігати окремо, наприклад у БД, але перевірте спочатку, щоб файл не називався "delete from admins";). В особливо небезпечних ситуаціях має сенс перевірити не тільки ім'я, але і вміст файлу.

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

е ) Дані, які будуть відображені на сторінках сайту, фільтруємо на предмет HTML / JS / CSS - коротше, на предмет будь-яких тегів і всього того, що може виконатися на стороні клієнта. інакше банальне

<Script language = "JavaScript">
window.location = 'http: //www.saitstroyanamiivirusami.com'
</ Script>

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

Трохи вище я писав про те, що шкідливі дані можуть бути спеціально передані для логування та заподіяння шкоди при перегляді логів. Є спосіб захиститися і від цього: пишіть логи в файли поза доступних по HTTP каталогах, а переглядайте банальним F3 в FAR'e.

ж) Щоб уникнути атак на засоби надсилання електронних листів (всілякі форми зворотного зв'язку) перевіряємо на наявність (і видаляємо) символи "\ r" і "\ n" в будь-яких полях, отриманих з форми (крім тексту повідомлення). Іншими словами, форма зворотного зв'язку повинна дозволяти вибрати, кому пишеться лист, ввести заголовок і текст повідомлення, які піддаються фільтрації. Про всяк випадок нагадаю просту істину: список "кому" повинен містити в своїх

ідентифікатори адресатів, а не безпосередньо e-mail'и.

Пам'ятаємо про продуктивність

Щоб зробити гидоту, сайт не обов'язково ламати ін'єкціями коду або чимось подібним. Іноді його досить просто навантажити (забавно, що це може статися і просто через наплив відвідувачів, які не мають ніякого злого умислу).

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

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

Підвищити продуктивність допомагає грамотна архітектура проекту, що дозволяє збирати і виконувати тільки ті скрипти, які необхідні для генерації даної конкретної сторінки або виконання даної конкретної операції (тобто НЕ ТРЕБА збирати для будь-якого чиха мухи ВСЕ скрипти в один величезний мега-скрипт include'амі ).

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

Адаптація до змін

Іноді загроза приходить звідти, звідки не чекали. Дії адміністраторів сервера або просто накопичуються за час роботи службові дані можуть стати причиною падіння додатки.

Періодично перевіряємо кількість доступного дискового простору. Список, закачані відвідувачами файли, незабранная пошта - все це з'їдає доступне місце дуже швидко. Як тільки його кількість опустилося нижче деякого критичного значення - сповіщаємо адміністратора, а якщо ситуація зовсім плачевна - автоматично видаляємо те, чим можна пожертвувати (старі логи, пошта в спам-боксах і т.п.)

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

Налаштування: стежимо уважно

Критичні для безпеки і взагалі роботи настройки змінюємо на потрібні нам (всюди, де це можливо), використовуючи функцію ini_set ():

а) register_globals - це теж проблема вже багато років: для забезпечення сумісності з "творіннями окремих гігантів" деякі хостери включають цю опцію; про те, до чого це призводить, є багато статей, не буду повторюватися.

б) error_reporting і error_reporting (0) - є така опція, є така функція: пам'ятаємо, що на стадії реальної експлуатації додатки ніякі повідомлення від інтерпретатора PHP не повинні бути показані відвідувачам.

в) display_errors - додаток до попереднього пункту: теж можна вимкнути.

г) allow_url_fopen і allow_url_include - вимикаємо; пам'ятаємо, що в будь-include'и або require'и можна передавати ТІЛЬКИ рядкові константи або змінні, отримані з надійного джерела (наприклад, налаштувань сайту в БД).

д) short_open_tag - теж може створити вам проблему, якщо ви використовуєте відкриває тег "<?" замість рекомендованого "<? php". Так що треба звикати використовувати те, що працює завжди за замовчуванням: "<? Php". Тобто поставте у себе на девелоперської машині це значення в Off (каюсь, сам так ніколи не роблю; пару раз на ці граблі вже настав).

е) output_buffering - при розробці обов'язково відключити, щоб повніше перевірити роботу всіх функцій, модифікують заголовки HTTP-відповіді. При реальній експлуатації можна і включити, але особисто мені варіант з буферизацією "своїми силами" здається більш виправданим.

е ) Max_execution_time, max_input_time, default_socket_timeout - витікання будь-якого з цих таймаутів призводить до неприємних наслідків, часом фатальним. Тому визначаємо значення цих параметрів і враховуємо при виконанні будь-якої досить довгої операції.

ж) memory_limit - скрипти на PHP не дуже ненажерливі в плані використовуваної пам'яті: до тих пір, поки комусь не прийде в голову чудова ідея смикнути з БД або з диска в пам'ять многосотмегабайтний запит або файл. І все. Інтерпретатор не пробачить вашого скрипту таку вільність. Тому - перед "ненажерливої" операцією має сенс перевірити, чи вистачить нам пам'яті для її виконання.

з) max_input_nesting_level - не дайте зловмисникові передати вам мілліардомерний масив 🙂

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

й) session.use_trans_sid, session.use_only_cookies, session.bug_compat_42, session.bug_compat_warn - невірні значення цих параметрів часом призводять до часткової або повної непрацездатності сесій.

Також ще на стадії інсталяції слід перевірити наявність і версії необхідних вам бібліотек (через API самої бібліотеки та доступність тих чи інших функцій (function_exists ()). Також ніщо не заважає перевірити версію PHP, Apache, MySQL.

Полегшуючи розробку, які не полегшуйте злом

Іноді для швидкості роботи з проектом розробники заводять якісь "магічні значення": користувачів з простими паролями і повними правами, посилання для входу в систему без аутентифікації, жорстко прописують в код "завжди відповідні паролі" і використовують інші "заглушки". Якщо забути видалити щось подібне на стадії експлуатації, нічого хорошого не передбачається. А краще - спочатку цього не робити.

ведемо логи

Будь-які підозрілі ситуації має сенс протоколювати. Звернення до неіснуючих об'єктів файлової системи (або таким, доступ до яких закритий), помилки аутентифікації, звернення до неіснуючих сторінок сайту, помилки відправки файлів, форм і т.п. Все це може бути спробою злому. Вивчаючи подібні логи, можна знайти слабкі місця в безпеці вашої програми. Про проблему з логами вже двічі сказано вище, не буду повторюватися.

SQL-injections

Джерела - будь-які зовнішні дані, а також (в гіршому випадку) неправильну поведінку самого додатка (викликане зовнішніми впливами або внутрішніми збоями).

Що перевіряємо:

а) Запитаний URL (сам URL і змінні GET).
б) Форми: вміст полів, наявність необхідних і відсутність зайвих полів.
в) Кукі.

Як захищаємося:

а) Перш, ніж передати щось в СУБД, ви повинні гарантовано переконатися, що в цих даних немає нічого небезпечного. Застосовуємо фільтрацію даних скрізь, де це можливо, по самій повній програмі. Дуже добре в цьому плані працюють регулярні вирази.

б) Застосовуємо функцію addslashes () (при цьому пам'ятаємо про параметр magic_quotes_gpc: якщо він включений, застосування цієї функції дасть "подвійне екранування").

в) Застосовуємо функцію mysql_real_escape_string ().

г) Прекрасний спосіб захиститися від отримання яких би то ні було небезпечних для СУБД даних - використання хешування. Наприклад, рядок з sha1-хешем вже явно не буде виконана як шкідливий запит.

д) Іноді виправданою є модифікація алгоритмів роботи програми з метою обробки даних на стороні бізнес-логіки, а не на стороні даних: тобто якийсь константний запит проводить тільки вибірку, а потім PHP-код проводить аналіз. Альтернатива цього - використання збережених підпрограм (процедур і функцій).

е) Для всього, де йде ТІЛЬКИ вибірка, має сенс створювати т.зв. "В'юшки" (уявлення, view), для яких заводити окремого користувача БД з мінімальними правами.

е ) Перед виконанням запиту слід оцінити (найчастіше - це можливо), скільки пам'яті і часу нам знадобиться для його виконання. Перевіряємо це і не даємо виконувати "довгограючі важкі запити".

Закриваємо обхідні шляхи

Отже, ви купили хостинг, написали більш-менш захищене додаток і розмістили його в мережі. Зробіть це ЗІ СВОГО комп'ютера, чистого від вірусів і троянів, перехоплюючих паролі. Зробіть це в мережі, адміністратор якої не аналізує трафік з метою перехопити чужі логіни-паролі. Створіть окрему обліковий запис для роботи з FTP, логін і пароль якої не збігаються з логіном і паролем облікового запису у хостера. Те ж справедливо для будь-яких інших облікових записів: в СУБД, в пошті і т.п.

PS

У попередніх нотатках цієї серії я писав в кінці короткі чек-листи. Тут такого, на жаль, не буде, тому що предметна область занадто велика. Але якщо хтось поділиться готовим - з задоволенням розміщу посилання.

Затратно?
Php?
Д) short_open_tag - теж може створити вам проблему, якщо ви використовуєте відкриває тег "<?
Замість рекомендованого "<?
Так що треба звикати використовувати те, що працює завжди за замовчуванням: "<?

Новости