Как сделать авторизацию php

Безопасный метод авторизации на PHP

Давайте посмотрим вокруг: форумы, интернет магазины, гостевые книги и т.д. используют регистрацию и последующую авторизацию пользователей. Можно даже сказать, что это почти необходимая функция каждого сайта (только если это не домашняя страничка Васи Пупкина или не визитная карточка, какой-нибудь небольшой компании). Сегодня я хочу поделиться со всеми новичками информацией, о том, как лучше это все реализовать.

Модель авторизации:

Клиент
Сервер MySQL

При регистрации в базу данных записывается логин пользователя и пароль(в двойном md5 шифровании)

При авторизация, сравниваеться логин и пароль, если они верны, то генерируеться случайная строка, которая хешируеться и добавляеться в БД в строку user_hash. Также записываеться IP адрес пользователя(но это у нас будет опциональным, так как кто-то сидит через Proxy, а у кого-то IP динамический. тут уже пользователь сам будет выбирать безопасность или удобство). В куки пользователя мы записываем его уникальный индетификатор и сгенерированный hash.

Почему надо хранить в куках хеш случайно сгенерированной строки, а не хеш пароля?
1. Из-за невнимательности программиста, во всей системе могут быть дырки, воспользовавшийсь этими дырками, злоумышленик может вытащить хеш пароля из БД и подставить его в свои куки, тем самым получить доступ к закрытым данным. В нашем же случае, двойной хеш пароля не чем не сможет помочь хакеру, так как расшифровать он его не сможет(теоретически это возможно, но на это он потратит не один месяц, а может быть и год) а воспользоваться этим хешем ему негде, ведь у нас при авторизации свой уникальный хеш прикрепленный к IP пользователя.
2. Если злоумышленик вытащит трояном у пользователя уникальный хеш, воспользовать им он также не сможет(разве если только, пользователь решил принебречь своей безопастностью и выключил привязку к IP при авторизации).

Реализация

Структура таблицы `users` в базе данных ‘testtable’

register.php

login.php

check.php

logout.php

Для защиты формы логина от перебора, можно использовать капчу или временную задержку на повторную авторизацию.

Автор: http://jiexaspb.habrahabr.ru/. Адаптация под PHP 5.5 и MySQL 5.7 KDG.

Куки с флагом HttpOnly не видны браузерному javascript-коду, а отправляются только на сервер. На практике у вас никогда нет необходимости получать их содержимое в javascript. А вот злоумышленнику, нашедшему XSS — а XSS так или иначе когда-нибудь где-нибудь найдется — отсутствие HttpOnly на авторизационных куках доставит много радости.

Источник

Создание форм регистрации и авторизации на PHP

Система регистрации и авторизации необходима для любого сайта, который хранит информацию о своих пользователях. Такие системы используются на сайтах самой разнообразной тематики – от образовательных платформ, которые хранят сведения о прохождении обучающих курсов и оценках, до онлайн-магазинов, которые сохраняют историю покупок и адреса пользователей. В этом руководстве мы научим вас создавать формы регистрации и авторизации с нуля.

Разработка форм регистрации и авторизации

Мы рассмотрим процесс создания простых форм для регистрации и авторизации пользователей. Регистрационная форма будет содержать поля для ввода имени, пароля и адреса электронной почты. Имя пользователя и адрес электронной почты при этом должны быть уникальными для каждого конкретного пользователя. В случае попытки регистрации второй учетной записи с таким же именем пользователя (или электронной почтой) будет выводиться сообщение об ошибке с пояснением о том, что такие данные уже используются в системе.

Код регистрационной формы

Ниже приведен HTML-код необходимый для создания формы регистрации. Сохраните его вфайле register.php.

Несмотря на простоту данной формы, для проведения простейшей валидации данных в ней используется HTML5. К примеру, использование type=»email» обеспечит уведомление пользователя о том, что он неправильно ввел адрес электронной почты. Кроме того, применение pattern позволит провести проверку имени пользователя – логин может состоять только из латинских букв и цифр.

Наиболее продвинутый способ валидации данных подразумевает использование jQuery – в этом случае разработчик получает полный контроль над показом, расположением и внешним видом сообщений об ошибках ввода. Подробнее о валидации на стороне клиента с использованием jQuery рассказывается в этой статье.

Исходный код страницы авторизации

CSS-стили для оформления форм

Для улучшения внешнего вида форм примените к ним следующие CSS-стили:

В коде, приведенном выше, предусмотрено оформление заголовков и сообщений об ошибках валидации. Фрагменты HTML и CSS кода, рассмотренные выше, могут использоваться в качестве основы, поскольку ваш собственный проект может нуждаться в другом стиле оформления, а также в дополнительных полях ввода.

Создание таблицы с учетными данными и подключение к базе данных

Следующий шаг – создание таблицы базы данных, содержащей учетные данные пользователей. В нашем случае таблица состоит всего из четырех столбцов:

Для быстрого создания таблицы базы данных можно использовать следующий SQL-запрос:

Теперь создайте файл config.php и сохраните в нем приведенный далее код для подключения к базе данных:

В приведенном выше коде замените название базы данных на то, которое вы используете для своего собственного сайта.

Исходный код для регистрации пользователей

Теперь пришла очередь написать код для регистрации пользователей. Главная функция этого кода – проверить, не зарегистрирован ли уже аналогичный адрес электронной почты в базе данных. Если нет, новое имя пользователя вместе с адресом его электронной почты и паролем передаются для сохранения в базе данных.

Сохраните приведенный далее код в начале файла registration.php :

На первом этапе выполнения кода включается config.php, начинается сессия. Так мы получаем возможность сохранить любую информацию для дальнейшего использования на всех страницах сайта.

После этого мы проверяем, существует ли предоставленный пользователем адрес электронной почты в базе данных. Если это так, пользователь получит соответствующее сообщение. Если же такого email-адреса в базе данных, используемой сайтом, нет, вся введенная информация сохраняется в базе данных и пользователь видит сообщение об успешной регистрации.

Функция авторизации

На предыдущем этапе мы уже сохранили код для формы авторизации пользователей в системе. На этом этапе мы будем проверять, соответствуют ли введенные пользователем данные учетной записи, сохраненной в базе.

Приведенный далее код должен располагаться в начале файла login.php:

Важно отметить, что мы не проверяем правильность имени и пароля одновременно. Поскольку пароль сохранен в хэшированном виде, сначала необходимо запросить хэш с помощью предоставленного имени пользователя. Когда мы получим хэш, можно будет проверить предоставленный пользователем пароль на соответствие хэшированному – с помощью функции password_verify().

Ограничение доступа к страницам

На большинстве сайтов, запрашивающих учетные данные посетителей, есть страницы, на которых зарегистрированные пользователи хранят свою личную информацию. Для защиты подобных страниц от несанкционированного доступа можно использовать переменные сессии. Если переменная сессии не создана, пользователь перенаправляется на страницу авторизации. Если переменная сессии создана, пользователь видит содержимое страницы:

Все, что нужно сделать для ограничения или предоставления доступа – это использовать в начале приведенного выше скрипта строку session_start().

Типичные ошибки и способы их решения

При использовании скрипта для ограничения доступа неавторизованных пользователей обычно возникают три типа ошибок.

Некорректное имя переменной

«Заголовки уже отправлены»

Переменные сессии не сохраняются при переходах между страницами

Источник

Безопасный метод авторизации на PHP

Примечание: мини-статья написана для новичков

Давайте посмотрим вокруг: форумы, интернет магазины, гостевые книги и т.д. используют регистрацию и последующую авторизацию пользователей. Можно даже сказать, что это почти необходимая функция каждого сайта (только если это не домашняя страничка Васи Пупкина или не визитная карточка, какой-нибудь небольшой компании). Сегодня я хочу поделиться со всеми новичками информацией, о том, как лучше это все реализовать.

1. Модель (клиент)
Регистрация
— логин (a-z0-9)
— пароль
Вход
— логин
— пароль
Cookie
— уникальный идентификатор юзера
— хэш

Модель (сервер)
MySQL
Таблица users
user_id (int(11))
user_login (Varchar(30))
user_password (varchar(32))
user_hash (varchar(32))
user_ip (int(10)) по умолчанию 0

При регистрации в базу данных записываеться логин пользователя и пароль(в двойном md5 шифровании)

При авторизация, сравниваеться логин и пароль, если они верны, то генерируеться случайная строка, которая хешируеться и добавляеться в БД в строку user_hash. Также записываеться IP адрес пользователя(но это у нас будет опциональным, так как кто-то сидит через Proxy, а у кого-то IP динамический… тут уже пользователь сам будет выбирать безопасность или удобство). В куки пользователя мы записываем его уникальный индетификатор и сгенерированный hash.

Почему надо хранить в куках хеш случайно сгенерированной строки, а не хеш пароля?
1. Из-за невнимательности программиста, во всей системе могут быть дырки, воспользовавшийсь этими дырками, злоумышленик может вытащить хеш пароля из БД и подставить его в свои куки, тем самым получить доступ к закрытым данным. В нашем же случае, двойной хеш пароля не чем не сможет помочь хакеру, так как расшифровать он его не сможет(теоретически это возможно, но на это он потратит не один месяц, а может быть и год) а воспользоваться этим хешем ему негде, ведь у нас при авторизации свой уникальный хеш прикрепленный к IP пользователя.

2. Если злоумышленик вытащит трояном у пользователя уникальный хеш, воспользовать им он также не сможет(разве если только, пользователь решил принебречь своей безопастностью и выключил привязку к IP при авторизации).

2. Практика

— Структура таблицы `users`

CREATE TABLE `users` (

`user_id` int(11) unsigned NOT NULL auto_increment,

`user_login` varchar(30) NOT NULL,

`user_password` varchar(32) NOT NULL,

`user_hash` varchar(32) NOT NULL,

`user_ip` int(10) unsigned NOT NULL default ‘0’,

PRIMARY KEY (`user_id`)

) ENGINE=MyISAM DEFAULT CHARSET=cp1251 AUTO_INCREMENT=1 ;

register.php

// Страница регситрации нового пользователя

$err [] = «Логин может состоять только из букв английского алфавита и цифр» ;

$err [] = «Логин должен быть не меньше 3-х символов и не больше 30» ;

# проверяем, не сущестует ли пользователя с таким именем

$err [] = «Пользователь с таким логином уже существует в базе данных» ;

# Если нет ошибок, то добавляем в БД нового пользователя

# Убераем лишние пробелы и делаем двойное шифрование

header ( «Location: login.php» ); exit();

print «При регистрации произошли следующие ошибки:
» ;

login.php

// Страница авторизации

# Функция для генерации случайной строки

$chars = «abcdefghijklmnopqrstuvwxyzABCDEFGHI JKLMNOPRQSTUVWXYZ0123456789» ;

# Вытаскиваем из БД запись, у которой логин равняеться введенному

# Генерируем случайное число и шифруем его

$hash = md5 ( generateCode ( 10 ));

# Если пользователя выбрал привязку к IP

# Переводим IP в строку

# Записываем в БД новый хеш авторизации и IP

# Переадресовываем браузер на страницу проверки нашего скрипта

header ( «Location: check.php» ); exit();

print «Вы ввели неправильный логин/пароль» ;

check.php

// Скрипт проверки

print «Хм, что-то не получилось» ;

print «Включите куки» ;

Для защиты формы логина от перебора, можно использовать captcha.ru target=»_blank»>капчу.

Хочу отметить, что здесь я рассматривал авторизацию основоную на cookies, не стоит в комментариях кричать, что сессии лучше/удобнее и т.д. Спасибо.

Источник

HTTP-аутентификация в PHP

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

Пример #1 Пример Basic HTTP-аутентификации

Пример #2 Пример Digest HTTP-аутентификации

Это пример реализации простого скрипта Digest HTTP-аутентификации. За подробностями обращайтесь к » RFC 2617.

die( ‘Текст, отправляемый в том случае, если пользователь нажал кнопку Cancel’ );
>

Замечание: Замечание касательно совместимости

Будьте особенно внимательны при указании HTTP-заголовков. Для того, чтобы гарантировать максимальную совместимость с наибольшим количеством различных клиентов, слово «Basic» должно быть написано с большой буквы «B», регион (realm) должен быть взят в двойные (не одинарные!) кавычки, и ровно один пробел должен предшествовать коду 401 в заголовке HTTP/1.0 401. Параметры аутентификации должны разделяться запятыми, как это было показано в примере Digest аутентификации выше.

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

Замечание: Замечание касательно конфигурации

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

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

И Netscape Navigator и Internet Explorer очищают кеш аутентификации текущего окна для заданного региона (realm) при получении от сервера статуса 401. Это может использоваться для реализации принудительного выхода пользователя и повторного отображения диалогового окна для ввода имени пользователя и пароля. Некоторые разработчики используют это для ограничения авторизации по времени или для предоставления кнопки «Выход».

Пример #3 Пример HTTP-аутентификации с принудительным вводом новой пары логин/пароль

function authenticate () <
header ( ‘WWW-Authenticate: Basic realm=»Test Authentication System»‘ );
header ( ‘HTTP/1.0 401 Unauthorized’ );
echo «Вы должны ввести корректный логин и пароль для получения доступа к ресурсу \n» ;
exit;
>

Для того, чтобы добиться корректной работы HTTP-аутентификации в IIS сервере с CGI версией PHP, вы должны отредактировать конфигурационную настройку IIS под названием » Directory Security «. Щёлкните на надписи » Edit » и установите опцию » Anonymous Access «, все остальные поля должны остаться неотмеченными.

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

User Contributed Notes 46 notes

Workaround for missing Authorization header under CGI/FastCGI Apache:

This is the simplest form I found to do a Basic authorization with retries.

// If arrives here, is a valid user.
echo «

Congratulation, you are into the system.

In case of CGI/FastCGI you would hot be able to access PHP_AUTH* info because CGI protocol does not declare such variables (that is why their names start from PHP) and server would not pass them to the interpreter. In CGI server should authenticate user itself and pass REMOTE_USER to CGI script after it.

So you need to «fetch» request headers and pass them to your script somehow.

In apache you can do it via environment variables if mod_env is installed.

SetEnvIfNoCase ^Authorization$ «(.+)» PHP_AUTH_DIGEST_RAW=$1

Do not forget to strip auth type («Digest» in my case) from your env variable because PHP_AUTH_DIGEST does not have it.

If mod_env is not installed you probably have mod_rewrite (everyone has it because of «human readable URLs»).

You can fetch header and pass it as GET parameter using rewrite rule:

RewriteRule ^.*$ site.php?PHP_AUTH_DIGEST_RAW=% [NC,L]


If you use ZF you probably use Zend_Auth_Adapter_Http to auth user.

$this->isLogin();
> else <
new Msg(true,»Benutzername und Passwort stimmen nicht überein!»);
>

A short example for an user Login

[NOTE BY danbrown AT php DOT net: The following note was added by «Anonymous» on 01-APR-2010 (though we presume it’s not an April Fool’s Day joke).]

this logout method does not work 100% anymore, because of another bulls**t from M$:
http://support.microsoft.com/kb/834489

I came up with another approach to work around the problem of browsers caching WWW authentication credentials and creating logout problems. While most browsers have some kind of way to wipe this information, I prefer having my website to take care of the task instead of relying on the user’s sanity.

Even with Lalit’s method of creating a random realm name, it was still possible to get back into the protected area using the back button in Firefox, so that didn’t work. Here’s my solution:

Since browsers attach the credentials to specific URLs, use virtual paths where a component of the path is actually a PHP script, and everything following it is part of the URI, such as:

By choosing a different number for the last component of the URL, browsers can be tricked into thinking that they are dealing with a completely different website, and thus prompting the user for credentials again.

Note that using a random, unrestricted number will still allow the user to hit the back button to get back into the page. You should keep track of this number in a server-side file or database and regenerate it upon each successful login, so that the last number(s) become invalid. Using an invalid number might result in a 403 response or, depending on how you feel that day, a 302 to a nasty website.

Care should be taken when linking from the page generated in this case, since relative links will be relative to the virtual and non-existant directory rather than the true script directory.

you have logged!

Be careful using http digest authentication (see above, example 34.2) if you have to use the ‘setlocale’ function *before* validating response with the ‘http_digest_parse’ function, because there’s a conflict with \w in the pattern of ‘preg_match_all’ function :

In fact, as \w is supposed to be any letter or digit or the underscore character, you must not forgot that this may vary depending on your locale configuration (eg. it accepts accented letters in french).

Due to this different pattern interpretation by the ‘preg_match_all’ function, the ‘http_digest_parse’ function will always return a false result if you have modified your locale (I mean if your locale accepts some extended characters, see http://fr.php.net/manual/en/reference.pcre.pattern.syntax.php for further information).

IMHO, I suggest you not to use setlocale before having your authentication completed.

PS : Here’s a non-compatible setlocale declaration.
setlocale ( LC_ALL, ‘fr_FR’, ‘fr’, ‘FR’, ‘french’, ‘fra’, ‘france’, ‘French’, ‘fr_FR.ISO8859-1’ ) ;

= ‘test_login’ ;
$pass = ‘test_pass’ ;

A simpler approach on the post of:
bernard dot paques at bigfoot dot com
24-Sep-2004 01:42

This is another «patch» to the PHP_AUTH_USER and PHP_AUTH_PW server variables problem running PHP as a CGI.

It’s a variation of the script by Bernard Paques.
Thanks to him for that snippet.

Back to the autherisation in CGI mode. this is the full working example:

# In the beginning the script checking the authorization place the code:

if ( count($userpass) == 2 ) <
#this part work not for all.
#print_r($userpass);die; #

Note that Microsoft has released a ‘security update’ which disables the use of username:password@host in http urls.

The methods described above which rely on this will no longer work in Microsoft browsers, sadly.

You can re-enable this functionality as described at

but your users will probably be unwilling to do this.

The regex in http_digest_parse from Example #2 does not work for me (PHP 5.2.6), because back references are not allowed in a character class. This worked for me:

Some servers won’t support the HTTP1.0 specification and will give an error 500 (for instance). This happened with a server where I uploaded an authentication script.

If it happens, you can try the HTTP1.1 header syntax :

( «WWW-Authenticate: Basic realm=\»My Realm\»» );
header ( ‘status: 401 Unauthorized’ );
?>

To force a logout with Basic Auth, you can change the Realm out from under them to a different Realm.

This forces a new set of credentials for a new «Realm» on your server.

You just need to track the Realm name with the user/pass and change it around to something new/random as they log in and out.

I believe that this is the only 100% guaranteed way to get a logout in HTTP Basic Auth, and if it were part of the docs a whole lot of BAD user-contributed comments here could be deleted.

Then you need small piece of php code to parse this line and then everything will work like with mod_php:

Simpler WorkAround for missing Authorization header under CGI/FastCGI available in Apache HTTP Server 2.4.13 and later

Please don’t enable Authorization header with Basic Authentication, is very insecure.

I spent the better part of a day getting this to work right. I had a very hard time thinking through what the browser does when it encounters an authentication request: seems to me that it tries to get the password, then reloads the page. so the HTML doesn’t get run. At least, this was the case with IE, I haven’t tested it with anything else.

// «standard» authentication code here, from the ZEND tutorial above.

Источник

Надёжная авторизация для веб-сервиса за один вечер

Как сделать авторизацию php. Смотреть фото Как сделать авторизацию php. Смотреть картинку Как сделать авторизацию php. Картинка про Как сделать авторизацию php. Фото Как сделать авторизацию php

Предыстория

Осень 2015-ого. Примерно полтора года назад, когда мне случилось стать участником разработки проекта, где пользователей существенно больше пары десятков человек, я наконец-то впервые в своей жизни задумался о надёжности авторизации.

По сути, авторизация — это то, с чего начинается процесс взаимодействия зарегистрированного пользователя с системой (для незарегистрированного пользователя всё начинается с регистрации, и эти два процесса, как вы уже догадались, очень сильно взаимосвязаны). Я внезапно осознал, что во всех проектах, что я делал до этого, с безопасностью всё очень плохо.

Достаточно рассмотреть простейший пример: кто-то подобрал пароль, или человек каким-то образом скомпрометировал его. Конечно, у нас в базе данных хранятся хэши с использованием соли, мы даже настолько продвинуты, что используем только HTTPS… Но это никак не спасёт нас от человеческого фактора.

Что делает в случае взлома грамотная система? Блокирует аккаунт (совсем хорошо — если автоматически, по ряду необычных признаков в поведении пользователя), в дальнейшем предлагая средства для восстановления: привязанный телефонный номер, контрольный вопрос, и т.д. Привязка сотового — отличная мера с крайне высокой надёжностью. К сожалению, у нашего проекта не было на это средств, поэтому приходилось обходиться без этого. Благо, блокировку можно реализовать, посадив человека на специальный почтовый ящик, или даже принимать экстренные звонки на сотовый.

И тут традиционный метод, который применяют многие начинающие разработчики (найденный в книгах, либо в статьях в интернете) даёт осечку. Дело в том, что этот метод предполагает хранение авторизации в сессиях. Это статья не относится к конкретному языку, и справедлива для любой платформы, но я буду иллюстрировать всё на примере PHP.

Стандартная реализация

Итак, сессия в PHP по умолчанию хранится в файле. Её id сохраняется в cookie (если куки отключены — задействуется механизм передачи через адресную строку, если это не отключено в конфигурации). Время жизни такой сессионной куки по умолчанию — до момента закрытия браузера (и обычно его никто не меняет). Поэтому более продвинутые программисты реализуют галочку «запомнить меня», либо реализуют её функционал по умолчанию, без возможности отключить. Что они делают? Просто сохраняют в собственной куке айди пользователя. Но поскольку просто айди хранить как-то уж слишком стрёмно (любой может поставить любое число и получить доступ к произвольному аккаунту), то часто вместе с айди за компанию сохраняют и пароль. В открытом виде.

Если кто-то не понимает, чем это плохо — представьте себе, что у нас пользователь пользуется очень старым, либо очень плохо реализованным браузером. Мы ведь не можем гарантировать, что браузер надёжно шифрует куки? Да и вирусы всякие могут быть у пользователя на компьютере, и в случае особо серьёзной малвари может не спасти и шифровка — зловред может попытаться считать значения прямо из памяти, когда они расшифрованы. Человек, случайно оказавшийся у вашего компьютера в ваше отсутствие — сможет просто скопировать себе все интересующие куки, и в новых браузерах этот процесс стал ещё проще. Дыры в безопасности браузера могут потенциально привести к тому, что ваша кука станет доступна стороннему сайту (да, сейчас это крайней маловероятно, но чем чёрт не шутит).

Ну и самое главное — если у нас SSL используется только при авторизации (а на остальных страницах его решили отключить ради выигрыша в скорости, либо чтобы лучше работало промежуточное кэширование)… То наш пароль всё время передаётся открытым текстом. При каждом запросе. А пароль меняют редко, то есть это некий токен, который имеет долгий срок жизни. А теперь представим себе, что наш трафик кто-то перехватил…

Как быть?

Очевидно, с этим надо что-то делать. Да, можно сразу бежать покупать сертификат и подключать SSL. Но можно сделать кое-что ещё до этого, и существенно снизить тем самым необходимость в нём. В конце концов, в том же ВКонтакте SSL стал принудительным всего полгода назад, а до этого как-то ведь жили.

Первое, что приходит на ум — хранить вместо пароля хэш от него. Это не особо поможет, если кто-то перехватит трафик, и не поможет при краже кук. Но вот в ситуации «кто-то открыл настройки и прошёлся по кукам, пытаясь запомнить значения» — сильно усложнит жизнь злоумышленнику.

Можно пойти дальше, и при создании хэша использовать юзер-агент. Теперь взломщику придётся использовать браузер той же версии, что и у нас (а в случае с IE ещё и с тем же набором плагинов тех же самых версий, что выходит уж очень маловероятно, особенно если взломщик не догадался подсмотреть юзер-агент).

Но если пароль всё же утёк — мы по-прежнему бессильны. Даже сменив пароль через форму восстановления либо изнутри системы — взломщик останется залогинен, пока не закроет браузер. Ведь в основе авторизации у нас всё ещё лежит сессия.

Можно, конечно, на PHP получить id сессии при логине, записать его куда-то в базу, а при сбросе пароля стартовать по очереди все сессии с айдишниками из базы и делать session_destroy()… Возможный вариант, но не обязательно следовать ему.

Итак, мы сформулировали основной список требований к нашей системе:

1. Быть простой в реализации
2. Желательно не зависеть от используемой серверной платформы
3. Позволить «выкинуть» взломщика из системы, завершив все открытые сессии, либо некоторые из них (на основе браузера/операционной системы/времени логина)
4. Видеть список всех своих открытых сессий в системе, просматривать его
5. Максимально затруднить атаку в случае отсутствия SSL (например, открытый Wi-Fi)
6. Не создавать неудобств легитимным пользователям

Приступаем к работе

Перво-наперво создадим в нашей базе данных (будем считать, что мы используем реляционную СУБД) таблицу для хранения сессий. Наших сессий (не путать с PHP-шными!). Таблица будет иметь следующие поля: id, user_id, ip, user_agent, time. В качестве времени будем хранить время создания сессии. Можно хранить и время последнего доступа, но вскоре мы увидим, что это избыточно, и можно к этому прибегнуть лишь в рамках денормализации, чтобы ускорить выборку данных. Создаваться сессия будет в момент авторизации, а также при регистрации (мы хотим, чтобы после регистрации пользователю не приходилось заполнять форму логина, и тут же авторизуем его). Также создадим вторую таблицу — log, с таким же набором полей. Туда будем добавлять запись при каждой авторизации (через форму входа или по кукам).

Теперь мы можем хранить на клиенте id сессии, и сверять его с базой. При этом сервер может проверить, что у нас совпадает юзер-агент и IP. Если хотя бы что-то одно не сходится — считаем, что пользователь не авторизован, и направляем его на страницу логина.

Но этого как-то мало — IP клиента взломщик может знать (и если он перехватывает трафик, то уж точно его знает), то же самое касается юзер-агента (который вообще получить не составляет никаких проблем). Нам хотелось бы хранить в cookie не пароль, а некую производную от пароля, привязанную к сессии… то есть к юзер-агенту и IP. Соответственно, если что-то стало отличаться — доступ прекратится. Поэтому, как вы уже догадались, разумно использовать хэш от этих переменных, и сохранять в куке его. При этом просто посмотрев на хэш, взломщик не только не сможет узнать пароль, но даже точно определить, какие именно компоненты были использованы при его создании (и с какими модификациями).

Создадим в таблице сессий ещё одну колонку — hash. Будем использовать её для хранения хэшей наших сессий.

Вопрос удобства

Но тут мы сталкиваемся с другой проблемой. Клиент может переключаться между разными IP (как минимум между домашним Wi-Fi и 3G в дороге, а ещё рабочим защищённым Wi-Fi, как пример). И было бы очень негуманно заставлять его вводить заново логин и пароль каждый раз при смене IP адреса. Как быть, хранить в куке целый список хэшей? При запросе на сервер отсылать его? Можно, но это несколько усложняет реализацию. Кроме того, это раздувает размер куки (при том, что нам основную часть времени может требоваться всего один хэш, мы будем хранить все когда-либо использовавшиеся), а при очистке кук мы (как пользователь) потеряем их все, и опять придётся с каждого IP адреса вводить пароль вручную.

Что, если привязать хэш только к юзер-агенту? Можно, но это существенно снизит безопасность. Нам нужен компромиссный подход.

Делегирование полномочий

Что нам это даёт? Мы можем запихать в этот скрипт сколь угодно хитрую логику, вплоть до анализа последних действий пользователя и добавления его в бан-лист по IP, либо блокировки его аккаунта на тот или иной срок. Но сперва реализуем очень базовую функциональность: будем сверять значение хэша, пришедшего из куки, с тем, что должно было бы получиться, если хэш не был искажён. Сервер знает IP, знает юзер-агент, знает пароль текущего пользователя… Кажется, всё готово!

Разобьём начальную стадию нашего внешнего скрипта (это тоже можно вынести в отдельный файл, но в данный момент это не важно) на два этапа. На первом мы смотрим, установлена ли обычная сессия PHP. Если нет — сразу перебрасываем пользователя на форму авторизации. Но форма при загрузке проверяет наличие кук, и если кука с хэшем нашей сессии существует, и хэш совпадает с ожидаемым (хэш-функция от пароля* и юзер-агента), то ставим переменные сессии (в первую очередь user_id), делаем запись в таблицу log, и обратно перебрасываем пользователя туда, откуда он пришёл (адрес раздела для возврата разумно передавать прямо в ссылке). Итак, если нет PHP-сессии, но есть куки, и куки соответствуют браузеру — пользователь залогинен.

Теперь рассмотрим вариант, когда сессия есть (после того, как пользователь залогинен по паролю из формы или по кукам, он тоже попадёт на этот шаг). И вот тут вступает в игру наш скрипт security.php. Вот его код:

Если нам нужна система банов — можно раскомментировать последние две строки. И не забываем, что чтобы не произошло бесконечного зацикливания, в скрипт авторизации проверку на бан тоже надо включить!

Скрипт прост как дважды два. Мы проверяем наличие сессии с нашими текущими параметрами и нашим текущим хэшем. Если что-то не совпало — нас попросят пройти повторную авторизацию через редирект. И вот тут происходит самое вкусное. Если у нас поменялся только IP — мы пройдём проверку, для нас создастся новая сессия, если её нет для этого IP (этот момент надо включить в код авторизации), и создастся новая кука.

Если у нас поменялся браузер (угнанная кука) — придётся вводить пароль, которого у взломщика нет, иначе зачем ему воровать куки. При этом IP скорее всего будет другим, но может быть и тем же (взломщик находится с нами в одной локальной сети). В случае, когда взломщик в одной сети с жертвой, и имеет тот же юзер-агент — к сожалению, мы никак не сможем отличить его от настоящего пользователя. Привет, беспарольный Wi-Fi.

Если поменялся только хэш (поменяли куку вручную? побились файлы на жёстком диске?) — придётся авторизоваться заново, ничего не поделать.

Наконец, можно очень эффектно моментально отключить доступ к аккаунту. Для этого даже не обязательно удалять строку с сессией. Достаточно удалить/поменять/добавить один символ в хэше (последнее поле). Пример из жизни — если легитимный пользователь по счастливому стечению обстоятельств первым успеет воспользоваться кнопкой смены пароля в кабинете — взломщика выкинет, причём при первой же перезагрузке страницы. У настоящего пользователя уже будет новая кука (в хэше будет использован новый пароль), старые сессии будут полностью удалены из таблицы, а новая сессия создана с новым хэшем. А взломщик останется со старым невалидным хэшем, который не пройдёт проверку в security.php. При первом же непрохождении будет сброшена сессия PHP, и выполнен редирект на авторизацию. Которую, опять же, нельзя пройти, имея неактуальный хэш от старого пароля.

Всё хорошо?

Почти. Данная система по-прежнему не защищает от кражи хэша из кук при открытом канале и отсутствии SSL. Кроме того, сложно обеспечить своевременную блокировку, если доступ к аккаунту утерян (особенно если взломщик уже сменил пароль). В этом случае очень помог бы сброс пароля через смс-команду с привязанного телефона — но для этого уже нужна возможность принимать и отправлять смс. А если она есть — то можно сразу внедрить двухфакторную авторизацию, которая снимет массу проблем. Но не проблему с необходимостью в SSL — поскольку всё равно результатом любой авторизации является получение некого токена доступа, работающего ограниченное время. И этого времени может быть достаточно, чтобы сделать от нашего имени что-то, что нам не понравится.

Данный код можно, наверное, сделать ещё лучше и надёжнее. Если у вас есть по этому поводу соображения — пишите их в комментарии. Пока что система зарекомендовала себя с очень хорошей стороны, и была использована уже минимум в трёх проектах, два из которых имеют массовый доступ.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *