THE BELL

Є ті, хто прочитав цю новину раніше за вас.
Підпишіться, щоб отримувати статті свіжими.
Email
Ім'я
Прізвище
Як ви хочете читати The Bell
Без спаму

Ті, хто більш-менш серйозно вивчав PHPзнають, що існує один дуже корисний глобальний масив у PHP, який називається $_SERVER. І ось хотілося б у цій статті розібрати найпопулярніші ключі та їх значення у цьому масиві, тому що їх знання просто обов'язково навіть для початківця PHP-програміста.

Перш ніж приступити до глобальному масиву $_SERVER у PHP, одразу зроблю невелику підказку. Є чудова функція, вбудована в PHP, яка називається phpinfo(). Давайте відразу наведу приклад її використання:

phpinfo();
?>

В результаті виконання цього просто скрипта Ви побачите величезну таблицю з різними налаштуваннями інтерпретатора PHP, у тому числі, ближче до кінця буде таблиця значень глобального масиву $_SERVER. Там будуть перераховані всі ключі та всі відповідні їм значення. Чим це може допомогти Вам? А тим, що якщо Вам знадобиться те чи інше значення, і Ви забудете, як називається ключ, то за допомогою функції phpinfo()Ви можете завжди згадати його назву. Загалом, Ви виконаєте цей скрипт і відразу мене зрозумієте.

А тепер давайте перейдемо до найпопулярніших ключів масиву $_SERVER:

  • HTTP_USER_AGENT- Цей ключ дозволяє дізнатися про характеристику клієнта. У більшості випадків це, безумовно, браузер, однак, не завжди. І знову ж таки, якщо браузер, то який, ось у цій змінній про це можна і дізнатися.
  • HTTP_REFERER- Містить абсолютний шлях до того файлу ( PHP-скрипт, HTML-сторінка), з якого перейшли на цей скрипт. Грубо говорячи, звідки прийшов клієнт.
  • SERVER_ADDR - IP-адресасервера.
  • REMOTE_ADDR - IP-адресаклієнта.
  • DOCUMENT_ROOT- фізичний шлях до кореневої директорії сайту. Це опція задається через конфігураційний файл сервера Apache.
  • SCRIPT_FILENAME- фізичний шлях до скрипту.
  • QUERY_STRING- Досить корисне значення, яке дозволяє отримати рядок із запитом, а далі можна займатися парсингом цього рядка.
  • REQUEST_URI- ще корисніше значення, що містить як сам запит, а й разом із відносний шлях до скрипту від кореня. Це дуже часто використовується для видалення дублювання з index.php, тобто коли у нас такий URL: "http://mysite.ru/index.php"і" http://mysite.ru/ведуть на одну сторінку, а URLрізні, отже, дублювання, що погано позначиться на пошуковій оптимізації. І ось за допомогою REQUEST_URIми можемо визначити: з index.phpчи ні, був викликаний скрипт. І можемо зробити редирект з index.php(якщо він був присутній у REQUEST_URI) на без index.php. Таким чином, при наданні такого запиту: " http://mysite.ru/index.php?id=5", у нас буде відбуватися редирект на URL: "http://mysite.ru/?id=5". Тобто ми позбулися дублювання, вилучивши з URLцей index.php.
  • SCRIPT_NAME- відносний шлях до скрипту, що викликається.

Мабуть, це все елементи глобального масиву $_SERVER в PHP, які використовуються регулярно. Їх треба знати та вміти використовувати, коли це необхідно.

Суперглобальний масив $_SERVER

Одним з найважливіших зумовлених масивів є масив $_SERVER - в нього PHP-інтерпретатор містить змінні, отримані від сервера. Без даних змінних складно організувати повноцінну підтримку Web-додатків. Нижче наводиться опис найважливіших елементів суперглобального масиву $_SERVER.

Зауваження

Переглянути повний список елементів масиву $_SERVER можна або за допомогою функції print_r(), яка друкує дамп масиву або за допомогою функції phpinfo(), яка виводить інформацію про PHP-інтерпретатора.

Елемент $_SERVER["DOCUMENT_ROOT"]

Елемент $_SERVER["DOCUMENT_ROOT"] містить шлях до кореневої директорії сервера, якщо скрипт виконується у віртуальному хості, в цьому елементі вказується шлях до кореневої директорії віртуального хоста. Тобто. у конфігураційному файлі httpd.conf віртуальний хост має директиву DocumentRoot, якій надано значення "D:/main", елемент $_SERVER["DOCUMENT_ROOT"] міститиме значення "D:main".

Елемент $_SERVER["HTTP_ACCEPT"]

В елементі $_SERVER["HTTP_ACCEPT"] описуються переваги клієнта щодо типу документа. Вміст цього елемента витягується з HTTP-заголовка Accept, який надсилає клієнт серверу. Вміст даного заголовка може виглядати так

image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/msword, */*

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

Символ * використовується для групування типів медіа-ряду. Наприклад, символом */* визначається використання всіх типів, а позначення type/* визначає використання всіх підтипів вибраного типу type.

Зауваження

Медіа-типи відокремлюються один від одного комами.

Кожен медіа-ряд характеризується додатковим набором параметрів. Одним з них є так званий відносний коефіцієнт переваги q, який приймає значення від 0 до 1, відповідно, від менш відданих типів до кращих. Використання декількох параметрів q дозволяє клієнту повідомити серверу відносний ступінь переваги для того чи іншого медіа-типу.

Зауваження

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

Приклад заголовка типу Accept:

Accept: audio/*; q=0.2, audio/basic

У цьому заголовку першим йде тип audio/* що включає всі музичні документи і характеризується коефіцієнтом переваги 0.2. Через кому вказано тип audio/basic, котрого коефіцієнт переваги не вказано і набуває значення за умовчанням рівне одиниці. Цитуючи RFС2616 цей заголовок можна інтерпретувати наступним чином: "Я віддаю перевагу типу audio/basic, але мені можна також надсилати документи будь-якого іншого audio-типу, якщо вони будуть доступні, після зниження коефіцієнта переваги більш ніж на 80%".

Приклад може бути складнішим.

Accept: text/plain; q=0.5, text/html,
text/x-dvi; q=0.8, text/x-c

Зауваження

Слід враховувати, що елемент $_SERVER["HTTP_ACCEPT"] містить таку саму інформацію, але без початкового заголовка Accept.

Цей заголовок інтерпретується наступним чином: Типи документів text/html і text/xc є кращими, але якщо вони недоступні, тоді клієнт, що відсилає даний запит, віддасть перевагу text/x-dvi, а якщо і його немає, то він може прийняти тип text/ plain.

Елемент $_SERVER["HTTP_ACCEPT_LANGUAGE"]

В елементі $_SERVER["HTTP_ACCEPT_LANGUAGE"] описуються переваги клієнта щодо мови. Ця інформація отримується з HTTP-заголовка Accept-Language, який надсилає клієнт серверу. Можна навести такий приклад:

Accept-Language: ru, en; q=0.7

Який можна інтерпретувати так: клієнт віддає перевагу російській мові, але у разі його відсутності згоден приймати документи англійською. Елемент $_SERVER["HTTP_ACCEPT_LANGUAGE"] міститиме таку саму інформацію, але без заголовка Accept-Language:

ru, en; q=0.7

Вміст елемента $_SERVER["HTTP_ACCEPT_LANGUAGE"] можна використовувати для визначення національної приналежності відвідувачів. Однак результати будуть приблизними, так як багато користувачів використовують англійські варіанти браузерів, які сповіщатимуть сервер про те, що відвідувач віддає перевагу лише одній мові - англійській.

Елемент $_SERVER["HTTP_HOST"]

В елементі $_SERVER["HTTP_HOST"] міститься ім'я сервера, яке зазвичай збігається з доменним ім'ям сайту, розташованого на сервері. Як правило, ім'я, зазначене в цьому параметрі, збігається з ім'ям $_SERVER["SERVER_NAME"]. У параметрі наводиться лише доменне ім'я без назви протоколу (http://), тобто.

www.sofftime.ru

Елемент $_SERVER["HTTP_REFERER"]

В елементі $_SERVER["HTTP_REFERER"] наводиться адреса сторінки, з якої відвідувач прийшов на цю сторінку. Перехід має здійснюватися за посиланням. Створимо дві сторінки index.php та page.php.

Сторінка index.php

echo "Посилання на сторінку PHP
"
;
echo.
$_SERVER [ "HTTP_REFERER" ]
?>

Сторінка page.php буде аналогічного змісту, але посилання вказуватиме на сторінку index.php.

Сторінка page.php

echo "Посилання на сторінку PHP
"
;
echo "Вміст $_SERVER["HTTP_REFERER"] - ".
$_SERVER [ "HTTP_REFERER" ]
?>

При переході з однієї сторінки на іншу, під посиланням буде виводиться адреса сторінки, з якої було здійснено перехід.

Елемент $_SERVER["HTTP_USER_AGENT"]

Елемент $_SERVER["HTTP_USER_AGENT"] містить інформацію про тип та версію браузера та операційну систему відвідувача.

Ось типове зміст цього рядка: "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)". Наявність підрядку "MSIE 6.0" говорить про те, що відвідувач переглядає сторінку за допомогою Internet Explorer версії 6.0. Рядок "Windows NT 5.1" повідомляє, що як операційна система використовується Windows XP.

Зауваження

Для Windows 2000 елемент $_SERVER["HTTP_USER_AGENT"] виглядає так: "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)")", тоді як для Windows XP - "Mozilla/4.0 (compatible; MSIE 6.0 ; Windows NT 5.1) ".

Якщо відвідувач скористається браузером Opera, то зміст $_SERVER["HTTP_USER_AGENT"]може виглядати так: "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98) Opera 6.04". Підрядок "MSIE 6.0" тут також присутній, повідомляючи, що браузер Opera сумісний з браузером Internet Explorer і використовує ті ж динамічні бібліотеки Windows. Тому, при аналізі рядка, що повертається браузером, слід мати на увазі, що до Internet Explorer відноситься рядок, що містить підрядок "MSIE 6.0" і не містить підстроки "Opera". Крім того, з цього рядка можна зробити висновок, що користувач використовує операційну систему Windows 98.

Зауваження

Користувальницький агент браузера Firefox може виглядати так Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8) Gecko/20051111 Firefox/1.5.

При використанні браузера Netscape, вміст елемент $_SERVER["HTTP_USER_AGENT"] може виглядати так: "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.4) Gecko/20030624 Netscape/7.1". Приналежність до цього браузеру можна визначити за наявності підрядка Netscape. Крім того, можна дізнатися, що відвідувач виходить в Інтернет, використовуючи операційну версію Linux з ядром, оптимізованим під Pentium IV, перебуваючи в графічній оболонці X-Window. Цей механізм зручно використовувати для збору статистичної інформації, яка дозволяє дизайнерам оптимізувати сторінки під найпоширеніші браузери.

Елемент $_SERVER["REMOTE_ADDR"]

В елемент $_SERVER["REMOTE_ADDR"] міститься IP-адреса клієнта. При тестуванні на локальній машині - ця адреса дорівнюватиме 127.0.0.1. Однак при тестуванні в мережі змінна поверне IP-адресу клієнта або останнього проксі-сервера, через який клієнт потрапив на сервер. Якщо клієнт використовує проксі-сервер, дізнатися його IP-адресу можна за допомогою змінної оточення HTTP_X_FORWARDED_FOR, значення якої можна отримати за допомогою функції getenv().

Зауваження

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

Вилучення змінної оточення HTTP_X_FORWARDED_FOR

echo getenv (HTTP_X_FORWARDED_FOR);
?>

Елемент $_SERVER["SCRIPT_FILENAME"]

В елемент $_SERVER["SCRIPT_FILENAME"] міститься абсолютний шлях до файлу від кореня диска. Так, якщо сервер працює під керуванням операційної системи Windows, такий шлях може виглядати наступним чином "d:main estindex.php", тобто. шлях вказується від диска, в UNIX-подібної операційної системи шлях вказується від кореневої директорії /, наприклад "/var/share/www/test/index.php".

Елемент $_SERVER["SERVER_NAME"]

В елемент $_SERVER["SERVER_NAME"] вміщується ім'я сервера, що зазвичай збігається з доменним ім'ям сайту, розташованого на ньому. Наприклад,

www.softtime.ru

Вміст елемента $_SERVER["SERVER_NAME"] часто збігається з вмістом елемента $_SERVER["HTTP_HOST"]. Крім імені сервера суперглобальний масив $_SERVER дозволяє з'ясувати ще ряд параметрів сервера, наприклад IP-адресу сервера, порт, який прослуховується, який Web-сервер встановлено і версію HTTP протоколу. Ця інформація міститься в елементах $_SERVER["SERVER_ADDR"], $_SERVER["SERVER_PORT"], $_SERVER["SERVER_SOFTWARE"] та $_SERVER["SERVER_PROTOCOL"], відповідно. Нижче наводиться приклад із використанням даних елементів.

Використання елементів масиву $_SERVER

echo "Ім'я сервера". $_SERVER ["SERVER_NAME"]. "
" ;
echo "IP-адреса сервера -". $_SERVER ["SERVER_ADDR"]. "
" ;
echo "Порт сервера -". $_SERVER ["SERVER_PORT"]. "
" ;
echo "Web-сервер -". $_SERVER ["SERVER_SOFTWARE"]. "
" ;
echo "Версія HTTP-протоколу -". $_SERVER ["SERVER_PROTOCOL"]. "
" ;
?>

Елемент $_SERVER["REQUEST_METHOD"]

В елемент $_SERVER["REQUEST_METHOD"] міститься метод запиту, який застосовується для виклику скрипта: GET або POST.

Компонент служить висновку форми авторизації. Використовується зазвичай у шаблоні дизайну сайту. Компонент є стандартним та входить до дистрибутиву модуля. У візуальному редакторі компонент розташований на шляху: Службові > Користувач.

У візуальному редакторі компонент розташований на шляху: Службові > Користувач > Форма авторизації.

Приклад виклику компонента system.auth.form

Опис параметрів

Механізм відновлення пароля (для довідки)

Якщо користувач запитав відновлення пароля, відновлення відбувається за наступним механізмом:

  1. Користувач натискає на Забули пароль?у формі авторизації.
  2. Генерується випадковий рядок довжиною 32 символи з урахуванням секрету, відомого лише серверу.
  3. Результат записується в БД та відправляється на пошту. Посилання виду: http://site.ru/bitrix/admin/index.php?change_password=yes&lang=ru&USER_CHECKWORD=3farde09fay52547f11c68bf17d95760&USER_LOGIN=market, де:
    • http://site.ru/bitrix/admin/index.php - шлях до сторінки авторизації або зміни пароля;
    • change_password=yes – дія зміни пароля;
    • lang=ua ідентифікатор мови;
    • USER_CHECKWORD=- контрольний рядок для зміни пароля 32 символи. У рядку використовуються символи. доступні для MD5: .

      При зміні пароля в запит контрольного рядка вводити тільки контрольний рядок, без USER_CHECKWORD= !

    • &USER_LOGIN=market - вказівка ​​якого користувача відбувається зміна пароля.
  4. При порівнянні контрольного рядка з форми про те, що записано в БД, враховується час закінчення, зазначений у груповій безпековій політиці.

Примітка. У формі відновлення пароля рекомендується використовувати CAPTCHA - поле Використовувати CAPTCHA для відновлення пароляу налаштуваннях Головного модуля.


"Бітрікс", 2001-2019, "1С-Бітрікс", 2019

Reg.ru: домени та хостинг

Найбільший реєстратор та хостинг-провайдер у Росії.

Понад 2 мільйони доменних імен на обслуговуванні.

Просування, пошта для домену, рішення для бізнесу.

Понад 700 тис. клієнтів у всьому світі вже зробили свій вибір.

*Наведіть курсор миші, щоб зупинити прокручування.

Назад вперед

Створення простої системи реєстрації користувачів на PHP та MySQL

Створення системи реєстрації – це велика робота. Вам доводиться писати код, який здійснює валідацію email-адрес, відправляє повідомлення на пошту з підтвердженням реєстрації, а також здійснює валідацію решти полів форми та багато ще всього.

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

У цьому уроці ми створимо дуже просту систему реєстрації, яка не вимагає і не зберігає паролів взагалі! Результат буде легко змінювати та додати до вже існуючого PHP-сайту. Бажаєте з'ясувати, як це працює? Читайте нижче.



Ось як наша супер проста система працюватиме:

Ми скомбінуємо форму авторизації та реєстрацію. У цій формі буде поле для введення email-адреси та кнопка реєстрації;
- При заповненні поля email-адресою, після натискання на кнопку реєстрації буде створено запис про нового користувача, але тільки в тому випадку, якщо введена email-адреса не була знайдена в базі даних.

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

Переваги такого підходу:

Не потрібно зберігати паролі та здійснювати валідацію полів;
- немає необхідності у відновленні пароля, секретних питань тощо;
- З моменту, як користувач зареєструвався/авторизувався, ви можете завжди бути впевнені, що цей користувач буде у вашій зоні доступу (що email-адреса є істинною);
- неймовірно простий процес реєстрації;

Недоліки:

Безпека облікового запису користувача. Якщо хтось має доступ до пошти користувача, він може авторизуватись.
- Email не захищений і може бути перехоплений. Майте на увазі, що це питання є актуальним і у випадку, коли пароль був забутий і його необхідно відновити, або в будь-якій системі авторизації, яка не використовує HTTPS для передачі даних (логін/пароль);
- Поки ви налаштуєте як потрібно поштовий сервер, існує шанс, що повідомлення з посиланнями на авторизацію потраплятимуть до спаму;

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

Тож використовувати її пропонується для реєстрацій на форумах та сервісах, які не працюють із важливою інформацією.

Як користуватись цією системою

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

Вам потрібно завантажити вихідні джерела, додані до уроку
- В архіві знайти файл tables.sqlІмпортуйте його у вашу базу даних, використовуючи опцію імпорту в phpMyAdmin. Альтернативний спосіб: відкрити цей файл через текстовий редактор, скопіювати SQL запит та виконати його;
- Відкрити includes/main.phpта заповнити налаштування зв'язку з вашою базою даних (вказати користувача та пароль для зв'язку з базою, а також хост та ім'я бази). У цьому ж файлі, ви також повинні вказати email, який буде використаний як оригінальна адреса для повідомлень, що відправляються системою. Деякі хости блокують вихідні мейли поки у формі не буде вказана справжня адреса електронної пошти, яка була створена з панелі управління хостом, так що вкажіть реальну адресу;
- Завантажте всі файли index.php, protected.phpта папки assets та includes через FTP на ваш хост;
- Додати код нижче на кожну PHP-сторінку, де потрібно відобразити форму авторизації;

Require_once "includes/main.php"; $user = new User(); if(!$user->loggedIn())( redirect("index.php"); )
– Готово!

Для тих, кому цікаво, як це все працює - вперед до читання нижче!

Перший крок – написання HTM-коду форми авторизації. Цей код міститься у файлі index.php. Цей файл також містить PHP-код, який обробляє дані форми та інші корисні функції системи авторизації. Докладніше про це можна дізнатись у розділі нижче, присвяченому огляду PHP коду.

index.php

Tutorial: Super Simple Registration System With PHP & MySQL

Login or Register

Enter your email address above and we will send
you a login link.

У головній секції (між тегами і) я підключив основні стилі (в цьому уроці вони не розуміються, тому ви можете подивитися їх самі. Папка assets/css/style.css). До тега, що закриваєя підключив бібліотеку jQuery та файл script.js, який ми напишемо та розберемо трохи нижче.


JavaScript

jQuery відстежує стан кнопки "Зареєструватися/авторизуватися" за допомогою функції e.preventDefault()та надсилає AJAX-запити. Залежно від відповіді сервера, виводить те чи інше повідомлення та визначає подальші дії/

assets/js/script.js

$(function()( var form = $("#login-register"); form.on("submit", function(e)( if(form.is(".loading, .loggedIn")))( return false ; ) var email = form.find("input").val(), messageHolder = form.find("span");e.preventDefault(); (m)( if(m.error)( form.addClass("error"); messageHolder.text(m.message); ) else( form.removeClass("error").addClass("loggedIn"); messageHolder). text(m.message); ) )); )) $(document).ajaxStart(function()( form.addClass("loading"); )); $(document).ajaxComplete(function()( form. removeClass("loading"); )); ));

був доданий у форму для відображення поточного стану AJAX-запиту (це стало можливим завдяки методам ajaxStart()) та ajaxComplete(), які ви зможете знайти ближче до кінця файлу).

Цей клас показує анімований gif-файл, що крутиться (як би натякає нам на те, що запит обробляється), і також виступає як прапор, що запобігає повторному відправленню форми (коли кнопка зареєструватися була вже одного разу натиснута). Клас .loggedIn- це інший прапор, - встановлюється тоді, коли було відправлено email. Цей прапор моментально блокує будь-які подальші дії з формою.

Схема бази даних

Наша неймовірно проста система реєстрації використовує 2 MySQL таблиці (SQL-код знаходиться у файлі tables.sql). Перша зберігає дані про облікові записи користувачів. Друга зберігає інформацію про кількість спроб входу.


Схема таблиці користувачів.

Система не використовує паролі, що видно на схемі. На ній можна побачити колонку tokenз токенами, що сусідить з колонкою token_validity. Токен встановлюється, як тільки користувач підключається до системи, задає свій email для відправлення повідомлення (трохи докладніше про це в наступному блоці). Колонка token_validityвстановлює час на 10 хвилин пізніше, після якого токен перестає бути актуальним.


Схема таблиці, яка рахує кількість спроб авторизації.

В обох таблицях IP-адреса зберігається в обробленому вигляді за допомогою функції ip2long в полі типу integer.

Тепер ми можемо написати трохи PHP-коду. Основний функціонал системи покладено на клас User.class.phpВи можете бачити нижче.

Цей клас активно використовує idorm (docs), ці бібліотеки є мінімально необхідними інструментами для роботи з базами даних. Він обробляє доступ до бази даних, генерацію токенів та їх валідацію. Він є простим інтерфейсом, що дозволяє легко підключити систему реєстрації до вашого сайту, якщо він використовує PHP.

User.class.php

Class User( // Приватний ORM випадок private $orm; /** * Знайти користувача по токену. Тільки валідні токени, прийняті до розгляду. Токен генерується тільки на 10 хвилин з того моменту, як був створений * @param string $token. Це шуканий токен * @return User. ("token", $token) ->where_raw("token_validity > NOW()") ->find_one(); if(!$result)( return false; ) return new User($result); ) /** * Авторизувати або зареєструвати користувача * @param string $email Користувацька email-адреса * @return User */ public static function loginOrRegister($email)( // Якщо такий користувач вже існує, повернути значення функції User від заданої email-адреси, що зберігається в базі if(User::exists($email))( return new User($email); ) // Інакше створити нового користувача теля в базі та повернути значення функції User::create від вказаного email return User::create($email); ) /** * Створити нового користувача та зберегти в базу * @param string $email. Користувацька email-адреса * @return User */ private static function create($email)( // Записати нового користувача та повернути результат функції User від цих значень $result = ORM::for_table("reg_users")->create(); $result->email = $email;$result->save(); Користувацька email-адреса * @return boolean */ public static function exists($email)( // Чи існує користувач у базі? $result = ORM::for_table("reg_users") ->where("email", $email) ->count(); return $result == 1; ) /** * Створити новий об'єкт користувача * @param екземпляр $param ORM , id, email or 0 * @return User */ public function __construct($param = null) ( if($param instanceof ORM)( // ORM перевірка пройдена $this->orm = $param; ) else if(is_string($param))( // Перевірка на email пройдена $this->orm = ORM::for_table ("reg_users") ->where("email", $param) ->find_one(); ) else( $id = 0; if(is_numeric($param))( // ідентифікатору користувача передається значення змінної $param $id = $param; ) else if(isset($_SESSION["loginid"]))( // Інакше дивись сесію $id = $_SESSION["loginid"]; ) $this->orm = ORM::for_table( "reg_users") ->where("id", $id) ->find_one(); ) ) /** * Сгенерувати новий SHA1 токен авторизації, записує в базу і повертає його значення * @return string */ public function generateToken()( // Згенерувати токен для авторизованого користувача та зберегти його в базу $token = sha1($this->email.time().rand(0, 1000000) )); // Зберегти токен в основі // І помітити його, що він актуальний лише протягом 10 наступних хвилин $this->orm->set("token", $token); $this->orm->set_expr("token_validity", "ADDTIME(NOW(),"0:10")"); $this->orm->save(); return $token; ) /** * Авторизувати користувача * @return void */ public function login()( // Позначити користувача, як авторизованого $_SESSION["loginid"] = $this->orm->id; // Оновити значення поля бази last_login $this->orm->set_expr("last_login", "NOW()"); $this->orm->save(); ) /** * Знищити сесію та розлогініть користувача ()( $_SESSION = array(); unset($_SESSION); ) /** * Перевірка, чи заходив користувач * @return boolean */ public function loggedIn()( return isset($this->orm->id) && $_SESSION["loginid"] == $this->orm->id; ) /** * Перевірка чи є користувач адміністратором * @return boolean */ public function isAdmin()( return $this->rank() = = "administrator"; ) /** * Знайти тип користувача, можливо або administrator або regular * @return string */ public function rank()( if($this->orm->rank == 1)( return "administrator "; ) return "regular"; ) /** * Метод дозволяє отримати приватну інформацію мацію користувача в якості властивостей об'єкта User * @param string $key Ім'я властивості, що отримує доступ * @return mixed */ public function __get($key)( if(isset($this->orm->$key))( return $this->orm->$key; ) return null; )

Токени генеруються за допомогою алгоритму SHA1 і зберігаються в базі даних. Я використовую функції часу MySQL, щоб встановити 10-хвилинне обмеження актуальності токена.

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

Зверніть увагу, що я використовую чарівний метод __getбібліотеки docs наприкінці файлу, щоб перехопити доступ до властивостей об'єкта User.

Завдяки цьому стає можливим отримати доступ до інформації, що зберігається в базі завдяки властивостям $user->email, $user->tokenта ін У наступному фрагменті коду розглянемо для прикладу, як використовувати ці класи.


Захищена сторінка

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

functions.php

Function send_email($from, $to, $subject, $message)( // Хелпер, що відправляє email $headers = "MIME-Version: 1.0" . "\r\n"; $headers .= "Content-type: text /plain; charset=utf-8" . "\r\n"; $headers .= "From: ".$from . "\r\n"; return mail($to, $subject, $message, $headers ); ) function get_page_url()( // Визначити URL PHP-файлу $url = "http".(empty($_SERVER["HTTPS"])?"":"s")."://".$_SERVER ["SERVER_NAME"]; if(isset($_SERVER["REQUEST_URI"]) && $_SERVER["REQUEST_URI"] != "")( $url.= $_SERVER["REQUEST_URI"]; ) else( $url. = $_SERVER["PATH_INFO"]; ) return $url; :for_table("reg_login_attempt") ->where("ip", sprintf("%u", ip2long($ip))) ->where_raw("ts > SUBTIME(NOW(),"1:00")") ->count(); // Кількість спроб входу за останні 10 хвилин за цією IP-адресою $count_10_min = ORM::for_table("reg_login_attempt") ->where("ip", sprint f("%u", ip2long($ip))) ->where_raw("ts > SUBTIME(NOW(),"0:10")") ->count(); if($count_hour > $limit_hour || $count_10_min > $limit_10_min)( throw new Exception("Too many login attempts!"); ) ) function rate_limit_tick($ip, $email)( // Створити новий запис у таблиці кількість спроб входу $login_attempt = ORM::for_table("reg_login_attempt")->create(); $login_attempt->email = $email; $login_attempt->ip = sprintf("%u", ip2long($ip)); $login_attempt->save(); ) function redirect($url)( header("Location: $url"); exit; )

Функції rate_limitі rate_limit_tickстежать за кількістю спроб авторизації за минулий період часу з першої спроби. Спроба входу записується в базі стовпець reg_login_attempt. Ці функції викликаються, коли відбувається обробка та відправлення даних форми, як ви можете бачити з наступного фрагмента коду.

Код нижче взято з файлу index.phpі він обробляє надсилання форми. Він повертає JSON-відповідь, яка, у свою чергу, обробляється jQuery у файлі assets/js/script.js, що ми вже розбирали раніше.

index.php

Try( if(!empty($_POST) && isset($_SERVER["HTTP_X_REQUESTED_WITH"]))( // Output a JSON header header("Content-type: application/json"); // Чи є ця email-адреса валідним if(!isset($_POST["email"]) || !filter_var($_POST["email"], FILTER_VALIDATE_EMAIL))( throw new Exception("Please enter a valid email."); ) // Перевірка. Чи дозволено користувачеві авторизуватися, чи не перевищив він кількість допустимих підключень?(файл functions.php для більшої інформації) rate_limit($_SERVER["REMOTE_ADDR"]); // Записати цю спробу авторизації rate_limit_tick($_SERVER["REMOTE_ADDR" _POST["email"]); // Надіслати лист користувачеві $message = ""; $email = $_POST["email"]; $subject = "Your Login Link"; )( $subject = "Thank You For Registering!"; $message = "Thank you for registering at our site!\n\n"; ) // Спроба авторизувати або зареєструвати користувача $user = User::loginOrRegister($_POST[ "email"]); ; $message.= get_page_url()."?tkn=".$user->generateToken()."\n\n"; $message.= "Отправляється expire автоматично після 10 хвилин."; $result = send_email($fromEmail, $_POST["email"], $subject, $message); if(!$result)( throw new Exception("There was an error sending your email. Please try again."); ) die(json_encode(array("message" => "Thank you! We\"ve sent a link catch(Exception $e)( die(json_encode(array("error"=>1, "message" => $e->getMessage() )));

Після успішної авторизації/реєстрації код вище надішле користувачеві посилання на авторизацію. Токен стає доступним, т.к. він передається як змінна в генерованому засланні методом $_GETз маркером tkn

index.php

If(isset($_GET["tkn"]))( // Чи є цей токен валідним для авторизації? $user = User::findByToken($_GET["tkn"]); if($user)( // Так , є. Здійснити редирект на захищену сторінку $user->login(); redirect("protected.php"); ) // Ні, токен не валідний. ");

$user->login()

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

Подібним чином влаштовано і обробку функції на вихід із системи.

index.php

If(isset($_GET["logout"]))( $user = new User(); if($user->loggedIn())( $user->logout(); ) redirect("index.php") ;)

В кінці коду я знову поставив редирект на index.php, таким чином параметр ?logout=1переданий за допомогою URL-адреси не потрібно.

Наш файл index.phpпотребує дод. захисту - ми не хочемо, щоб люди, які колись авторизувалися в системі знову бачили форму реєстрації. Для цього, ми використовуємо метод $user->loggedIn().

index.php

$user = new User(); if($user->loggedIn())( redirect("protected.php"); )

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

protected.php

// Щоб захистити кожну сторінку на вашому сайті, підключіть до неї файл // main.php і створіть новий об'єкт User. Ось як це просто! require_once "includes/main.php"; $user = new User(); if(!$user->loggedIn())( redirect("index.php"); )

Після цієї перевірки можете бути впевнені, що користувач був успішно авторизований. Ви також можете отримати доступ до інформації, що зберігається в базі за допомогою властивостей об'єкта $user. Для виведення emailу користувача та його статусу використовуйте цей код:

Echo "Your email: ".$user->email; echo "Your rank: ".$user->rank();

Метод rank()використовується тут тому, що в базі зазвичай зберігаються номери (0 для звичайного користувача, 1 для адміністратора) і нам потрібно перетворити ці дані на статуси, до яких вони відносяться, в чому нам і допомагає цей метод.

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

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

Ми закінчили!

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

Матеріал підготував Денис Малишок спеціально для сайту сайт

P.S.Бажаєте рухатися далі в освоєнні PHP та ОВП? Зверніть увагу на преміум-уроки з різних аспектів сайтобудування, включаючи програмування на PHP, а також на безкоштовний курс створення своєї CMS-системи на PHP з нуля з використанням ООП:

Сподобався матеріал і хочете віддячити?
Просто поділіться з друзями та колегами!


Сьогодні ми розглянемо експлуатацію критичної 1day-уразливості у популярній CMS Joomla, яка прогриміла на просторах інтернету наприкінці жовтня. Йтиметься про вразливості з номерами CVE-2016-8869, CVE-2016-8870і CVE-2016-9081. Усі три походять з одного шматочка коду, який п'ять довгих років нудився в надрах фреймворку в очікуванні свого часу, щоб потім вирватися на волю і принести з собою хаос, зламані сайти та сльози ні в чому не винних користувачів цієї Joomla. Лише найдоблесніші і сміливіші розробники, чиї очі червоні від світла моніторів, а клавіатури завалені хлібними крихтами, змогли кинути виклик нечисті, що розбушувалась, і покласти її голову на вівтар фіксів.

WARNING

Вся інформація надана виключно для ознайомлення. Ні редакція, ні автор не несуть відповідальності за будь-яку можливу шкоду, заподіяну матеріалами цієї статті.

З чого все почалося

6 жовтня 2016 року Деміс Пальма (Demis Palma) створив топік на Stack Exchange, в якому поцікавився: а чому, власне, у Joomla версії 3.6 існують два методи реєстрації користувачів з однаковою назвою register()? Перший знаходиться в контролері UsersControllerRegistration, а другий - у UsersControllerUser. Деміс хотів дізнатися, чи використовується десь метод UsersControllerUser::register() , або це лише еволюційний анахронізм, що залишився від старої логіки. Його турбував той факт, що навіть якщо цей метод не використовується жодним уявленням, він може бути викликаний за допомогою сформованого запиту. На що отримав відповідь від девелопера під ніком itoctopus, який підтвердив: проблема справді існує. І надіслав звіт розробникам Joomla.

Далі події розвивалися найшвидшим чином. 18 жовтня розробники Joomla приймають репорт Деміса, який накидав PoC, що дозволяє реєструвати користувача. Він опублікував замітку на своєму сайті, де загалом розповів про знайдену проблему та думки з цього приводу. Цього ж дня виходить нова версія Joomla 3.6.3, яка все ще містить уразливий код.

Після цього Давід Тампелліні (Davide Tampellini) розкручує баг до стану реєстрації не простого користувача, а адміністратора. І вже 21 жовтня команді безпеки Joomla прилітає новий кейс. У ньому йдеться про підвищення привілеїв. Цього ж дня на сайті Joomla з'являється анонс про те, що у вівторок, 25 жовтня, буде випущено чергову версію з порядковим номером 3.6.3, яка виправляє критичну вразливість в ядрі системи.

25 жовтня Joomla Security Strike Team знаходить останню проблему, що створює виявлений Демісом шматок коду. Потім у головну гілку офіційного репозиторію Joomla пушиться коміт від 21 жовтня з непримітною назвою Prepare 3.6.4 Stable Release, який фіксує злощасний баг.

Після цього камін-ауту до міжсобойчика розробників підключаються численні зацікавлені особи - починають розкручувати вразливість та готувати сплоїти.

27 жовтня дослідник Гаррі Робертс (Harry Roberts) викладає в репозиторій Xiphos Research готовий експлоїт, який може завантажувати PHP-файл на сервер із вразливою CMS.

Деталі

Що ж, із передісторією покінчено, переходимо до найцікавішого – розбору вразливості. Як піддослідна версія я встановив Joomla 3.6.3, тому всі номери рядків будуть актуальні саме для цієї версії. А всі шляхи до файлів, які ти побачиш далі, будуть вказуватись щодо кореня встановленої CMS.

Завдяки знахідці Деміса Пальми ми знаємо, що є два методи, які виконують реєстрацію користувача у системі. Перший використовується CMS і знаходиться у файлі /components/com_users/controllers/registration.php:108. Другий (той, що нам і потрібно буде викликати), мешкає у /components/com_users/controllers/user.php:293. Подивимося на нього ближче.

286: /** 287: * Method to register a user. 288: * 289: * @return boolean 290: * 291: * @since 1.6 292: */ 293: public function register() 294: ( 295: JSession::checkToken("post") or jexit(JText::_ ("JINVALID_TOKEN"));... 300: // Get the form data. .. 315: $return = $model->validate($form, $data) 316: 317: // Check for errors 318: if ($return === false) / Finish the registration.346: $return = $model->register($data);

Тут я залишив лише цікаві рядки. Повну версію вразливого методу можна переглянути у репозиторії Joomla.

Розберемося, що відбувається при звичайній реєстрації користувача: які дані надсилаються та як вони обробляються. Якщо реєстрація користувачів включена в налаштуваннях, форму можна знайти за адресою http://joomla.local/index.php/component/users/?view=registration .


Легітимний запит на реєстрацію користувача виглядає як наступний скріншот.


За роботу з користувачами відповідає компонент com_users. Зверніть увагу на параметр task у запиті. Він має формат $controller.$method. Подивимося структуру файлів.

Імена скриптів у папці controllersвідповідають назвам контролерів, що викликаються. Так як у нашому запиті зараз $controller = "registration", то кличеться файл registration.phpта її метод register() .

Увага, питання: як передати обробку реєстрації вразливе місце в коді? Ти, напевно, вже здогадався. Імена вразливого і реального способів збігаються (register), тому досить змінити назву викликаного контролера. А де ми маємо вразливий контролер? Правильно, у файлі user.php. Виходить $controller = "user". Збираємо всі разом і отримуємо task=user.register. Тепер запит на реєстрацію обробляється потрібним методом.


Друге, що нам потрібно зробити, – це надіслати дані у правильному форматі. Тут усе просто. Легітимний register() чекає від нас масив під назвою jform, в якому ми передаємо дані для реєстрації - ім'я, логін, пароль, пошту (див. скріншот із запитом).

  • /components/com_users/controllers/registration.php: 124: // Get the user data. 125: $requestData = $this->input->post->get("jform", array(), "array");

Наш підопічний отримує ці дані з масиву з ім'ям user.

  • /components/com_users/controllers/user.php: 301: // Get the form data. 302: $data = $this->input->post->get("user", array(), "array");

Тому змінюємо у запиті імена всіх параметрів із jfrom на user.

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

  • /components/com_users/controllers/user.php: 296: JSession::checkToken("post") or jexit(JText::_("JINVALID_TOKEN"));

Він виглядає як хеш MD5, а взяти його можна, наприклад, із форми авторизації на сайті /index.php/component/users/?view=login .


Тепер можна створювати користувачів за допомогою потрібного методу. Якщо все вийшло, то вітаю - ти щойно проексплуатував уразливість CVE-2016-8870відсутня перевірка дозволів на реєстрацію нових користувачів.

Ось як вона виглядає у «робочому» методі register() з контролера UsersControllerRegistration:

  • /components/com_users/controllers/registration.php: 113: // If registration is disabled - Redirect to login page. 114: if (JComponentHelper::getParams("com_users")->get("allowUserRegistration") == 0) 115: ( 116: $this->setRedirect(JRoute::_("index.php?option=com_users&view= login", false)); 117: 118: return false; 119: )

А так у вразливому:

  • /components/com_users/controllers/user.php:

Ага, ніяк.

Щоб зрозуміти другу, більш серйозну проблему, відправимо сформований нами запит і простежимо, як він виконується на різних ділянках коду. Ось шматок, який відповідає за перевірку надісланих користувачем даних у робочому методі:

Продовження доступне лише учасникам

Варіант 1. Приєднайтесь до спільноти «сайт», щоб читати всі матеріали на сайті

Членство у спільноті протягом зазначеного терміну відкриє тобі доступ до ВСІХ матеріалів «Хакера», збільшить особисту накопичувальну знижку та дозволить накопичувати професійний рейтинг Xakep Score!

THE BELL

Є ті, хто прочитав цю новину раніше за вас.
Підпишіться, щоб отримувати статті свіжими.
Email
Ім'я
Прізвище
Як ви хочете читати The Bell
Без спаму