Пароль на сторінку. Частина 1. Швидше теоретична. , Захист додатків і безпеку, PHP, статті

Я вирішив описати способи закрити паролем частина сайту. Тема, насправді, велика, тому на перший раз обмежусь авторизацією 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. Блокування підбору


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

Але спочатку про блокування підбору. Банальності, але все-таки. Пароль довгою десять символів з букв латиниці і цифр – це дуже багато варіантів. Якщо підбирати пароль за 1000000 варіантів у секунду, знадобиться кілька тисяч років. Але оскільки таку абракадабру запам’ятати складно, ми частіше робимо пароль з осмислених слів. Кілька років тому виявилося, що більшість паролів можна підібрати за допомогою словника з 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>

*

*