* Повишаване на сигурността при cookies

Публикувано на 20 ноември 2008 в раздел ПТСК.

Нека вземем кода от предишната статия и се опитаме да го направим по-сигурен. На първата стъпка ще се възползваме от няколко допълнителни променливи, които можем да дадем като входни параметри при създаване на cookie, а именно:
- директория на сървъра, за която cookie е валидно
- домейн за който cookie е валидно
- задължително използване на SSL

Промяната в кода на съществуващата програма е в два реда:

<?php
	function security_start_session($ssl, $timeout, $maxtime, $ip){
		// ... security checks
		session_start();
		session_regenerate_id(true);
		return 1;
	}
	if (security_start_session(0, 6*60, 20*60, 1) != 1){
		echo "<br>session destroyed!";
		return;
	}
        function showheaders(){
                echo '<html><head><title>WorldBank.dom login page</title></head><body>';
        }
	function showloginform(){
		echo '<form action="cookie.php" method="POST">';
		echo 'user:';
		echo '<input type="text" name="user">';
		echo '<br>pass:';
		echo '<input type="password" name="pass">';
		echo '<br>Set cookie: ';
		echo '<input type="checkbox" name="remember" value="true">';
		echo '<br><input type="submit" name="submit"';
	}

	function checklogin($user, $pass){
		if ($user == "test" && $pass == "123456") return 1;
		else return 0;
	}

	if(isset($_COOKIE['user']) && isset($_COOKIE['pass'])){
		if (checklogin($_COOKIE['user'], $_COOKIE['pass']) == 1){
			$_SESSION['authenticated'] = 1;
		}
		else{
			setcookie("user","", mktime(12,0,0,1, 1, 1970));
			setcookie("pass","", mktime(12,0,0,1, 1, 1970));
		}
	}

	if($_SESSION['authenticated'] == 1){
                showheaders();
		echo "Welcome back. I remember your session...";
	}
	else{
		if( isset($_POST['submit'])){
			if (checklogin($_POST['user'], $_POST['pass']) == 1){
				$_SESSION['authenticated'] = 1;

				if($_POST["remember"]=="true"){
					setcookie("user",$_POST['user'], time()+3600, "/secure/", "users.cphpvb.net", true);
					setcookie("pass",$_POST['pass'], time()+3600, "/secure/", "users.cphpvb.net", true);
				}
				echo "You are logged in successfully!";
			}
			else{
                                showheaders();
				echo "Invalid username and password";
			}
		}
		else{
                        showheaders();
			showloginform();
		}
	}

?>
</body></html>

В удебелените два реда сме добавили именно тези допълнителни параметри. Така създаденото cookie е валидно само за файлове намиращи се в директорията на адрес users.cphpvb.net/secure/. Освен това с последния параметър „true“ сме указали, че cookie няма да бъде валидно ако канала не е криптиран с SSL (тоест връзката трябва задължително да мине през https).

При версии на PHP по-големи от 5.2.0 можете да добавите още един последен параметър със стойност true, който ще отговаря на полето „httponly„. Когато той е true то cookie ще бъде фиксирано да бъде предавано само и единствено през http протокол. Това означава, че скриптове като JavaScript и VBScript няма да имат достъп до това cookie, което е значителен напредък в борбата срещу XSS атаки.

Ако отворите създаденото cookie в текстов редактор ще видите, че в него сме записали следната информация:

	user
	test
	users.cphpvb.net/secure/
	...
	*
	pass
	123456
	users.cphpvb.net/secure/
	...
	*

Веднага ще забележите един сериозен проблем – всеки, който открадне това cookie ще види паролата като обикновен текст. Би било хубаво да прикрием паролата, така че тя да не бъде явно видима. За целта може да използвате криптографски хеш алгоритъм, като например md5. Идеята е на сървъра да „складирате“ паролите на потребителите криптирани. Когато потребителя въведе парола, то вие ще я получите като обикновен текст. Тогава вие ще криптирате неговата парола и ще я сравните с вече въведената в базата данни. Ще разгледаме тази техника и ще обясним нуждата от нея в по-следваща статия.

Сега ще се фокусираме върху по-сериозен проблем – как да защитим приложението срещу кражба на cookie? Действително ако злонамерен човек придобие този файл, то той би могъл да се автентикира с него пред нашия сървър.

За да се справим с този проблем ще е нужно да запишем даден отличителен белег за потребителя, като например неговия IP адрес. Този адрес можете да запишете в базата от данни на сървъра, например в поле „LAST_IP_USED“. Ако засечете, че потребител се опитва да се автентикира чрез cookie на вашия сайт, но е сменил своето IP (проверка например чрез $_SERVER['REMOTE_ADDR']) то бихме могли да изтрием неговото cookie и да го накараме да се автентикира наново. Въпреки, че някои потребители биха сметнали това за неудобство (някои хора често пътуват и използват WiFi), все пак е добър напредък към по-сигурно приложение. Бихте могли да прецените доколко е важна сигурността на вашето приложение и до колко бихте могли да „отстъпите“ в полза на удобството за потребителя.

Накрая – в самото cookie можем да запишем характерна информация за потребителя, например в cookie-то да не записваме чисто паролата, а паролата заедно с IP адреса на потребителя криптирани заедно:

 $password = md5($password.$_SERVER['REMOTE_ADDR']);

При автентикация и използване на Cookie в последствие можем да направим проверка именно по обратния начин. Така cookie-то е неизползваемо от друг IP адрес. Неудобството за потребителите ще бъде, че винаги когато използват различно IP ще трябва да влизат в системата. Реализирайте такава функционалност в горния примерен скрипт.

С казаното дотук бихме си позволили да въведем следните степени на сигурност в едно приложение с автентикация:

1. Не използва cookies – най-голяма сигурност!
2. Използва защитени с криптиране, фискирани по IP и защитени с SSL cookies – висока степен на сигурност.
3. Използва фиксирани по IP и защитени с SSL cookies, но съдържащи некриптирани данни – все още висока степен на сигурност, но пробив в сигурността на машината на клиента е фатален.
4. Използва cookies, които не са защитени с SSL, но са фиксирани по IP и са с криптирани данни – относително малка сигурност, тъй като IP може да се направи IP spoofing.
5. Използва cookies, които не са защитени с SSL, не са фиксирани по IP, но са с криптирани данни – на практика без никаква сигурност!
6. Използва обикновени, незащитени cookies през http канал – абсолютно никаква сигурност!

В следващите статии ще покажем техниките за криптиране на имена и пароли.

Задача: Опитайте се да реализирате фиксиране на cookie с IP адрес. За целта напишете програма, в която явно сте записали своя IP адрес и преди проверката за валидност на cookie го сверявате с $_SERVER['REMOTE_ADDR']. Опитайте се да пренесете това cookie на друга машина и изпробвайте дали създадената защита работи.

Забележка: Забележката към статията за Фиксиране на сесия по IP адрес е валидна и за тази статия. Ако фиксирате cookie по IP адрес то на практика ще блокирате потребителите на големи NAT мрежи като AOL. За разлика от сесиите обаче тук имаме по-малка щета: ако фиксирате сесия по IP вие ще лишите тези потребители от цялата услуга, а ако фиксирате cookie то ще ги лишите само от възможността да влизат автоматично в системата. Преценката дали тази защита е оправдана я оставяме на вас.



11 коментара за “Повишаване на сигурността при cookies”

 
  1. bunchevi:

    Искам да попитам тези проверки за куки и сесии дали не трябв а да са преди html тага или просто трябва да се направят преди да се извежда информация в кода. Другото което е: например аз искам да направя бисквитките задължителни в сайта си, а не потребителя да избира. В такъв случай проверката на всяка страница пак трябва да е за куки или за сесия дали има или греша? Има ли значение също дали първо ще проверя за куки или за сесия? Според моята логика мисля, че първо трябва да проверя дали има сесия, ако няма тогава проверявам за куки- ако информацията в кукито съвпада с тази от БД то тогава потребителя има достъп, в противен случай разрушавам кукито и го препращам да се логне. Ако пък няма и куки следва препращане към логина. Правилно ли съм разбрала смисъла на всичко това?
    Благодаря предварително!

     
  2. Филип Петров:

    Със сигурност трябва записването на cookie да е преди отварянето на какъвто и да е html таг и изпращането на какъвто и да е друг код. Кодът по-горе е категорично неработещ и го знам отдавна… но отдавна не съм го поправил :)

    За другото – правилно си разбрала смисъла.

    П.С. Редактирах кода. Надявам се сега да е добре.

     
  3. bunchevi:

    Благодаря за бързия отговор и обяснението ти. Сега ще опитам да си направя и моя код и дано стане!

     
  4. bunchevi:

    А в случай, че използвам задължително бисквитки в сайта, мога ли при проверката дали имам сесия и ако имам да извлека потребителското име от COOKIE['user'] например или тази променлива мога да я извлека само в случая с isset(COOKIE['user'])?
    Другият ми въпрос е как да извлека от всеки потребител ключа му за SSL, за да мога да го използвам като проверка?

     
  5. Филип Петров:

    Функцията „isset“ проверява дали променливата е дефинирана.

    Колкото до втория въпрос – предполагам, че OpenSSL функциите ще помогнат.

     
  6. bunchevi:

    Съжалявам за грешно зададения въпрос. Иимах предвид относно първото ми питане следното: Не да използвам isset(COOKIE['user']) за да извлека username-a от бисквитката а да използвам само COOKIE['user']. Но въпроса ми беше относно следния код:
    if($_SESSION['log'] == „true“)
    {
    echo „welcome COOKIE['user']„;// мога ли така да извлека името или трябва да е със сесия задължително? В сайта използването на кукита е задължително.
    }
    elseif(isset($_COOKIE['user']))
    {
    правя си разни проверки и т.н.
    }
    else
    {
    трябва да не логне
    }

     
  7. Филип Петров:

    Не, така не може:

    1. Преди да се използва променлива трябва да си убеден, че е дефинирана (а тук не си и няма как да си);

    2. Никога (!!!) не се отпечатват променливи подавани от потребител, преди да бъдат филтрирани.

     
  8. bunchevi:

    Относно първия ти отговор: защо да не съм сигурна че е дефинирана, нали при логин задължително създавам куки с юзърнейма примерно. А и използването на сесии да изведа юзърнейма не е ли несигурно? ИСка ми се да е с бисквитка въпросното извеждане.
    По втория отговор: Тук не съм написала кода изцяло, а само идеята си, но честно казано бях забравила да филтрирам стойностите от бисквитките. Това необходимо ли е дори ако не извеждам стойност от бисквитката а само я използвам за проверка?

     
  9. Филип Петров:

    Cookie се пази на клиентската машина, а не са сървъра – това означава, че приложението ти няма никакъв контрол над това дали и как клиента го съхранява. Следователно няма как да гарантираш, че той не е направил нещо „нередно“ с него. Например ако има firewall той може да го изтрие. Или хакер може да го подмени или „прецака“. Проверка трябва да се прави винаги.

    По втория въпрос – ако нещо се отпечатва на екрана, то трябва да се отпечата филтрирано. Когато се използва в други ситуации – според ситуацията. Проверка на типовете данни, проверка за валидност – винаги са препоръчителни.

     
  10. bunchevi:

    А има ли разлика дали ще използвам if($_SESSION['authenticated'] == 1) или if(isset($_SESSION['username']) && isset($_SESSION['userid'])) { проверка дали има такива стойности и т.н.} ?

    Не е ли по- малко сигурно да се използва първия вариант тъй като стойността е константна?

     
  11. Филип Петров:

    bunchevi – тук съм дал просто най-прост пример, в който не се проверява кой е потребителя, а просто дали има такъв или не.

     

RSS за коментарите

Пусни коментар