Про безопасность: Привязка сессии к IP и(или) UserAgent

Безопасный вход

Недавно, после того как у меня обострилась паранойя после добавление на свой сайт платёжной системы я всерьез заинтересовался проблемами безопасности. Одна из распространенных проблем в безопасности — это кража кук, с помощью всяческих XSS уязвимостей, которые (XSS уязвимости) присуствовали даже в старых релизах LS. Проблема есть и она очень даже серьезная, и чтобы такого не произошло я сделал у себя на сайте возможность привязки сессии к IP адресу и тем у кого IP динамический или серый привязку к юзерагенту браузера. Механизм очень интересный, если злоумышленник «похищает» куку, а у пользователя при авторизации включена привязка к IP или юзерагенту, то при попытке воспользоваться кукой она просто теряет свою актуальность, т.е. в случае несовпадения юзерагента или IP выполняется выход из системы и сессия автоматически закрывается, а чтобы начать новую нужно вводить пароль, без этого ни как. К сожалению не знаю как оформить это плагином, поэтому описываю пошагово процесс внедрения, надеюсь что понятно.

1. Модернизация кода:
1.1. Ищем файл
classes/actions/ActionLogin.class.php

В функцию EventAjaxLogin(), после строки (85 строка):
$this->User_Authorization($oUser,$bRemember);


Добавляем следующие строки
// * Если нужно то запоминаем IP в сессию
if(getRequest('remember_ip', false)) {
	$this->Session_Set('user_ip', $_SERVER['REMOTE_ADDR']);
	$this->Session_SetCookie('check_ip', 1, Config::Get('sys.cookie.time'));
}

// * Если нужно то запоминаем браузер в сессию
if(getRequest('remember_browser', false)) {
	$this->Session_Set('user_browser', md5($_SERVER['HTTP_USER_AGENT']));
	$this->Session_SetCookie('check_useragent', 1, Config::Get('sys.cookie.time'));
}


Сохраняем.

1.2. Ищем файл
classes/modules/user/User.class

В функцию Init(), после строки (71 строка):
if ($this->oSession=$oUser->getSession()) {


Добавляем следующие строки
#Проверка IP адреса		
if($this->Session_Get('user_ip')){
	#Проверяем совпадает ли IP в сессии и IP клиента
	if($this->Session_Get('user_ip') != $_SERVER['REMOTE_ADDR']) {
		$this->Logout();
		return;						
	}
}

#Проверка браузера	
if($this->Session_Get('user_browser')){
	#Проверяем совпадает ли хеш агента браузер в сессии и хеш агента браузера клиента
	if($this->Session_Get('user_browser') != md5($_SERVER['HTTP_USER_AGENT'])) {
		$this->Logout();
		return;						
	}
}


В функцию Authorization(), после строк (после 526 строки):
if ($bRemember) {
	setcookie('key',$sKey,time()+Config::Get('sys.cookie.time'),Config::Get('sys.cookie.path'),Config::Get('sys.cookie.host'),false,true);
}


Добавляем следующие строки
/**
 * Если нужно то запоминаем IP в сессию и продляем жизнт куке
 */		
if($this->Session_GetCookie('check_ip')) {
	$this->Session_Set('user_ip', $_SERVER['REMOTE_ADDR']);
	$this->Session_SetCookie('check_ip', 1, Config::Get('sys.cookie.time'));
}

/**
 * Если нужно то запоминаем агент браузера в сессию и продляем жизнт куке
 */	
if($this->Session_GetCookie('check_useragent')) {
	$this->Session_Set('user_browser', md5($_SERVER['HTTP_USER_AGENT']));
	$this->Session_SetCookie('check_useragent', 1, Config::Get('sys.cookie.time'));
}


В функцию Logout(), после строки (571 строка):
$this->Session_Drop('user_id');


Добавляем следующие строки
// * Удаляем IP из сессии
$this->Session_Drop('user_ip');	
// * Удаляем куки флага проверки IP адреса
$this->Session_DelCookie('check_ip');		
// * Удаляем хэш агента браузера из сессии
$this->Session_Drop('user_browser');
// * Удаляем куки флага проверки хэша агента браузера
$this->Session_DelCookie('check_useragent');


Сохраняем.

2. Модернизация шаблона (на примере дефолтного шаблона synio):
2.1. Ищем файл
templates/skin/synio/window_login.tpl

В функцию EventAjaxLogin(), после строки (85 строка):
$this->User_Authorization($oUser,$bRemember);


После 40 строки:
<label class="remember-label"><input type="checkbox" name="remember" class="input-checkbox" checked /> {$aLang.user_login_remember}</label>


Добавляем следующие строки
<label class="remember-label"><input type="checkbox" name="remember_ip" class="input-checkbox" /> {$aLang.user_login_remember_ip}</label>
<label class="remember-label"><input type="checkbox" name="remember_browser" class="input-checkbox" /> {$aLang.user_login_remember_browser}</label>


Сохраняем.

2.2. Ищем файл
templates/skin/synio/actions/ActionLogin/index.tpl

После 18 строки:
<p><label><input type="checkbox" name="remember" checked class="input-checkbox" /> {$aLang.user_login_remember}</label></p>


Добавляем следующие строки
<p><label><input type="checkbox" name="remember_ip" class="input-checkbox" /> {$aLang.user_login_remember_ip}</label></p>
<p><label><input type="checkbox" name="remember_browser" class="input-checkbox" /> {$aLang.user_login_remember_browser}</label></p>


Сохраняем.

3. Добавляем языки (на примере Русского языка)
3.1. Ищем файл
templates/language/russian.php

После 23 строки
return array(


Добавляем следующие строки
'user_login_remember_ip' => 'Привязать сессию к IP',
'user_login_remember_browser' => 'Привязать сессию к браузеру',


Сохраняем и пользуемся.

22 комментария

avatar
Спасибо за инструкцию!

которые (XSS уязвимости) присуствовали даже в старых релизах LS.

Вот с этого места бы по подробней, т.е. присутствуют в последнем релизе (1.0.3), если правильно понял. Желательно с описанием к Администрации, и последующей публикацией найденных XSS.
avatar
Действующих XSS уязвимостей, известных мне, в текущем релизе пока не выявлено, но это не значит что их нет. Любой плагин или даже шаблон, как это было с bootstrap может потенциально содержать угрозу.
avatar
Пропустил про bootstrap, где можно почитать подскажите пожалуйста.
avatar
Поищите по сайту, кажется здесь выкладывали, не могу найти.
комментарий был удален
avatar
Упаковались в плагин
gowebpro.github.io/lsp-securesession/

Тестируем, баг-репортим
avatar
Может лучше перенести эти настройки в настройки профиля?
avatar
Не делайте так по умолчанию хотя бы. IP может меняться в рамках даже одной сессии, если пользователь включил сжатие трафика в Chrome (пока это есть только в мобильном) или Opera, если используются прокси, или анонимайзеры. Сталкивался с провайдерами которые почти каждый раз выдавали динамический IP. Постоянно скидывалась сессия и приходилось перелогиниваться. Другая банальная ситуация — вы пошли в кафе и решили проверить личку подключившись к WiFi.
Теперь внимание, UserAgent — включает номер версии, который будет меняться после каждого обновления браузера, что так же будет приводить к сбросу.
Испортите user experience. Лучше реально выявлять и закрывать XSS.
avatar
IP может меняться
Не ставьте галочку
UserAgent — включает номер версии, который будет меняться после каждого обновления браузера, что так же будет приводить к сбросу.
Исправим
avatar
UserAgent — включает номер версии, который будет меняться после каждого обновления браузера, что так же будет приводить к сбросу.
Исправим
Не надо
avatar
Ну и кагбэ сессия на сервере живет очень не долго. К примеру на дебиане это по умолчанию 1440 сек. Через этот интервал времени на сервере сессии уже нет и проверять нечего. Нужно работать с таблицей сессий и закрывать персистентную сессию, или сессии (то что с галочкой «запомнить меня» открывается).
avatar
Кагбэ функция Logout.
галочкой «запомнить меня» создается кука, которая дропается функцией Logout модуля User.

Какбэ функционал добавляет опции, а не жесткие правила привязки к IP и UserAgent.
avatar
Вы зашли на сайт. Открылась сессия. Ваша сессионная кука по умолчанию живет 1440 сек. При отсутствии активности сессия вычищается на сервере скриптом или сборщиком мусора. Далее, если стояла галочка «запомнить меня», то на сервере в табличку сессий или куда-то еще должна была записаться строчка с персистентной кукой (здесь кажется это называется ключ сессии). Если вы повторно заходите на сайт, то первым делом сессия открывается заново, с перегенерацией session_id или без (конкретно в лайвстрите насколько я помню без). Скрипт обнаруживает, что сессия уже дропнулась (нет user_id). Если при этом у вас в куках установлена вторая кука с ключом, то скрипт смотрит в табличку и если сессия такая существует он заново устанавливает вам user_id в сессию, после чего вы считаетесь полностью авторизованным.

Писать что-либо в сессию и проверять бессмысленно, сессия снова дропнется. Этот прием защищает только если злоумышленник украдет вашу сессионную куку и нарисуется в итервале времени жизни сессии на сервере. Если же он украл персистентную куку (ключ сессии) то он спокойно зайдет, переоткрыв новую сессию. На другой чаше весов испорченный пользовательский опыт.

Надо делать как я сказал выше — писать IP в таблицу сессий, закрывать старые и открывать новые, принудительно закрывать при смене IP, проверять помимо прочего не закрылась ли сессия.
avatar
Неправильно сказал в начале — не кука живет 1440 сек, а сессия. Кука будет жить сколько вы сами настроили.
avatar
Тут Вы правы. IP сессии храниться в БД, вызывается функциями Session_getIpCreate() и Session_getIpLast(), хеш браузера по умолчанию не сохранятся.
avatar
Нужно получать объект сессии (сущность) по ключу, который передает пользователь в куках. Из сущности брать IP $oSession->getIpLast().
avatar

Элементарный способ проверки смены IP.
Осталось разобраться с принципом хранения чекбоксов.
В куках или сессии хранить метку о том, что это нужно чекать, бессмысленно. Придется добавить поле в табличку сессий, другого варианта не вижу
avatar
Обновил код, исправил досадную ошибку с обновлением сессий.
  • ff00
  • 0
комментарий был удален
avatar
Это все извращенства, например в laravel по-умолчанию шифруются значения cookie, их не подменить, а CSRF-защита по-умолчанию включена для всех запросов, чтобы где-то не проверялось, нужно специально отключать, а в livestreet только там где сам автор плагина не забыл проверить. Как и писал автор топка, любой плагин увеличивает шанс взлома.
  • gran
  • -1
avatar
Чтобы не разводить панику, благо все аякс запросы автоматически проверяются на XSS хотя бы за это можно быть спокойными :)
avatar
Думаю лучше будет перенести все эти настройки в личные настройки юзера. Для тех кто так скажем больше думает о своей безопасности
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.