C, PHP, VB, .NET

Дневникът на Филип Петров


* Създаване на стриктен режим на сесия

Публикувано на 14 ноември 2008 в раздел ОСУП.

В тази статия ще пазгледаме модел за създаване на стриктен режим на достъп до сесия на приложение, работещо върху сървър работещ на разрешителен режим. За примера ще използваме програмния език PHP.

PHP предава идентификатора на сесията чрез специална променлива PHPSESSID. При него е предоставена възможност за предаване на тази променлива през URL, например: http://worldbank.dom/login.php?PHPSESSID=1234

По този начин потребител може да бъде подведен да използва фиксирана сесия и друг да я използва в последствие от негово име.

Нека разгледаме следния примерен код. В него просто създаваме потребителска сесия, в която вкарваме като променлива брояч. Кода на fixation.php ще бъде следния:

<?php
	session_start();
?>
<html>
<head>
	<title>WorldBank.dom session page</title>
</head>
<body>
<?php
	echo 'session id is: ';
	echo session_id();
	echo '<br>';
	if (!isset($_SESSION['counter'])){
		$_SESSION['counter'] = 0;
	}
	else{
		$_SESSION['counter']++;
	}
	echo 'counter: ';
	echo $_SESSION['counter'];
?>
</body></html>

Скриптът първо ще покаже на екрана вашето session_id, а после ще ви покаже стойността на брояча.

Нека вие отначало се представите за атакуващият. Отворете страницата и направете няколко пъти refresh. Ще видите как браузърът „запомня“ променливата counter в сесията и я увеличава с единица всеки път. Резултатът след 10 пъти refresh ще бъде подобен на следното:

	session id is: o5bhaffh530s1b5cbvs0759g03
	counter: 10

Сега отворете същата страница в нов браузър. Ще видите, че в различната инстанция на браузъра се е заредило друго session id и броячът за тази нова сесия започва отначало. С тази инстанция вие сте обикновен потребител, чиято сесия не е фиксирана.

Накрая отворете трети браузър, но този път в края на URL добавете session id от първата инстанция. От примера това е:

	?PHPSESSID=o5bhaffh530s1b5cbvs0759g03

Вие току що станахте „жертва“ на собствената си атака за фиксиране на сесия. Ще видите, че както session id е това което сте подали в URL и променливата (броячът) ще извади стойността си от преди това създадена сесия на атакуващия.

За да се справим с тази уязвимост и да направим достъпа стриктен ще е нужно да използваме вградена функция за сървъра, която „насилствено“ генерира ново session id. За PHP това е функцията session_regenerate_id(bool). Променете кода на fixation.php като добавите тази функция:

<?php
	session_start();
	session_regenerate_id(true);
?>
<html>
<head>
	<title>WorldBank.dom session page</title>
</head>
<body>
<?php
	echo 'session id is: ';
	echo session_id();
	echo '<br>';
	if (!isset($_SESSION['counter'])){
		$_SESSION['counter'] = 0;
	}
	else{
		$_SESSION['counter']++;
	}
	echo 'counter: ';
	echo $_SESSION['counter'];
?>
</body></html>

Функцията копира параметрите на текущата сесия и ги записва в нова. Параметърът „true“ указва, че старата сесия ще бъде изтрита.

Опитайте променения пример – отворете „атакуващ браузър“ и направете няколко пъти refresh, така че да увеличите брояча. След това отворете втори „потребителски браузър“ и отворете същата страница, но с последното session id от атакуващия браузър като параметър в URL. Ще забележите, че се е генерирало ново session id (както очаквахме), но броячът се е запазил такъв, какъвто беше при „атакуващия браузър“. Това е така, защото session_regenerate_id() копира старата сесия в нова. Сега обаче се върнете при „атакуващия браузър“ и направете refresh. Броячът му ще се нулира, защото през клиентския браузър сме унищожили последната му сесия и той е принуден да създаде нова. Така вече атакуващият браузър няма нищо общо със сесията на потребителя и може да се приеме, че сесията на потребителя не е узязвима. Успяхме да създадем приложение със стриктно управление на сесии, работещо под сървър на разрешителен режим.

Препоръчително е да използвайте session_regenerate_id(true); на всяка страница където се променят параметри от сесията. Тъй като функцията е доста бърза и не отнема ресурси, то може да си позволите да я сложите и на всяка страница, където има управление на сесии. Практически е задължително да я има поне на:

  • Влизане в системата;
  • Излизане от системата;
  • Смяна на права и промяна на важни сесийни променливи;
  • Прехвърляне на много важна информация от потребителя към сървъра и обратно.

Използването на session_regenerate_id с параметър false или без параметър (по подразбиране е false) НЕ изтрива старата сесия. Това е достатъчна защита ако потребителят НЕ е влезнал в системата все още. Но от там насетне полезността на параметър „false“ е практически нулева – на сървъра ще се пазят множество неактивни сесии – практически по една за всяко действие на потребител, а проблемът с фиксиране на сесия не е решен напълно (ако стара сесия на потребителя, вече е вътре в системата, е още активна и фиксирана, то хакера няма проблем да влезе през нея). Проблемът със „session prediction“ също нараства главоломно. Така, че session_regenerate_id(false) се явява вредна практика – само и единствено увеличава вероятността от „налучкване“ (prediction) на сесии. Винаги използвайте session_regenerate_id с параметър true!

Забележка: Имайте предвид, че session_regenerate_id(true) ще създаде проблеми с използването на Back / Forward бутоните на браузъра ако предавате session id с GET вместо с cookie *. Това всъщност е рядък и специфичен случай – например когато искаме да напишем приложение, което ще работи дори при забранени cookies от страна на клиента. Някой frameworks използват този метод за предаване на session id така, че го имайте предвид. В тези случаи функцията трябва да се използва пестеливо – само на задължителните места. В противен случай ще се навреди на функционалността на браузъра на клиента.

* Благодаря на Христо Гаров за уточнението. Ако не използвате GET, то няма проблем да правите session_regenerate_id(true) на всяка страница и това няма да наруши функционалности на браузъра.

 



7 коментара


  1. Христо Гаров каза:

    Привет!

    Какви по-точно проблеми биха създали Back/Forward навигацията в browser-a при session_regenerate_id(true) след всяко стартиране на сесията? Лично за мен единствен проблем би бил неволен logout. Но след кратък тест, който направих не установих нередност т.е. веднъж логнал се потребител без значение back/forward си остава валиден. Много е вероятно да бъркам нещо, затова бихте ли разяснили?

  2. Категорично влизаш във вече невалидна сесия и правилната реакция на системата е logout.

  3. „Категорично влизаш във вече невалидна сесия и правилната реакция на системата е logout.“
    Това не се ли случва само при сесийни id-та предавани по URL? С кукита поне при мен работи.

  4. От http://php.net/manual/en/function.session-regenerate-id.php:

    „delete_old_session – Whether to delete the old associated session file or not.“

    Когато стария сесиен файл бъде изтрит и ти натиснеш „back“, това означава, че се връщаш със старото session_id от кеша на браузъра, а то вече е невалидно (изтрито). PHP позволява на потребителя сам да си задава session_id, затова приложението ще работи все едно е създадена нова сесия. Правилната реакция на добре проектирана система в такива ситуации (поява на нова сесия зад login страницата) е да унищожи сесията (т.е. logout) и евентуално да пренасочи към login страницата.

    Впрочем не само back бутона, ами и отварянето на връзки от същия сайт в нови tabs ще бъде засегнато.

    П.С. Обърни специално внимание на параметъра със стойност TRUE. Ако го няма или е false, то старата сесия не се трие (но и полза от извършване на това действие зад login страницата почти няма).

  5. Ползвам session_regenerate_id(true), с false се обезсмисля начинанието :)

    При back би се предало старото (кеширано) ID само и единствно, ако то се пази в $_GET (т.е. url). Ако се ползва куки, както е логично според мен, няма такъв проблем.

    http://stackoverflow.com/questions/2490707/what-are-the-weaknesses-of-this-user-authentication-method/2490749#2490749

  6. Да нямаш предвид клиентско cookie – т.е. масива $_COOKIE? Това няма общо със сесиите – две различни неща са. Дай прост пример, за да видим какво имаш в предвид.

  7. ОК, разбрах какво имаш предвид – да, прав си, проблемът го има само с променливи прехвърлени с GET. Ще вмъкна уточнение в статията. С cookie работи, понеже дори върнат в страницата от кеша, браузъра си пази новото cookie и при следващо действие го използва именно него.

    Когато писах оригинално статията имах взимане-даване с приложение, което трябваше да работи с браузъри с изключени cookies, поради което кодирахме session id в URL. Явно с каквито проблеми съм се сблъсквал – такива съм писал. Мерси много за корекцията – беше много повече от съществена!

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

Адресът на електронната поща няма да се публикува


*