Злочинний auth php. HTTP Встановлення захисту на сторінку за допомогою MySQL і PHP. Додаток. Початкові тексти скрипту

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

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

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

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

Можливість входу до захищеної зони зберігається протягом всього сеансу роботи вікна браузера, але після того, як ви його закриєте, увійти знову можна буде лише набравши ім'я користувача та пароль. Тобто, скориставшись комп'ютером, незаконних дій від вашого імені зробити неможливо. Чим ще добрий цей метод? Він не приймає жодних змінних зі сторонніх серверів і після триразового неправильного введення пароля вам доведеться оновлювати сторінку, що ускладнює зламування системи підбором.

А виглядає це ось так:

Введений користувачем логін зберігається в змінній $PHP_AUTH_USER , пароль у $PHP_AUTH_PW . До речі, зверніть увагу на перевірку існування запису користувача з таким ім'ям у БД – це критичний момент, який враховувати дуже важливо. У випадку, якщо такої перевірки не буде, це призведе до плачевних результатів — $row дорівнюватиме нулю, тобто ввівши неіснуюче ім'я користувача і порожній пароль можна буде потрапити в захищену зону.

Між інструкціями Header("HTTP/1.0 401 Unauthorized"); і exit(); вставляємо будь-що - від простої фрази про те, що сюди не можна до пропозиції кудись сходити, злітати, збігати, сповзати і так далі. Так, мало не забув - у змінних $dbhost, $dbuser, $dbpasswd і $dbname зберігаються дані, що забезпечують доступ до бази та ім'я бази.

Подібний код необхідно вставити на кожну сторінку захищеної зони, наприклад через include .

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

Вчимося робити просту аутентифікацію користувачів на сайті. На сайті можуть бути сторінки лише для авторизованих користувачів і вони будуть повноцінно функціонувати, якщо додати до них блок аутентифікації. Щоб створити його, потрібна база даних MySQL. Вона може мати 5 колонок (мінімум), а може і більше, якщо ви хочете додати інформацію про користувачів. Назвемо базу даних "Userauth".

Створимо в ній такі поля: ID для підрахунку числа користувачів, UID для унікального ідентифікаційного номера користувача, Username для імені користувача, Email для його адреси електронної поштита Password для пароля. Ви можете використовувати для авторизації користувача і вже наявну базу даних, тільки, як і у випадку з новою базою даних, створіть в ній наступну таблицю.

Код MySQL

CREATE TABLE `users` (`ID` int (11) NOT NULL AUTO_INCREMENT, `UID` int (11) NOT NULL, `Username` text NOT NULL, `Email` text NOT NULL, `Password` text NOT NULL, PRIMARY KEY (`ID`)) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

Тепер створимо файл "sql.php". Він відповідає за підключення до бази даних. Цей код, по-перше, створює змінні для сервера та користувача, коли він підключається до сервера. По-друге, він вибере базу даних, у разі "USERAUTH". Цей файл потрібно підключити до "log.php" і "reg.php" для доступу до бази даних.

Код PHP

//Ваше ім'я користувача MySQL$pass = "redere"; //пароль $conn = mysql_connect ($server, $user, $pass); //з'єднання з сервером$ db = mysql_select_db ("userauth", $ conn); //Вибір бази даних if (!$db) ( //якщо не може вибрати базу даних echo "Вибачте, помилка:(/>"); //Показує повідомлення про помилку exit(); //Дозволяє працювати іншим скриптам PHP } ?>

Далі сторінка входу, хай вона називається "login.php". По-перше, вона перевіряє введені дані про наявність помилок. Сторінка має поля для імені користувача, пароля, кнопку надсилання та посилання для реєстрації. Коли користувач натисне кнопку «Вхід», форма буде оброблена кодом із файлу "log.php", а потім відбудеться вхід до системи.

Код PHP

0) { //якщо є помилки сесії$err = "

"; //Start a table foreach ($_SESSION["ERRMSG"] as $msg) ( //Розпізнавання кожної помилки$err .= " "; //запис їх у змінну) $err .= "
" . $msg . "
"; //закриття таблиці unset ($_SESSION["ERRMSG"]); //Видалення сесії } ?> форма входу
Ім'я користувача
Пароль
Реєстрація


Потім пишемо скрипт для входу до системи. Назвемо його "log.php". Він має функцію для очищення вхідних даних від ін'єкцій SQL, які можуть зіпсувати ваш скрипт. По-друге, він отримує дані форми та перевіряє їх на правильність. Якщо вхідні дані правильні, скрипт надсилає користувача на сторінку авторизованих користувачів, якщо ні – встановлює помилки та надсилає користувача на сторінку входу.

Код PHP

//початок сесії для запису function Fix($str) ( //очистка полів $str = trim($str); if (get_magic_quotes_gpc()) ( $str = stripslashes ($str); ) //масив для збереження помилок$errflag = false; //прапор помилки $username = Fix($_POST["username"]); //Ім'я користувача$password = Fix($_POST["password"]);//пароль ) //перевірка пароля if ($password == "") ( $errmsg = "Password missing"; //помилка $errflag = true ; //піднімає прапор у разі помилки ) //якщо прапор помилки піднято, направляє назад до форми реєстрації //записує помилки session_write_close(); //закриття сесії //перенаправлення exit(); ) //запит до бази даних$qry = "SELECT * FROM `users` WHERE `Username` = "$username" AND `Password` = "" . md5 ($password) . """; $result = mysql_query ($qry); //перевірка, чи був запит успішним (чи є дані щодо нього) if (mysql_num_rows ($result) == 1) ( while ($row = mysql_fetch_assoc ($result))) ( $_SESSION["UID"] = $row["UID"]; //отримання UID з бази даних та розміщення його в сесію$_SESSION["USERNAME"] = $username; //встановлює, чи збігається ім'я користувача із сесійним session_write_close(); //закриття сесії header("location: member.php"); //перенаправлення) ) else ( $_SESSION["ERRMSG"] = "Invalid username or password"; //помилка session_write_close(); //закриття сесії header("location: login.php"); //перенаправлення exit(); ) ?>

Зробимо сторінку реєстрації, назвемо її "register.php". Вона схожа на сторінку входу, тільки має на кілька полів більше, а замість посилання на реєстрацію – посилання на login.php на випадок, якщо користувач вже має обліковий запис.

Код PHP

0) { //якщо є помилки сесії$err = "

"//початок таблиці foreach ($_SESSION["ERRMSG"] as $msg) ( //встановлює кожну помилку$err .= " "; //записує їх у змінну) $err .= "
" . $msg . "
//кінець таблиці unset ($_SESSION["ERRMSG"]); //знищує сесію } ?> Форма регістрації
Ім'я користувача
E-mail
Пароль
Повтор пароля
У мене є обліковий запис


Тепер зробимо скрипт реєстрації у файлі "reg.php". У нього буде включено "sql.php" для підключення до бази даних. Використовується та сама функція, що й у скрипті входу для очищення поля введення. Встановлюються змінні для можливих помилок. Далі – функція створення унікального ідентифікатора, який ніколи раніше не надавалися. Потім витягуються дані з форми реєстрації та перевіряються. Відбувається перевірка, що адреса електронної пошти вказана у потрібному форматі, а також, чи правильно вказано пароль. Потім скрипт перевіряє, чи немає у базі даних користувача з таким самим ім'ям, і, якщо є, повідомляє про помилку. І, нарешті, код додає користувача до бази даних.

Код PHP

//початок сесії для запису function Fix($str) ( //очистка полів $str = @trim($str); if (get_magic_quotes_gpc()) ( $str = stripslashes ($str); ) return mysql_real_escape_string($str); ) $ errmsg = array (); //масив для зберігання помилок$errflag = false; //прапор помилки $UID = "12323543534523453451465685454";//унікальний ID $username = Fix($_POST["username"]); //Ім'я користувача$email = $_POST["email"]; //Email $password = Fix($_POST["password"]);//пароль $rpassword = Fix($_POST["rpassword"]);//повтор пароля //перевірка імені користувача if ($username == "") ( $errmsg = "Username missing"; //помилка $errflag = true ; //піднімає прапор у разі помилки) //перевірка Email if(!eregi("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@+(\.+)*(\.(2,3) ))$", $email)) ( //має відповідати формату: [email protected]$errmsg = "Invalid Email"; //помилка $errflag = true; //піднімає прапор у разі помилки } //перевірка пароля if ($password == "") ( $errmsg = "Password missing"; //помилка $errflag = true ; //піднімає прапор у разі помилки } //перевірка повтору пароля if ($rpassword == "") ( $errmsg = "Repeated password missing";//помилка $errflag = true ; //піднімає прапор у разі помилки } //перевірка валідності пароля if (strcmp($password, $rpassword) != 0) ( $errmsg = "Passwords do not match";//помилка $errflag = true ; //піднімає прапор у разі помилки } //перевірка, чи вільне ім'я користувача if ($username != "") ( $qry = "SELECT * FROM `users` WHERE `Username` = "$username""; //запит до MySQL $result = mysql_query ($qry); if ($result) ( if (mysql_num_rows ($result) > 0) ( //якщо ім'я вже використовується$errmsg = "Username already in use"; //повідомлення про помилку$errflag = true; //піднімає прапор у разі помилки) mysql_free_result ($result); ) //якщо дані не пройшли валідацію, направляє назад до форми реєстрації if ($errflag) ( $_SESSION["ERRMSG"] = $errmsg; //повідомлення про помилку session_write_close(); //закриття сесії header("location: register.php"); //перенаправлення exit(); ) //додавання даних у базу$qry = "INSERT INTO `userauth`.`users`(`UID`, `Username`, `Email`, `Password`) VALUES("$UID","$username","$email","" . md5 ($password) ."")"; $result = mysql_query ($qry); //перевірка, чи був успішним запит на додавання if ($result) ( echo "Дякую Вам за реєстрацію, " .$username . ". Будь ласка, заходьте сюди"; exit (); ) else ( die ("Помилка, зверніться пізніше"); ) ?>

Ще потрібно зробити скрипт для виходу користувача із системи. Він припиняє сесію для користувача з цим унікальним ідентифікатором та ім'ям, а потім перенаправляє користувача на сторінку входу до системи.

Код PHP

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

Код PHP

Одна з умов у коді вище є предметом питання у .

Наступний код потрібно вставити на сторінку для авторизованих користувачів, вона називається, наприклад, "member.php", а у Вас може називатися як завгодно.

Код PHP

Вам дозволено доступ до цієї сторінки. Вийти ( )

Аутентифікація користувачів готова!

Можливо використовувати функцію header()для надсилання повідомлення "Authentication Required"браузеру, змусивши його показати віконце для введення логіну та пароля. Як тільки користувач заповнить логін і пароль, посилання, що містить PHP-скрипт буде викликано ще раз з визначеними змінними PHP_AUTH_USER , PHP_AUTH_PW і AUTH_TYPE , встановленими в логін, пароль і тип аутентифікації відповідно. Ці зумовлені змінні зберігаються в масивах $_SERVER та $HTTP_SERVER_VARS . Підтримуються обидва типи: "Basic" та "Digest" (починаючи з версії PHP 5.1.0). Докладніше дивіться функцію header().

Приклад фрагмента скрипта, який змушує клієнта авторизуватись для перегляду сторінки:

Приклад #1 Приклад Basic HTTP-автентифікації

if (!isset($_SERVER ["PHP_AUTH_USER"])) (
header ( "WWW-Authenticate: Basic realm="My Realm"");

echo "Текст, який надсилається в тому випадку,
якщо користувач натиснув кнопку Cancel"
;
exit;
) else (
echo
"

Hello ( $_SERVER [ "PHP_AUTH_USER" ]) .

" ;
echo "

Ви ввели пароль( $_SERVER [ "PHP_AUTH_PW" ]) .

" ;
}
?>

Приклад #2 Приклад Digest HTTP-автентифікації

Це приклад реалізації простого скрипту Digest HTTP аутентифікації. За подробицями звертайтесь до RFC 2617 .

$realm = "Заборонена зона";

//user => password
$users = array("admin" => "mypass", "guest" => "guest");

if (empty($_SERVER ["PHP_AUTH_DIGEST"])) (
header ("HTTP/1.1 401 Unauthorized");
header ( "WWW-Authenticate: Digest realm="". $realm.
"",qop="auth",nonce="" . uniqid(). "",opaque="" . md5 ($ realm). """);

Die( "Текст, який надсилається в тому випадку, якщо користувач натиснув кнопку Cancel");
}

// аналізуємо змінну PHP_AUTH_DIGEST
if (!($data = http_digest_parse ($_SERVER ["PHP_AUTH_DIGEST"])) ||
!isset($users [ $data [ "username" ]]))
die( "Неправильні дані!");

// генеруємо коректну відповідь
$A1 = md5 ($data ["username" ] . ":" . $realm . ":" . $users [ $data [ "username" ]]));
$A2 = md5 ($_SERVER [ "REQUEST_METHOD" ]. ":" . $data [ "uri" ]);
$valid_response = md5 ($A1 . ":" . $data [ "nonce" ]. ":" . $data [ "nc" ]. ":" . $data [ "cnonce" ]. ":" . $data [ "qop" ]. ":" . $A2 );

if ($data [ "response" ] != $valid_response )
die( "Неправильні дані!");

// ok, логін та пароль вірні
echo "Ви увійшли як:". $ data [ "username"];

// функція аналізу заголовка http auth
function http_digest_parse ($txt )
{
// Захист від відсутніх даних
$needed_parts = array("nonce" => 1 , "nc" => 1 , "cnonce" => 1 , "qop" => 1 , "username" => 1 , "uri" => 1 , "response" => 1);
$ data = array ();
$keys = implode ("|", array_keys ($needed_parts));

Preg_match_all ("@(" . $keys .) ")=(?:([\""])([^\2]+?)\2|([^\s,]+))@", $ txt , $ matches , PREG_SET_ORDER );

Foreach ($matches as $m) (
$ data [ $ m [ 1 ] ] = $ m [ 3 ] ? $ m [3]: $ m [4];
unset($needed_parts [$ m [1]]));
}

Return $needed_parts? false: $ data;
}
?>

Зауваження: Зауваження щодо сумісності

Будьте особливо уважні при вказівці заголовків HTTP. Для того, щоб гарантувати максимальну сумісність з найбільшою кількістю різних клієнтів, слово "Basic" має бути написане з великої літери "B", регіон (realm) повинен бути взятий у подвійні (не одинарні!) лапки, і рівно один пробіл повинен передувати коду 401 у заголовку HTTP/1.0 401. Параметри автентифікації повинні розділятися комами, як це було показано у прикладі Digest автентифікації вище.

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

Ви можете переглянути особливості роботи браузера Internet Explorer. Він дуже вимогливий до параметра заголовків, що передаються. Трюк із зазначенням заголовка WWW-Authenticateперед відправкою статусу HTTP/1.0 401поки що працює для нього.

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

Зауваження: Примітка щодо конфігурації

PHP використовує вказівку директиви AuthTypeдля вказівки того, використовується зовнішня автентифікація чи ні.

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

І Netscape Navigator та Internet Explorer очищають кеш аутентифікації поточного вікна для заданого регіону (realm) при отриманні від сервера статусу 401. Це може використовуватися для реалізації примусового виходу користувача та повторного відображення діалогового вікна для введення імені користувача та пароля. Деякі розробники використовують це для обмеження авторизації за часом або надання кнопки "Вихід".

Приклад #3 Приклад HTTP-автентифікації з введенням нової пари логін/пароль

function authenticate () (
header ( "WWW-Authenticate: Basic realm="Автоматичне тестування системи"");
header ("HTTP/1.0 401 Unauthorized");
echo "Ви повинні ввести коректний логін та пароль для отримання доступу до ресурсу \n";
exit;
}

if (!isset($_SERVER ["PHP_AUTH_USER"]) ||
($_POST [ "SeenBefore" ] == 1 && $_POST [ "OldAuth" ] == $_SERVER [ "PHP_AUTH_USER" ]))) (
authenticate();
) else (
echo "

Ласкаво просимо: ". htmlspecialchars ($_SERVER ["PHP_AUTH_USER"]) . "
" ;
echo "Попередній логін:". htmlspecialchars ($_REQUEST ["OldAuth"]);
echo "

\n";
echo "\n";
echo ". htmlspecialchars ($_SERVER ["PHP_AUTH_USER"]) . "\" />\n" ;
echo "\n";
echo "

\n" ;
}
?>

Ця поведінка не регламентується стандартами HTTP Basic-аутентифікації, отже, ви повинні залежати від цього. Тестування браузера Lynxпоказало, що Lynxне очищає кеш авторизації при отриманні від сервера статусу 401, і, натиснувши послідовно "Back", а потім "Forward" можна відкрити таку сторінку, за умови, що необхідні атрибути авторизації не змінилися. Однак користувач може натиснути клавішу "_" для очищення кешу автентифікації.

Для того, щоб досягти коректної роботи HTTP-аутентифікації в IIS сервері з CGI версією PHP, ви повинні відредагувати конфігураційне налаштування IIS під назвою " Directory Security". Клацніть на написі " Edit" та встановіть опцію " Anonymous Access"Всі інші поля повинні залишитися невідзначеними.

Зауваження: Примітка щодо IIS:
Для того, щоб HTTP-автентифікація коректно працювала в IIS, у конфігурації PHP опція cgi.rfc2616_headers має бути встановлена ​​значенням 0 (значення за замовчуванням).

Зауваження:

У випадку, якщо використовується безпечний режим, UID поточного скрипту буде додано до realm-частина заголовка WWW-Authenticate.

Обмеження доступу до будь-якої області сайту зазвичай виглядає
одноманітно: кожному користувачеві видається логін та пароль чи він сам
їх вибирає, і для входу до захищеної частини сайту їх потрібно ввести. З технічної точки зору для перевірки пароля використовуються
різні методи. Для введення логіну та пароля може використовуватися HTML-форма.
У цьому випадку пароль передається на сервер відкритим текстом у запиті POST.
Це неприйнятно, якщо користувач сидить у локалці, де можливо
використання сніфера. Для вирішення цієї проблеми придумано метод
аутентифікації за допомогою хешів, при якому пароль не передається, а
передається хеш рядок, що залежить від пароля, якогось одноразового
параметра та, можливо, ще від будь-яких параметрів. Цей метод ще
називають challenge/response, оскільки при його використанні клієнт
отримує запит з одноразовим параметром і надсилає відповідь, що містить хеш. На рівні протоколу HTTP 1.1 можлива автентифікація методом
Basic, що ні чим не краще за використання HTML-форми, і Digest, який
ми розглянемо докладно.

При використанні методу Digest, як було зазначено, пароль
не передається, і його неможливо відзняти, проте є й інша сторона
проблеми. Для того, щоб перевірити пароль, сервер має обчислити
відповідь і порівняти його з відповіддю клієнта, отже, на сервері повинен
зберігатися пароль або залежні від нього дані, необхідні для
проходження аутентифікації. Звідси випливає, що людина, яка отримала права
на читання облікових записів (наприклад, за допомогою SQL-injection), зможе отримати
доступ до сторінок, захищених методом Digest. При використанні методу
Basic можливе зберігання хешей замість паролів, що не дає підняти права,
прочитавши ці хеші (нижче ми побачимо, що в Digest теж можуть зберігатися хеші,
але такі, що їх знання достатньо для обчислення відповіді. Таким чином, перед нами дилема: або наш пароль відзніфять,
або отримають через web-уразливість, яку хтось обов'язково знайде,
бо хто шукає, той завжди знайде. Є метод аутентифікації без
обох цих недоліків - метод аутентифікації на основі відкритого ключа:
для перевірки потрібен відкритий ключ, а для проходження перевірки – секретний,
однак у HTTP 1.1 такий метод не передбачено. RFC 2069
рекомендує використовувати SSL, якщо захист такий важливий. Захищається лише передача пароля, а контент не шифрується, так
що немає сенсу захищати цим методом ресурси, звідки користувач
отримує таємну інформацію. Для них потрібний SSL. А має сенс
захищати, наприклад, форум або заливання контенту на сайт. Отже, якщо хостинг не підтримує SSL, а автентифікація повинна
бути безпечним, то будемо використовувати Digest. В Apache передбачено модуль mod_digest. Для його використання
у конфізі (або ст.htaccess) пишемо:

AuthType Digest
AuthUserFile<файл>
AuthName<название защищаемой области>
Require valid_user

Файли користувачів створюються утилітою
htdigest. Про mod_digest у свій час з'являлися повідомлення, що він вразливий, так що,
можливо, там ще якісь проблеми виявляться. Крім того, коли
я спробував його використати у себе вдома, отримав помилку
500 Server Internal Error. Крім того, якщо додавання облікових записів повинно відбуватися
автоматично, і їх має бути багато, вони мають
зберігатися над конфізі Апача, а MySQL. Рішення -
використовувати PHP. У PHP немає вбудованої підтримки цього
методу, тому його доведеться реалізувати. Для цього необхідно вивчити
цей метод детально. Відразу зауважу, що наведена у цій статті
реалізація працює тільки на Apache, тому що повний доступ до заголовків
запиту (функція apache_request_headers) працює тільки в Apache, а на
інших серверів може бути відсутнім. Нам просто необхідно прочитати
заголовок Authorization.

Опис методу

Повністю опис методу можна прочитати в RFC 2069, а якщо
Коротко, то метод працює так. Коли сервер отримує запит, що стосується захищеної області,
він видає помилку 401 Authorization Required і заголовок із запитом
автентифікації такого виду:

WWW-Authenticate: Digest realm="secure area", nonce="123456123456"

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

Authorization: Digest realm="середня область", username="123", uri="/index.php", nonce="123456123456", response="1234567890abcdef1234567890abcdef"

Параметр uri повинен збігатися з URI у запиті, а response – це
відповідь, яка обчислюється так:

response = H(H(A1) + ":" + nonce + ":" + H(A2))
H - хеш-функція, за замовчуванням MD5
A1 = логін + ":" + realm + ":" + пароль
A2 = метод запиту + ":" + URI
метод запиту – це GET, POST і тд.

Як бачимо, A1 не залежить ні від запиту, ні від одноразового
значення, тому на сервері може зберігатися не пароль, а
H(A1). Саме так це реалізовано в mod_digest в Apache.
Однак цих даних достатньо і клієнту. Зловмисник, отримавши
цей хеш може обчислити відповідь за наведеними вище формулами і
сформувати HTTP-запит, наприклад, за допомогою програми
AccessDriver та її інструмент HTTP
Debugger. Докладніше цей процес буде показаний нижче. Сервер повинен перевірити, чи є одноразове значення
тим, яке раніше видано клієнту і чи не застаріло воно.
Якщо відповідь відповідає параметру nonce, але значення цього параметра
не актуально, видається описана вище відповідь з кодом 401 з тією
різницею, що до заголовка WWW-Authenticate додається параметр
stale=true, що вказує, що у доступі відмовлено лише з цієї причини,
і слід повторити спробу, не запитуючи у користувача новий пароль. Це, імхо, незручно, бо якщо така ситуація виникне
при запиті POST або PUT з великим блоком даних, клієнту доведеться
передати всі дані двічі. Щоб уникнути цього стандартом, передбачено
заголовок Authentication-Info, в якому сервер може відповідати на
успішний запит повідомити клієнта про наступне одноразове значення.
Синтаксис такий же, як у WWW-Authenticate, крім того, що nonce
замінюється на nextnonce. Однак, судячи з результатів моїх
Експерименти Opera ігнорує цей заголовок. Інше рішення: відповідно до
RFC 2068 (HTTP/1.1), сервер може відповісти раніше, ніж завершиться запит,
щоб клієнт перервав непотрібну передачу даних, але на Apache+PHP це
не реалізується, оскільки скрипт починає виконуватися лише після того,
як Apache повністю отримає та пропарсить запит.

Зберігання даних між запитами

У реалізації методу challenge/response на PHP є тонкий момент.
Одноразовий параметр формується та видається клієнту в одній відповіді, а
перевіряється вже у іншому сеансі роботи скрипта.
Тобто, його необхідно зберегти від одного виклику скрипта до іншого, і для цього доведеться
використовувати файли чи БД. У моєму прикладі використовуються файли з іменами,
відповідні одноразові значення, а в самих файлах записані
IP-адреси клієнтів, яким вони видані. У прикладі не реалізовано збір
сміття: потрібно періодично видаляти старі файли.

Розбір коду

Цей скрипт перевіряє лише пароль, і працює незалежно від
логіна. Залежно від успішності перевірки видаються найпростіші відповіді.

$ realm = "secure area"; // Назва області, що захищається
$pass = "pass"; // Пароль
$fileprefix = "./"; // Шлях для файлів-міток, що позначають валідність nonce

/* Сконструюємо одноразовий параметр так, як рекомендується RFC2069, хоча можна і по-іншому. Параметр за рекомендацією повинен залежати від адреси клієнта, поточного часу та секретного рядка. */
$nonce = md5($_SERVER["REMOTE_ADDR"] . ":" . time() . ":MyCooolPrivateKey");

// Отримуємо заголовки
$headers = apache_request_headers();

// Прапор, який ми встановимо у TRUE при успішній перевірці
$ auth_success = FALSE;
$ stale = "";

// Якщо немає заголовка Authorization, то нічого й перевіряти
if (isset($headers["Authorization"]))
{
$authorization = $headers["Authorization"];

/* Пропаруємо заголовок за допомогою регулярного виразу. Заголовок містить слово "Digest" та список
пареметрів виду param="value" або param=value через кому. Цей регулярний вираз відповідає одному такому параметру.
*/
preg_match_all("/(,|\s|^)(\w+)=("([^"]*)"|([\w\d]*))(,|$)/",
$authorization, $matches, PREG_SET_ORDER);

/* Тепер сформуємо для зручності подальшої обробки масив, де ключі – назви параметрів, а значення елементів масиву –
значення параметрів.
*/
$auth_params = Array();
for ($i = 0; $i< count($matches); $i++)
{
$match = $matches[$i];

/* Назва завжди у другій групі дужок, у значення залежно від того, у лапках вона чи ні, може
бути у 4-й чи 5-й групі. Для груп дужок, що потрапили
у нереалізовану гілку, у масиві порожній рядок,
тому можна просто скласти значення.
*/
$auth_params[$match] = $match . $ match;
}

/* Обчислимо відповідь, яка відповідає
логіну, введеному користувачем, паролю і одноразовому параметру, переданому користувачем.
*/
$a1 = $auth_params["username"] . ":". $auth_params["realm"] . ":". $pass;
$a2 = $_SERVER["REQUEST_METHOD"] . ":". $_SERVER["REQUEST_URI"];
$resp = md5(md5($a1) . ":" . $auth_params["nonce"] . ":" . md5($a2));

// Перевіряємо відповідь.
if ($resp == $auth_params["response"])
{
//
Перевіряємо актуальність одноразового параметра
$fn = $fileprefix . $auth_params["nonce"];
if (@file_get_contents($fn) == $_SERVER["REMOTE_ADDR"])
{
unlink($fn); //
Більше цей параметр неактуальний
$ auth_success = TRUE; //
Аутентифікацію пройдено
) else
{
// Одноразовий параметр неактуальний
$ stale = ", stale = true";
}
}
}

if ($auth_success)
{
print(" Digest auth test

print("Successfully authenticated\n");
var_dump($auth_params);

print("");

) else
{
file_put_contents($fileprefix . $nonce, $_SERVER["REMOTE_ADDR"]);

$proto = $_SERVER["SERVER_PROTOCOL"];
Header("$proto 401 Not Authorized");
Header("WWW-Authenticate: Digest realm=$realm", nonce=$nonce$stale);

print(" Digest auth test

");
print("You must authenticate with Digest method");
print("


");
}

Проходження Digest Auth за відомого H(A1)

Покажу на прикладі, як проходити перевірку, якщо пароль невідомий,
але відомий H(A1). Для цього, як уже було сказано, знадобиться
AccessDriver. Розрахунки хешей я робитиму викликаючи з командного рядка
PHP CLI. Захищена сторінка нехай знаходиться за адресою
http://mrblack.local/auth1.php, а хеш H(A1) дорівнює "a8fb5b2d780a7bf0782207a51a013f04".

Відкриваємо AccessDriver->Tools->HTTP Debugger та вбиваємо адресу
"http://mrblack.local/auth1.php". Тиснемо "Connect". Отримуємо:

HTTP Header = HTTP/1.1 401 Authorization Required
HTTP Header = Date: Mon, 04 Jul 2005 08:09:17 GMT
HTTP Header = Server: Apache/1.3.31 (Win32) PHP/5.0.2
HTTP Header = X-Powered-By: PHP/5.0.2
HTTP Header = WWW-Authenticate: Digest realm="secure area", nonce="5925bea78552224abda11bfe318a8a03"
HTTP Header = Connection: close
HTTP Header = Content-Type: text/html

Відкриваємо консоль, переходимо до папки з PHP і вбиваємо таку команду:

php -r "print md5("a8fb5b2d780a7bf0782207a51a013f04:
: ".md5("GET:http://mrblack.local/auth1.php"));"

Отримуємо шукану Digest-відповідь: c6d0af0db239d75c
3f59640a4896d096
Тепер в AccessDriver ставимо галочку "Header Data", копіюємо в те, що з'явилося.
поле заголовки, які були надіслані в минулому запиті, та дописуємо до них
Authorization. Ось що виходить:

GET http://mrblack.local/auth1.php HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, */*
Accept-Language: en-us,en;q=0.5
User-Agent: Mozilla compatible
Host: mrblack.local
Pragma: no-cache
Authorization: Digest username="mrblack", realm="secure area", nonce="5925bea78552224ab
da11bfe318a8a03", uri="http://mrblack.local/auth1.php", response="c6d0af0db239d75c3f59
640a4896d096"

Тиснемо "Connect". Отримуємо результат:



Щоб надіслати браузеру клієнта повідомлення "Authentication Required", що у свою чергу призведе до появи діалогового вікна для введення імені користувача та пароля. Після того як клієнт ввів своє ім'я та пароль, скрипт буде викликаний повторно, але вже з визначеними змінними PHP_AUTH_USER , PHP_AUTH_PW і AUTH_TYPE , які містять ім'я користувача, пароль і тип аутентифікації. Ці змінні можна знайти в масиві $_SERVER і $HTTP_SERVER_VARS . В даний час підтримується лише "Basic"-автентифікація. Також ви можете ознайомитись з більш детальним описом функції header() .

Приклад фрагмента скрипта, який змушує клієнта авторизуватись для перегляду сторінки:

Приклад HTTP-автентифікації

if (!isset($_SERVER ["PHP_AUTH_USER"])) (
header ( "WWW-Authenticate: Basic realm="My Realm"");

echo "Текст, який надсилається в тому випадку,
якщо користувач натиснув кнопку Cancel"
;
exit;
) else (
echo
"

Hello ($_SERVER["PHP_AUTH_USER"]).

"
;
echo "

Ви ввели пароль ($_SERVER["PHP_AUTH_PW"]).

"
;
}
?>

Примітка щодо сумісності:Будьте особливо уважні при вказівці заголовків HTTP. Для того, щоб гарантувати максимальну сумісність з найбільшою кількістю різних клієнтів, слово "Basic" має бути написане з великої літери "B", регіон (realm) повинен бути взятий у подвійний (не одинарні!) лапки, і рівно одна прогалина повинна передувати коду 401 у заголовку HTTP/1.0 401 .

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

Ви можете переглянути особливості роботи браузера Internet Explorer. Він дуже вимогливий до параметра заголовків, що передаються. Вказівка ​​заголовка WWW-Authenticateперед відправкою статусу HTTP/1.0 401 є невеликою хитрістю.

Починаючи з PHP 4.3.0, для того, щоб запобігти написанню будь-ким скрипту, що розкриває пароль до сторінки, яка використовує зовнішню автентифікацію, змінні PHP_AUTH не встановлюються у випадку, якщо ця сторінка використовує зовнішню автентифікацію та встановлений безпечний режим. Незважаючи на це, змінна REMOTE_USER може використовуватися для автентифікації користувача, що пройшов зовнішню автентифікацію. Таким чином, ви завжди можете скористатися змінною $_SERVER["REMOTE_USER"] .

Примітка: PHP використовує вказівку директиви AuthType для вказівки того, використовується зовнішня автентифікація чи ні.

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

І Netscape Navigator та Internet Explorer очищають кеш аутентифікації поточного вікна для заданого регіону (realm) при отриманні сервера. Це може використовуватися для реалізації примусового виходу користувача та повторного відображення діалогового вікна для введення імені користувача та пароля. Деякі розробники використовують це для обмеження авторизації за часом або надання кнопки "Вихід".

Приклад HTTP-автентифікації з примусовим введенням нової пари логін/пароль

function authenticate () (
header ( "WWW-Authenticate: Basic realm="Автоматичне тестування системи"");
header ("HTTP/1.0 401 Unauthorized");
echo "Ви повинні ввести коректний логін та пароль для отримання доступу до ресурсу \n";
exit;
}

If (!isset($_SERVER ["PHP_AUTH_USER"]) ||
($_POST [ "SeenBefore" ] == 1 && $_POST [ "OldAuth" ] == $_SERVER [ "PHP_AUTH_USER" ]))) (
authenticate ();
}
else (
echo
"

Ласкаво просимо: ($_SERVER["PHP_AUTH_USER"])
" ;
echo "Попередній логін: ($_REQUEST["OldAuth"])";
echo "

\n";
echo "\n";
echo "\n";
echo "\n";
echo "

\n" ;
}
?>

Ця поведінка не регламентується стандартами HTTP Basic-аутентифікації, отже ви не повинні залежати від цього. Як показали тести, браузер Lynx не очищає кеш авторизації при отриманні від сервера статусу 401, і, натиснувши послідовно "Back", а потім "Forward", можна відкрити таку сторінку, за умови, що необхідні атрибути авторизації не змінилися. Однак, користувач може натиснути клавішу "_", щоб очистити кеш аутентифікації.

Також слід зауважити, що до версії PHP 4.3.3, HTTP-автентифікація не працювала на серверах під керуванням Microsoft IIS, якщо PHP був встановлений як CGI-модуль через деякі обмеження IIS. Для того, щоб досягти коректної роботи в PHP 4.3.3+, ви повинні відредагувати конфігураційне налаштування IIS під назвою "Directory Security". Клацніть на написі "Edit" і встановіть опцію "Anonymous Access", усі інші поля мають залишитися невідзначеними.

Ще одне обмеження, якщо ви використовуєте IIS за допомогою ISAPI: змінні PHP_AUTH_* не визначені, але в той же час доступна змінна HTTP_AUTHORIZATION . приклад коду, який ви могли б використовувати: list($user, $pw) = explode(":", base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6))));

Примітка щодо IIS:Для того, щоб HTTP-автентифікація коректно працювала в IIS, у конфігурації PHP опція cgi.rfc2616_headers повинна бути встановлена ​​значенням 0 (за замовчуванням).

Увага:У випадку, якщо використовується захищений режим, UID поточного скрипту буде додано в realm-частину заголовка WWW-Authenticate.



<<< Назад Зміст Вперед >>>
Є ще питання чи щось незрозуміло - ласкаво просимо на наш