Пароль на сторінку. Частина 1. Швидше теоретична.

Я вирішив описати способи закрити паролем частина сайту. Тема, насправді,
велика, тому на перший раз обмежуся авторизацією php + mysql.

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

Додам дві речі. Перше – це куди класти файл. Htpasswd. Експериментальним
шляхом я з'ясував, що якщо, наприклад, шлях до документа з повідомленням про помилку
(ErrorDocument) пишеться щодо системної змінної DocumentRoot. Але шлях
до файлу з паролями (UserFile) пишеться щодо ServerRoot. Наскільки я
зрозумів, вище ServerRoot покласти. htpasswd не можна – "../" не сприймається. Всі
це зроблено для того, щоб можна було помістити файл із паролями, наприклад,
одним рівнем вище кореневої директорії сайту, щоб з мережі доступу до файлу не
було взагалі.

Друге – це те, що скрипт може дізнатися, хто його відкриває та пароль:
змінні $ PHP_AUTH_USER і $ PHP_AUTH_PW.

Головний недолік цього способу – сервер не може блокувати підбір пароля
(Це після декількох невдалих спроб входу користувачеві пропонується
почекати годинку-другу, а протягом цього часу звернення з його IP-адреси
ігноруються). Це написано в офіційній документації по Апач.

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

Автоматизація авторизації

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

Кожна сторінка закритій території підключає файл з ось таким кодом:

$result = mysql_query("
SELECT * FROM person WHERE
login = "". preg_replace ("/[^ w_-]/","",$ PHP_AUTH_USER). ""
AND pass=”". md5($PHP_AUTH_PW). "”");
if (@mysql_num_rows($result)!=1) {
header ("WWW-Authenticate: Basic realm =" User area "");
header("HTTP/1.0 401 Unauthorized");
print ("Щоб увійти в призначену для користувача частину сайту, треба
ввести ім'я та пароль. ");
exit();
};
$user_row = mysql_fetch_array($result);

У першому рядку з логіна видаляються всі символи крім літер, цифр, тире і
символу підкреслення. Потім перевіряється кількість отриманих рядків, і тільки
якщо це один рядок, дається доступ. В інших випадках користувач побачить у
броузері вікно, що пропонує ввести логін і пароль. Якщо ж користувач увійшов
успішно, в масиві $ user_row ми маємо всю інформацію про нього.

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


І останній на сьогодні спосіб – зберігання зашифрованих даних у куках.

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

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

Всі інші програми підключають код, який робить таке. Робить
запит до бази – вибирає рядок з отриманим логіном. З цього рядка бере полі
"Log_time" і пароль і робить з них, як і описано вище, хеш. Порівнює його з
тим, що отримав, і якщо вони збігаються, видає нову куку хеша, знову ж таки, від
пароля, часу і букви "И" та робить запит до бази даних "UPDATE user SET
log_time=”…” WHERE login=”$cookie_login””.

if (isset($HTTP_COOKIE_VARS[$cookie_login]) &&
isset($HTTP_COOKIE_VARS[$cookie_code])) {
$login = $HTTP_COOKIE_VARS[$cookie_login];
$code = $HTTP_COOKIE_VARS[$cookie_code];
$ Result = mysql_query ("SELECT date_format (log_date,"% Y% m% d% H% i% s ") as log_date1,
pass,uid FROM user
WHERE email=”$login”
AND log_date> "DATE_SUB (NOW (), INTERVAL 15 MINUTE )"");
if (!mysql_error() && @mysql_num_rows($result)==1) {
$log_time0 = time();
$log_time1 = date("YmdHis", $log_time0);
$log_time2 = date("Y-m-d H:i:s", $log_time0);
$current_user = mysql_fetch_array($result);
if (md5 ($ current_user ["pass"]. $ current_user ["log_date1"]. $ md5letter) == $ code) {
mysql_query("UPDATE user SET log_date=”$log_time2″
WHERE uid = ". $ Current_user [" uid "]);
setcookie ($ cookie_code, md5 ($ current_user ["pass"]. $ log_time1. $ md5letter),
time()+900, $site_path);
$auth = true;
}
else
unset($current_user);
};
};

Знову ж таки, тут немає ніякого захисту від підбору і атаки на сервер (до речі,
тут можна замість букви "И" писати IP-адресу користувача – щоб, наприклад,
сусідові по офісу не можна було взяти файл з кукой і зайти зі свого комп'ютера).

Пароль на сторінку. Частина 2. Блокування підбору


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

Але спочатку про блокування підбору. Банальності, але все-таки. Пароль довгою
десять символів з букв латиниці і цифр – це дуже багато варіантів. Якщо
підбирати пароль по 1 000 000 варіантів у секунду, знадобиться кілька тисяч
років. Але оскільки таку абракадабру запам'ятати складно, ми частіше робимо пароль з
осмислених слів. Кілька років тому виявилося, що більшість паролів можна
підібрати за допомогою словника з 10 000 слів. Свого часу в мережі з'явився хробак
(Вірус такий), який лазив по юніксовим серверів, використовуючи їх дірки в захисті,
і підбирав паролі прівелігірованих користувачів за допомогою … системного
орфографічного словника Юнікс. Нічого тягати не треба було!

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


Я довго думав, як можна викликати перевантаження на сервері, якщо механізм захисту
стоїть на файлах. Виявилося, нескладно (скільки це буде коштувати – інше питання).
Отже, припустимо, сервер не витримає, якщо скрипт буде намагатися 1000 разів на
секунду відкривати файли на запис і писати в них дані. Оскільки після 5
невдалих спроб увійти в систему користувач буде відразу отримувати відмову в
доступі (без будь-якої запису даних у файл), треба знайти 200 унікальних IP, з
яких по п'ять разів і звернутися. Це можливо. Вішаємо в баннерокрутилку
html-банер з п'ятьма тегами:

<img src="http://www.ishodniki.ru/"http://user:password@www.host.ru/secret/absent.gif" width=1 height=1>

Користувач моментально робить п'ять звернень сервер п'ять разів пише в файл
(До речі, в деяких броузерах, можливо, вискочить вікно для введення логіна і
пароля). Можна зробити html-сторінку з п'ятьма такими картинками, а саму сторінку
вставити через iframe на відвідуваний сайт (через iframe – щоб по полю referer
не знайшли. Навряд чи служба підтримки халявного хостингу буде займатися такими
речами як копання в лог-файлах у пошуках реферерів). Ті приклади, які я
привів, зрозуміло, натягнуті, але сам факт того, що можна скористатися таким
недоліком системи, доведений. До речі, щось подібне вже було.

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

order deny,allow
deny from all
allow from xxx.xxx.xxx

А ось код програми:

$errors = 0;
$ Fn = "ignore /". preg_replace ("[^ d.]", "", $ REMOTE_ADDR. ".
". $HTTP_FORWARDED_FOR);
if (is_file($fn)) {
if (filectime($fn) < time()-3600)
unlink($fn);
}
else
$errors = fread(fopen($fn, "r"), 2);

if ($errors>5) {
print ("Доступ закритий. Зайдіть через годину.");
exit();
};

/ / Тут відбувається установка зв'язку з сервером БД.
/ / Щоб не чіпати даремно, якщо користувача відразу ж "відгамселили".

$result = mysql_query("SELECT * FROM user WHERE
login = "". preg_replace ("/[^ w_-] / "," ", $ PHP_AUTH_USER). "" AND
pass=”". md5($PHP_AUTH_PW). "”");
if (@mysql_num_rows($result)!=1) {
header ("WWW-Authenticate: Basic realm =" secret area "");
header("HTTP/1.0 401 Unauthorized");
print ("Authorization required");
fwrite(fopen($fn, "w"), ++$errors);
exit();
};
$current_user = mysql_fetch_array($result);
mysql_free_result($result);


Втім, гріх працювати з файлами, якщо є база. Жарт. Хто не пройшов
авторизації створюємо таблицю:

CREATE TABLE unauth (username VARCHAR(64) NOT NULL,
pass VARCHAR(64) NOT NULL,
ip VARCHAR(255),
logintime TIMESTAMP)

І замість звернення до файлів працюємо з базою.

$ Errors = @ mysql_result (mysql_query ("SELECT count (username) as falses
FROM unauth WHERE
logintime> DATE_SUB (NOW (), INTERVAL 1 HOUR) AND ip = "$ REMOTE_ADDR" "), 0);
if (mysql_error())
die(mysql_error());
if ($errors>5) {
print ("Доступ закритий. Зайдіть через годину.");
exit();
};
$result = mysql_query("SELECT * FROM user WHERE
login = "". preg_replace ("/[^ w_-] / "," ", $ PHP_AUTH_USER). "" AND
pass=”". md5($PHP_AUTH_PW). "”");
if (@mysql_num_rows($result)!=1) {
header ("WWW-Authenticate: Basic realm =" secret area "");
header("HTTP/1.0 401 Unauthorized");
print ("Authorization required");
mysql_query ("INSERT INTO unauth (username, pass, ip) VALUES
("$ PHP_AUTH_USER", "$ PHP_AUTH_PW", "$ REMOTE_ADDR $ HTTP_X_FORWARDED_FOR ")");
exit();
};
$current_user = mysql_fetch_array($result);
mysql_free_result($result);

Зберігати чи старі записи для статистики чи ні – справа хазяйська. Якщо що,
їх можна видаляти, виконуючи перед авторизацією запит:

DELETE FROM unauth WHERE logintime <DATE_SUB (NOW (), INTERVAL 1 HOUR)

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

Пароль на сторінку. Частина 3. Пароль від бази


Була у мене свого часу проблема: треба закрити адміністративний частина
сайту, але при цьому я не можу покласти файл. htpasswd вище кореневої директорії
сайту. Вроджена підозрілість не дозволяла покласти файл з паролем і
окрему директорію і заблокувати доступ до неї по http. Вирішив спробувати
зробити захист як у phpMyAdmin: у користувача запитуються логін і пароль, з
якими скрипт з'єднується з базою. У своєму аналізаторі логів я зробив саме
так. Зручність методу в тому, що файл можна складати куди завгодно – ніяких кук,
ніяких директив сервера для директорії. Заодно, якщо зміниться пароль в базі
даних, не треба нічого виправляти в скрипті.

Розпишу метод на прикладі MySQL. Пишемо функцію, наприклад, mysql_die:

function mysql_die() {
header("HTTP/1.0 401 Unauthorized");
header ("WWW-authenticate: basic realm =" Statistics "");
print ("Access denied. User name and password required.");
exit();
}

На початку програми вказуються хост сервера БД і, якщо треба, ім'я бази:

$db_host = "localhost";
$db_name = "somedatabase";

А для з'єднання з базою беруться змінні сервера: $ PHP_AUTH_USER і
$PHP_AUTH_PW.

$ Db_connect = @ mysql_connect ($ db_host, $ PHP_AUH_USER, $ PHP_AUTH_PW) or mysql_die ();

І все. Тепер про недоліки. Зрозуміло, з таким захистом можна пробувати
підбирати пароль (в принципі, можна приробити блокування, але тоді загубиться
вся краса методу). Пароль, як і у випадку захистом засобами сервера,
пересилається у відкритому вигляді. Але для простих завдань таке цілком згодиться.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*