Moja propozycja systemu logowania. Jakie wasze propozycje?

0

Ostatnio zainteresowałem się prostymi systemami logowania na stronie. Każdy który znalazłem w sieci miał swoje wady i zalety. Postanowiłem więc podjąć się napisania własnego i z waszą pomocą jego usprawnienia. Bazuje on oczywiście na tych najprostszych lecz starałem się aby był w miarę możliwości bezpieczny. Póki co jest dopiero skrypt logowania bez formularza lecz myślę, że jest już w takim stadium w którym można by zacząć dyskusję na jego temat. Jako prosty skrypt może już być wykorzystany lecz jakie są wasze uwagi co do niego?

Co już jest:
-możliwość zablokowania logowania na różne konta z tego samego IP (zmienna 1 albo 0)
-nie można zalogować się na to samo konto z dwóch różnych przeglądarek/komputerów
-można tymczasowo zablokować logowanie się na stronie
-blokowanie możliwości logowania jeśli istnieje już ważna sesja o danym IP albo SID
-proste ustawienie ważności sesji
-przy wylogowaniu aktualizuje SID

Poziomy użytkownika:

  • -1 niezalogowany
  • 0 konto nieaktywne
  • cała reszta do waszej dyspozycji

Co jest w planie:
-blokowanie logowania z danego IP
-niewidoczny element wykorzystujący AJAX do odświeżania sesji co zadaną ilość sekund oraz sprawdzenia poprawności sesji
-odczytanie listy zalogowanych z ważną sesją
-rejestracja z wysłaniem maila aktywacyjnego
-funkcja dla admina usuwająca wszystkie sesje z bazy danych (wylogowanie wszystkich)

System wykorzystuje bazę MySQL przy użyciu PDO

Mam jakiś problem z dodawaniem załączników więc plik baza.sql ze strukturą oraz przykładowymi użytkownikami znajduje się na samym dole.

index.php

<?php

include('lconfig.php');

echo('strona glowna');
echo('<hr>');
Log_msg();

echo('<hr>');

if ($_SESSION['log_lvl'] > -1) {
	echo('<h3>');
	echo('Login: ' . $_SESSION['log_login'] . '<br>');
	echo('Id: ' . $_SESSION['log_uid'] . '<br>');
	echo('Poziom: ' . $_SESSION['log_lvl'] . '<br>');
	echo('Sesja: ' . $_SESSION['log_sid'] . '<br>');
	echo('Ip: ' . $_SESSION['log_ip'] . '<br>');
	echo('</h3>');
	echo('<a href="flogin.php?f=logout">Wyloguj</a>');
} else {
	echo('<a href="flogin.php?l=admin&p=admin">Admin</a><br>');
	echo('<a href="flogin.php?l=user&p=user">User</a><br>');
	echo('<a href="flogin.php?l=ktos&p=haslo">Nieaktywny</a><br>');
	echo('<a href="flogin.php?l=aaa&p=bbb">ZleDane</a><br>');
}

?>

lconfig.php

<?php
session_start();

$page_index = 'index.php'; //strona glowna na ktora bedzie przkierowanie jesli wszystko ok
$page_login = 'index.php'; //strona logowania w razie niepoprawnych danych logowania

$db_host = 'localhost'; //serwer bazy danych
$db_port = '3306'; //port
$db_name = 'test'; //nazwa bazy
$db_pass = 'haslo'; //haslo
$db_user = 'root'; //login do bazy

$sesion_exp = 3600; //waznosc sesji w sekundach
$allow_login = 1; //1=mozna sie logowac 0=nie mozna sie logowac
$ip_multilog = 0; //1=dozwolone logowanie z jednego ip 0=niedozwolone

$str_blad_polaczeniadb = 'Błąd połączenia z bazą';
$str_blokada_logowania = 'Logowanie na stronie jest tymczasowo niemożliwe';
$str_juz_zalogowany = 'Jesteś już zalogowany';
$str_ip_zalogowany = 'Ktoś inny z tego adresu IP jest już zalogowany na stronie';
$str_podaj_lh = 'Nie podano loginu lub hasła';
$str_zle_lh = 'Niepoprawny login lub hasło';
$str_konto_nieaktywne = 'To konto nie zostało jeszcze aktywowane';
$str_konto_zalogowane = 'Ktoś inny jest już zalogowany na tym koncie';
$str_logowanie_pomyslne = 'Zalogowano';
$str_wylogowany = 'Wylogowano';

//ustawianie zmiennych sesji na wartosci standardowe niezalogowanego uzytkownika
function reset_session_data() {
	if (!isset($_SESSION['log_uid'])) {$_SESSION['log_uid'] = -1;}
	if (!isset($_SESSION['log_login'])) {$_SESSION['log_login'] = '';}
	if (!isset($_SESSION['log_lvl'])) {$_SESSION['log_lvl'] = -1;}
	if (!isset($_SESSION['log_sid'])) {$_SESSION['log_sid'] = session_id();}
	if (!isset($_SESSION['log_ip'])) {$_SESSION['log_ip'] = $_SERVER['REMOTE_ADDR'];}
}

//laczenie z baza danych
function db_connect() {
	global $db_log_sys;
	global $str_blad_polaczeniadb;
	global $db_host;
	global $db_port;
	global $db_name;
	global $db_pass;
	global $db_user;

	try {
			$db_log_sys = new PDO('mysql:host=' . $db_host . ';port=' . $db_port . ';dbname=' . $db_name, $db_user, $db_pass);
		} catch(PDOException $e) {
			die($str_blad_polaczeniadb);
		}
}

//funkcja wylogowania
function logout() {
	global $db_log_sys;

	try {
		//sprawdzenie czy sesja w bazie zgadza sie z sesja przegladarki
		$s_dane = $db_log_sys -> query('SELECT sid FROM session where uid = ' . $_SESSION['log_uid']);
		
		//jesli tak to usun ta sesje z bazy danych przy wylogowaniu
		if($s_dane_row = $s_dane -> fetch()) {
			if (($s_dane_row['sid']) == session_id()) {
				$dane = $db_log_sys -> prepare('DELETE FROM session WHERE uid = :uid');
				$dane -> bindValue(':uid', $_SESSION['log_uid'], PDO::PARAM_INT);
				$dane -> execute();
				$dane -> closeCursor();
			}
		}
		
		$s_dane -> closeCursor();
	} catch(PDOException $e) {
	}
	
	$_SESSION = array();
	reset_session_data();
	session_regenerate_id(); //utworzenie nowego id sesji
}

//funkcja czyszczaca tekst aby nie bawic sie w sql injection itp.
function clear_string($s) {
    if(get_magic_quotes_gpc()) {
        $s = stripslashes($s);
    }
    $s = trim($s);
    $s = mysql_real_escape_string($s);
    $s = htmlspecialchars($s);
    return $s;
}

//predzej slonce zgasnie niz brute force odkryje co znajduje sie pod tym hashem 
//zakladajac ze tylko Ty znasz te znaczki
function solenie($pass) {
    return md5(sha1('*&^D%%F'.$pass).'#!%Rgd64');
}

//funkcja wyswietlajaca informacje jednorazowe typu: 'bledny login' czy 'zostales wylogowany'
//jesli wartosc bedzie pusta to nie bedzie zadnej reakcji
function Log_msg() {
	if (isset($_SESSION['log_msg'])) {
		if ($_SESSION['log_msg'] != '') {
			echo($_SESSION['log_msg']);
			unset($_SESSION['log_msg']);
		}
	}
}

//generuje aktualny czas w sekundach od 1 stycznia 1970 + sekundy podane w parametrze
function Generuj_Czas($ile) {
	$czas_akt = time();
	return($czas_akt + $ile);
}

reset_session_data();
db_connect();

//sprawdzanie zgodnosci danych zesji przegladarki z tymi w bazie
if ($_SESSION['log_lvl'] > -1) {
	$s_dane = $db_log_sys -> query('SELECT exp, ip, sid FROM session where uid = ' . $_SESSION['log_uid']);
	
	//sprawdzenie czy wpis o id sesji(uzytkownika) znajduje sie w bazie
	if($s_dane_row = $s_dane -> fetch()) {
		//jesli sesja w bazie jest nieaktualna to uruchom funkcje wyloguj
		if ($s_dane_row['exp'] <= Generuj_Czas(0)) {
			$s_dane -> closeCursor();
			logout();
		}
		
		//jesli id sesji nie zgadza sie z tym w bazie to wyloguj
		if ($s_dane_row['sid'] != session_id()) {
			$s_dane -> closeCursor();
			logout();
		}
		
		//jesli ip sesji nie zgadza sie z tym w bazie to wyloguj
		if ($s_dane_row['ip'] != $_SERVER['REMOTE_ADDR']) {
			$s_dane -> closeCursor();
			logout();
		}
	} else {
		//jesli wpis sesji nie znajduje sie w bazie to wyloguj
		$s_dane -> closeCursor();
		logout();
	}
	
	$s_dane -> closeCursor();
}
?>

flogin.php

<?php
include('lconfig.php');

//wylogowanie przy wykorzystaniu $_GET
if (isset($_GET['f'])) {
	if ($_GET['f'] == 'logout') {
		logout();
		$_SESSION['log_msg'] = $str_wylogowany;
		header('Location: ' . $page_index);
		exit;			
	}
}

//jesli logowanie wylaczone to przypisz komunikat i do strony glownej
if ($allow_login != 1) {
	$_SESSION['log_msg'] = $str_blokada_logowania;
	header('Location: ' . $page_index);
	exit;	
}

//komunikat w przypadku gdy uzytkownik jest juz zalogowany
if ($_SESSION['log_lvl'] > -1) {
	$_SESSION['log_msg'] = $str_juz_zalogowany;
	header('Location: ' . $page_index);
	exit;
}

//jesli logowanie kilku uzytkownikow z jednego ip jest zablokowane
if ($ip_multilog == 0) {
	$s_dane = $db_log_sys -> query('SELECT ip, exp  FROM session');
	foreach($s_dane as $s_dane_row) { //petla czytajaca wszystkie ip z bazy sesji
		if ($s_dane_row['ip'] == $_SERVER['REMOTE_ADDR']) { //jesli dany ip sie znajdzie
			if ($s_dane_row['exp'] >= Generuj_Czas(0)) { //a sesja jest w ciaz wazna
				$_SESSION['log_msg'] = $str_ip_zalogowany;
				$s_dane -> closeCursor();
				header('Location: ' . $page_index);
				exit;
			}
		}
	}
	$s_dane -> closeCursor();
}

										/// usunac w dzialajacym skrypcie
										/// 
$_POST['login'] = $_GET['l'];			/// to pozwala na logowanie za pomocą $_GET 
$_POST['pass'] = $_GET['p'];			/// uzywane w czasie testow bez formularza z metodą POST
										///



//komunikat jesli login albo haslo nie jest podane
if (!isset($_POST['login']) || !isset($_POST['pass'])) {
	$_SESSION['log_msg'] = $str_podaj_lh;
	header('Location: ' . $page_login);
	exit;
}

$_POST['login'] = clear_string($_POST['login']); //czyszczenie loginu
$_POST['pass'] = clear_string($_POST['pass']); //czyszczenie hasla
$_POST['pass'] = solenie($_POST['pass']); //solenie hasla


//pobranie danych uzytkownika o podanym loginie i hasle
$u_dane = $db_log_sys -> prepare('SELECT id, login, lvl, akt  FROM users WHERE login = :login AND haslo = :haslo LIMIT 1');
$u_dane -> bindValue(':login', $_POST['login'], PDO::PARAM_STR);
$u_dane -> bindValue(':haslo', $_POST['pass'], PDO::PARAM_STR);
$u_dane -> execute();

//jesli dany uzytkownik i haslo jest zgodne z tymi z bazy
if($u_dane_row = $u_dane -> fetch()) {	
	if ($u_dane_row['akt']!='') { //jesli pole z kluczem aktywacyjnym nie jest puste to znaczy ze jeszcze nie aktywowano konta
		$_SESSION['log_msg'] = $str_konto_nieaktywne;
		$u_dane -> closeCursor();
		header('Location: ' . $page_index);
		exit;
	} else { //a jesli konto aktywne
	
		//wyszukaj ewentualnie istniejacego wpisu sesji uzytkownika o danym ID
		$s_dane = $db_log_sys -> prepare('SELECT sid, ip, exp  FROM session WHERE uid = :uid LIMIT 1');
		$s_dane -> bindValue(':uid', $u_dane_row['id'], PDO::PARAM_INT);
		$s_dane -> execute();
		
		//jesli taki wpis istnieje
		if($s_dane_row = $s_dane -> fetch()) {
		
			//jesli ID sesji albo IP sesji się nie zgadza z danymi przeglądarki to informuj o tym
			//chyba ze waznosc tej sesji z bazy juz wygasla
			if ((($s_dane_row['sid'] != session_id()) || ($s_dane_row['ip'] != $_SERVER['REMOTE_ADDR'])) && ($s_dane_row['exp'] >= Generuj_Czas(0))) {
				$_SESSION['log_msg'] = $str_konto_zalogowane;
				$u_dane -> closeCursor();
				$s_dane -> closeCursor();
				header('Location: ' . $page_login);
				exit;
			} else {
				//jesli jednak dane IP i ID sesji się zgadza to aktualizuj waznosc sesji
				//to w przypadku gdyby ktos jakims cudem logowal sie na to samo konto gdy jest juz zalogowany
				$czas = Generuj_Czas($sesion_exp);
				$wpis = $db_log_sys -> prepare('UPDATE session SET exp = :exp WHERE uid = :uid');
				$wpis -> bindValue(':exp', $czas, PDO::PARAM_INT);
				$wpis -> bindValue(':uid', $u_dane_row['id'], PDO::PARAM_STR);
				$wpis -> execute();
				$wpis -> closeCursor();	
			}
		} else {
			//jesli wpis sesji nie istnieje w bazie to go utworz
			$czas = Generuj_Czas($sesion_exp);
			$wpis = $db_log_sys -> prepare('INSERT INTO session (uid, sid, ip, exp) VALUES (:uid, :sid, :ip, :exp)');
			$wpis -> bindValue(':uid', $u_dane_row['id'], PDO::PARAM_INT);
			$wpis -> bindValue(':sid', session_id(), PDO::PARAM_STR);
			$wpis -> bindValue(':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR);
			$wpis -> bindValue(':exp', $czas, PDO::PARAM_INT);
			$wpis -> execute();
			$wpis -> closeCursor();			
		}

		//przypisanie zmiennej sesyjnej wartosci z tabeli uzytkownika
		$_SESSION['log_uid'] = $u_dane_row['id'];
		$_SESSION['log_login'] = $u_dane_row['login'];
		$_SESSION['log_lvl'] = $u_dane_row['lvl'];
		$_SESSION['log_sid'] = session_id();
		$_SESSION['log_ip'] = $_SERVER['REMOTE_ADDR'];
		
		$_SESSION['log_msg'] = $str_logowanie_pomyslne;
		$u_dane -> closeCursor();
		$s_dane -> closeCursor();
		header('Location: ' . $page_index);
		exit;
	}
} else {
	//jesli w bazie nie ma uzytkownika o takim loginie i hasle to znaczy ze login albo haslo niepoprawne
	$_SESSION['log_msg'] = $str_zle_lh;
	$_SESSION['log'] = array();
	$u_dane -> closeCursor();
	header('Location: ' . $page_login);
	exit;
}

?>

dane.sql

-- phpMyAdmin SQL Dump
-- version 3.4.10.1
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Czas wygenerowania: 07 Lis 2012, 00:55
-- Wersja serwera: 5.5.20
-- Wersja PHP: 5.3.10

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;

--
-- Baza danych: `test`
--

-- --------------------------------------------------------

--
-- Struktura tabeli dla  `session`
--

CREATE TABLE IF NOT EXISTS `session` (
  `uid` int(11) NOT NULL,
  `sid` varchar(32) NOT NULL,
  `ip` varchar(20) NOT NULL,
  `exp` int(11) NOT NULL,
  KEY `uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Zrzut danych tabeli `session`
--

INSERT INTO `session` (`uid`, `sid`, `ip`, `exp`) VALUES
(0, '0', '0', 0);

-- --------------------------------------------------------

--
-- Struktura tabeli dla  `users`
--

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `login` varchar(32) NOT NULL,
  `haslo` varchar(32) NOT NULL,
  `lvl` tinyint(4) NOT NULL DEFAULT '0',
  `email` varchar(50) NOT NULL,
  `akt` varchar(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;

--
-- Zrzut danych tabeli `users`
--

INSERT INTO `users` (`id`, `login`, `haslo`, `lvl`, `email`, `akt`) VALUES
(1, 'Admin', 'ee2d6c60dae92745ebbf8c232c4564a2', 3, '[email protected]', ''),
(2, 'User', '8f93d514815e78a8181c01ec1a455395', 1, '[email protected]', ''),
(3, 'Ktos', 'bb41ab2bd977382ea329c49591ca207c', 0, '[email protected]', '8d7f5gsdf7');

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

0

możliwość zablokowania logowania na różne konta z tego samego IP - mało przydatne, gdyż zapominasz o sieciach osiedlowych w miastach, czy sieciach radiowych na wsi - tam najczęściej jedno IP współdzieli nawet 50 osób.

CHARSET=latin1 - tylko utf-8. Tu nawet nie ma co dyskutować.

md5(sha1( - podwójne hashowanie - nie rób tego!

Pisane strukturalnie, nieładnie..
No i globalki - ale kod nieobiektowy to wiadomo.

niewidoczny element wykorzystujący AJAX do odświeżania sesji co zadaną ilość sekund - chyba minut? Jeżeli ajax ma tylko i wyłącznie sesji pilnować to mówienie o sekundach to przesada.

0

Z tym IP to faktycznie nie pomyślałem. Co do utf-8 to moje niedopatrzenie bo bazę tworzyłem w phpmyadmin i nie zmieniłem kodowania.

Podwójne hashowanie wydawało mi się dobrym pomysłem. Dlaczego odradzasz?

jeżeli chodzi o OOP to przyznam się po prostu, że jest to dla mnie jeszcze czarna magia ale powoli staram się tego nauczyć.

A jeżeli chodzi o te sekundy to skrypt był pisany z myślą o małej gierce multiplayer wykorzystującej pooling. Oczywiście w miarę potrzeb będzie można samemu ustalić czas

No i jeszcze moje pytanie do specjalistów. Jak ten system wygląda pod kontem bezpieczeństwa na tle innych prostych systemów logowania?

0

W teorii podwójne hashowanie zmniejsza entropię, w praktyce nie ma to większego znaczenia, bo znalezienie kolizji nie będzie wcale łatwiejsze.
Lepszym pomysłem natomiast będzie dołączenie soli (tak jak zrobiłeś), ale lepiej, jeśli będzie ona unikalna dla każdego użytkownika. Dopiero to przeszkodzi w wygenerowaniu rainbow tables dla całego twojego serwisu naraz.
Idąc dalej - zamień md5 (nigdy z niego nie korzystaj!) i sha1 na bcrypt. Skaluje się on bardzo ładnie z wymaganym czasem procesora na jego generację, co komplikuje sprawę szukania kolizji o rząd wielkości ;).

0

Po co modyfikujesz hasło przed jego zahashowaniem?

$_POST['pass'] = clear_string($_POST['pass']); //czyszczenie hasla

W bazie będzie hash, więc usuwanie potencjalnie niebezpiecznych znaków nie ma sensu.

0

Nie chcę tutaj zaśmiecać wątku obszernym kodem z niewielkimi zmianami więc w załączniku podaję nową wersję skryptu.

Zmiany:
-wszystkie zmienne dostały prefix "L_" co powinno zmniejszyć prawdopodobieństwo kolizji nazewnictwa (mam nadzieję, że w miarę rozwoju nie zapomnę się i utrzymam prefix w nowych zmiennych)
-hasło nie jest już czyszczone bo faktycznie bez sensu (dziekuje afk)
-czyszczenie danych sesji zmienione trochę
-poprzednie solenie md5 i sha1 zastąpione bcrypt (hasło + sól + lowercase(login)) (dziękuję Rev i dzek69)
-W bazie zmienione kodowanie na utf-8 mam nadzieję, że tym razem nie dałem plamy (dziękuję dzek69)
-dodana tabela "ban" do bazy. Aktualnie ban na logowanie ale gotowa na rejestrację, pisanie tematów/postów/artów, komentowanie i przeglądanie określonych treści

Swoją drogą ten bcrypt to fajna sprawa bo za każdym razem generuje inny hash ale zgodny z hasłem podczas sprawdzania i jest odpowiednio wolny a z tego co czytałem w poszukiwaniu implementacji do PHP to te kilka ms (nawet sekunda) jest przy logowaniu nieszkodliwe a w razie próby łamania metodą brute force sprawia, że łamanie może potrwać nawet kilka lat.

implementację do PHP znalazłem pod adresem http://stackoverflow.com/questions/4795385/how-do-you-use-bcrypt-for-hashing-passwords-in-php

Poczytałem też trochę o klasach w PHP i blade pojęcie już posiadam lecz nie mam pojęcia jak można by ten aktualny kod umieścić w klasie aby prosto można go wykorzystać

P.S. Nie spodziewałem się tak życzliwej pomocy z waszej strony za co bardzo wam dziękuję i mam nadzieję, że z waszą pomocą stuningujemy ten skrypt jeszcze bardziej. Na pewno każdemu laikowi w tym także mnie się przyda. Pozdrawiam

0

-wszystkie zmienne dostały prefix "L_" co powinno zmniejszyć prawdopodobieństwo kolizji nazewnictwa (mam nadzieję, że w miarę rozwoju nie zapomnę się i utrzymam prefix w nowych zmiennych)

Tego właśnie byś uniknął stosując obiekty ;)
Stosujesz je w praktyce - PDO - to już dobrze wróży, teraz zostaje tylko ogarnięcie - w jakich sytuacjach tego potrzebuję i jak to wykonać.

Jeszcze jedna rzecz, na którą zwrócę uwagę:

catch(PDOException $e) {
  die($str_blad_polaczeniadb);
}

Błędów nie powinienś pokazywać użytkownikowi, a szczególnie tych, związanych z bazą danych. Zapisuj je sobie do pliku, albo jeżeli wyświetlasz (co jest wygodne w sumie) - to tylko na określonych warunkach (nawet po prostu sprawdzając czy IP jest równe Twojemu domowemu). Czyli najprościej - zrób sobie funkcję, której będziesz używać zamiast die(), w której to będziesz sprawdzał warunek, czy osoba przeglądająca stronę jest godna zaufania (czyli to Ty), coś takiego:

function funkcja_ogarniajaca_bledy($string) {
  if (IP_JEST_TAKIE_JAKIE_CHCEMY_LUB_INNE_WARUNKI) {
    die($string);
  }
  else {
    // to dla zmyłki przeciwnika - jak się coś dzieje krytycznego, to udajmy jakiś błąd serwera/konfiguracji, niż samego kodu - zawsze mniejsza szansa, że ktoś będzie szukał dziur
    header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
    die('500 Internal Server Error');
  }
}
0

Jak w poprawionym systemie logowania Pino używającym BCrypt zastosować COOKIESy w celach automatycznego logowania?

0
Pino napisał(a):

Swoją drogą ten bcrypt to fajna sprawa bo za każdym razem generuje inny hash ale zgodny z hasłem podczas sprawdzania i jest odpowiednio wolny a z tego co czytałem w poszukiwaniu implementacji do PHP to te kilka ms (nawet sekunda) jest przy logowaniu nieszkodliwe a w razie próby łamania metodą brute force sprawia, że łamanie może potrwać nawet kilka lat.

Nieprawda, zapewne ten Twój hash ma format uniksowy i zawiera dodatkowo informację o użytym algorytmie i soli, która za każdym hashowaniem jest losowana. Tak czy siak przy sprawdzaniu nadal wyciąga się sól z istniejącego hasha i dąży do uzyskania tej samej wartości.

//predzej slonce zgasnie niz brute force odkryje co znajduje sie pod tym hashem
//zakladajac ze tylko Ty znasz te znaczki
function solenie($pass) {
return md5(sha1('*&^D%%F'.$pass).'#!%Rgd64');
}

Byś się zdziwił z tym gaśnięciem słońca... Rzeczy typu sól do haseł powinny siedzieć jako stała zdefiniowana w konfiguracji projektu lub być generowane osobno dla każdego użytkownika (polecam to drugie). W konfiguracji powinno też się móc wybrać algorytm wykorzystywany do hashowania. Domyślnie powinien być wybrany algorytm, który był celowo projektowany w ten sposób, aby wygenerowanie hasha było bardzo kosztowne obliczeniowo. SHA-1 i MD5 to algorytmy optymalizowane pod względem szybkości, użycie ich jako dodatkowego środka chroniącego hasło mija się z celem. Przynajmniej w taki sposób, w jaki Ty to robisz.

Jakiekolwiek filtrowanie hasła przed hashowaniem nie ma sensu, szczególnie przepuszczanie tego przez takie funkcje jak mysql_real_escape_string :|

dzek69 napisał(a):

-wszystkie zmienne dostały prefix "L_" co powinno zmniejszyć prawdopodobieństwo kolizji nazewnictwa (mam nadzieję, że w miarę rozwoju nie zapomnę się i utrzymam prefix w nowych zmiennych)

Tego właśnie byś uniknął stosując obiekty ;)

Po grzyba? Przecież ostatnio do języka weszły funkcje anonimowe i przestrzenie nazw, można to zrobić w sposób cywilizowany pisząc np. funkcyjnie.

0

Jednak skusiłem się do użycia klasy. Myślę, że jakoś mi to poszło. Póki co to wszystko (logowanie, rejestracja, lista zalogowanych itp.) planowo będzie w jednej klasie. Pewnie nie jest to najlepsze wyjście ale to moje pierwsze podejście do klas i rozbicie tego mogło by mi sprawić problemy. Z biegiem czasu zobaczę czy dam radę to jakoś rozbić o ile to będzie konieczne.

Jeżeli chodzi o propozycję dziek69 z tymi błędami to utworzyłem funkcję która ogarnia błędy ale trochę inaczej to rozwiązałem. Mam nadzieję, że zda egzamin. funkcja ma 3 parametry: tekst jawny, tekst ukryty i krytyczny. jeżeli tekst jawny nie jest pusty i nie krytyczny to przypisuje go zmiennej $_SESSION['log_msg'], jeśli jest krytyczny to wyświetla go w die();. Tekst ukryty jeśli nie jest pusty do dodaje go do listy błędów w bazie (ip, time(), tresc beldu). Na razie tego nie ma ale tak to będzie wyglądało.

Buka77 jeśli masz zamiar korzystać z tego systemu to proponuję poczekać gdyż jego obsługa znacznie się zmieni ponieważ utworzyłem z tego klasę i dzięki Twojemu zapytaniu staram się wbudować funkcję automatycznego logowania przy użyciu cookies.

Demonical Monk jeżeli chodzi o ten hash bcrypt to w tych sprawach jestem zielony lecz z tego co zauważyłem to jedyne co zostaje na stałe przy tym hashu to sól wewnętrzna klasy bcrypt. Jednak do tej klasy idzie nie tylko hasło lecz także sól z systemu logowania oraz login. Hasło nie jest już filtrowane bo faktycznie nie ma to sensu. Generalnie samo hashowanie wydaje mi się wystarczająco bezpieczne jak na taki system logowania. Jeśli się mylę to mnie uświadom jak byś mógł

Wszelkie uwagi i propozycje wciąż mile widziane. Mam nadzieję, że po weekendzie będę już miał gotowe pliki do przedstawienia


Dodane po przemyśleniach ;)

BCRYPT moim zdaniem uniemożliwia zastosowania automatycznego logowania ze względu na to, że dla tego samego hasła hash jest zawsze inny a przechowywanie jawnego hasła w ciastkach nie jest mądre. Myślę, że powrót do solonego MD5 i/lub SHA1 będzie koneczne. Mam nadzieję, że znajdzie się jakaś możliwość zabezpieczenia przed tablicami tęczowymi i co najważniejsze spowolnienia funkcji solenie(). Macie jakiś pomysł aby funkcja wykonywała się dłużej?

0

To jeszcze coś:
Konsekwentnie nazywaj swoje funkcje i zmienne, przykłady Twoich funkcji:

clear_string
solenie
Log_msg
Generuj_Czas

małe litery i podkreślenie, po angielsku
małe litery, jedno słowo, po polsku
duża i mała litera z podkreśleniem, po angielsku
duża i duża litera z podkreśleniem, po polsku
Czyli:

  • duże/małe litery, z podkreślenia widzę korzystasz zawsze, akurat ja korzystam zawsze z camelCase i to chyba najpopularniejsze dla bibliotek PHP (żeby daleko nie szukać: PDO). Okej, wiem, że w samym PHP nie ma żadnej konsekwencji jeżeli chodzi o nazwy, ale Ty już nie musisz tak robić ;) Nie mówię, żebyś zmieniał na camelCase - po prostu trzymaj się jednego schematu.
  • polskie, lub angielskie. lepiej się przywiązać do angielskich (chodzi zarówno o zmienne jak i funkcje), bo takie już są w php i pozwolą każdemu zrozumieć co robią funkcje już po nazwie. zresztą wiem też, że z dziwnego powodu Ci, którzy próbują pisać wszystko po polsku - i tak robią angielskie wstawki

To taka uwaga już obok samego kodu ;)

0

BCRYPT moim zdaniem uniemożliwia zastosowania automatycznego logowania ze względu na to, że dla tego samego hasła hash jest zawsze inny a przechowywanie jawnego hasła w ciastkach nie jest mądre. Myślę, że powrót do solonego MD5 i/lub SHA1 będzie koneczne. Mam nadzieję, że znajdzie się jakaś możliwość zabezpieczenia przed tablicami tęczowymi i co najważniejsze spowolnienia funkcji solenie(). Macie jakiś pomysł aby funkcja wykonywała się dłużej?

Yhy, to jak niby w takim razie dałoby się sprawdzić poprawność hasła? Kompletna bzdura. Sugeruję poduczyć się trochę języka zanim zaczniesz implementować mechanizmy security, bo wygląda na to jakbyś miał gotową bibliotekę i nie potrafił zinterpretować jej działania.

0
Demonical Monk napisał(a):

(...)bo wygląda na to jakbyś miał gotową bibliotekę i nie potrafił zinterpretować jej działania.

Nie inaczej. W programowaniu orłem nie jestem ale po prostu chciałem podjąć się wyzwania i napisać taki system z pomocą bardziej obeznanych oraz podzielić się z innymi osobami tym co uda mi się napisać. Może komuś się przyda taki prosty gotowiec. Oczywiście każda krytyka jest pomocna lecz mój poziom nie pozwala na stosowanie metod jakich zaawansowani programiści by użyli. Jeśli znalazł byś czas i oczywiście mógł pomóc to fajnie by było gdybyś podpowiedział jak zrobić takie porównanie bo aktualnie przeszedłem na MD5 i SHA1 z powrotem

Jeżeli chodzi o to nazewnictwo to w PHP jak i delphi popełniam ten sam błąd. Może dla tego, że część funkcji bardziej do mnie przemawia w języku polskim a część w angielskim bo napatrzałem się w innych skryptach na nie. Postaram się jakoś to ujednolicić przynajmniej w metodach publicznych klasy

0
  1. Użytkownik loguje się i zaznacza "chcę być zalogowany cały czas przez następny tydzień".
  2. Skrypt generuje losowy ciąg, np. CcOjkrmYA2iNvaD1tBguvBri9HRejC0.
  3. Skrypt wrzuca ten ciąg do bazy danych razem z informacją o ID użytkownika oraz datą wygaśnięcia tokenu.
  4. Skrypt nadaje ciasteczko autologin o treści takiej jak wygenerowany ciąg.

Potem jak user ponownie odwiedzi stronę i nie ma aktywnej sesji wyciągamy wartość z ciasteczka autologin i sprawdzamy czy nie ma takiej w bazie danych. Dzięki temu przechowywana informacja sama w sobie jest bezużyteczna, trzymanie hasha hasła w ciasteczku daje niepotrzebną możliwość połamania tego hasła w przypadku kradzieży ciastka i uniemożliwia ustalenie jakiegoś konkretnego terminu wygaśnięcia autologowania (tutaj możesz nawet usunąć komuś token z bazy i już nie będzie się logował automatycznie).

0

Umieszczam skrypt logowania oparty na klasie. Mam nadzieję, że dobrze wszystko poszło.

-Blokowanie logowania z jednego IP usunięte lecz w dalszym ciągu blokada logowania tego samego użytkownika na wielu komputerach
-automatyczne logowanie zaimplementowane
-hashowanie już ogarnięte i takie zostanie. Może i jest się do czego przyczepić ale wydaje się wystarczająco wolne i odpowiednio posolone
-banowanie nie polega już na IP lecz na ID użytkownika
-zdefiniowanie ID administratora
-ustalenie ważności sesji oraz ważności opcji automatycznego logowania
-Ogarnięcie błędów nie takie jak planowane lecz sam błąd łączenia z bazą widoczny jako brak dostępu do strony

Najwięcej kłopotów miałem z automatycznym logowaniem i z nim związanymi operacjami na bazie danych lecz myślę, że wszystko poszło ok. Testowałem to i wydaje się już dopracowane.

Wciąż nie ma nic poza logowaniem ale rejestracja i pozostałe elementy takie jak lista zalogowanych użytkowników i skrypt instalacji systemu będę opracowywał jeśli to co już jest okaże się sprawne bo chyba to jest najtrudniejsza część i nie chcę iść dalej jeżeli z tym jest coś nie tak.

Zapraszam do testowania i wytykania mi kolejnych błędów ;)

0

Facepalm.

	//hashowanie hasla
	public function hash($pass, $login) {
		return crypt($pass, '$2a$14$' . sha1($login) . md5($this->sol));
	}

Co Ci uniemożliwia użycie tego? Już więcej hashy nawalić się nie dało.

0
Demonical Monk napisał(a):

Co Ci uniemożliwia użycie tego? Już więcej hashy nawalić się nie dało.

W sumie racja. za bardzo się głowiłem nad dosłownym porównaniem hashy podczas automatycznego logowania i jak już wykorzystałem Twoją technikę to nie pomyślałem o tym ponownie. W dalszych odsłonach na pewno postaram się to poprawić.

--edit ;)
A te md5 i sha1 to tak aby nawalić jak najwięcej śmieci do wynikowego hasha. Myślę, że są na tyle szybkie aby nie sprawiały problemu

0
Pino napisał(a):
Demonical Monk napisał(a):

Co Ci uniemożliwia użycie tego? Już więcej hashy nawalić się nie dało.

W sumie racja. za bardzo się głowiłem nad dosłownym porównaniem hashy podczas automatycznego logowania i jak już wykorzystałem Twoją technikę to nie pomyślałem o tym ponownie. W dalszych odsłonach na pewno postaram się to poprawić

Kolejna sprawa - czemu klasa instancjonuje sama siebie tuż po załadowaniu? To czym wtedy jest ta klasa? Opakowaniem na cały burdel który masz w projekcie i niczym więcej. Embedowalny system logowania nie powinien wcale dostarczać gotowych widoków, a przynajmniej nie na takiej zasadzie. Sama blokada "logowania z innego komputera" czymkolwiek ona jest to durnota, serwisy internetowe idą w kierunku bardzo wysokiej dostępności na urządzeniach różnej klasy, nie odwrotnie.

A te md5 i sha1 to tak aby nawalić jak najwięcej śmieci do wynikowego hasha. Myślę, że są na tyle szybkie aby nie sprawiały problemu

To bez sensu. Nie podniesiesz tak mocy wynikowego hasha. Sól powinna być losową solą per user i tyle. I bynajmniej nie taką: *^$D$^&D(D, tylko faktycznie wygenerowaną losowo i trochę dłuższą. Zalinkowana klasa sama tworzy sensowną sól. Po co w ogóle przechowywać globalnie jakąkolwiek sól skoro masz hashe w formacie uniksowym - nie mam pojęcia...

0

Myślę, że system jest już gotowy do pracy. Podzieliłem to na kilka klas (mam nadzieję, że w miarę sensownie). Blokowanie logowania tego samego użytkownika na wielu urządzeniach jest już opcjonalne. Jest już opcja rejestracji z wysłaniem maila potwierdzającego (opcjonalnie).

Mam tylko pytanie odnośnie zabezpieczenia przed kradzieżą sesji. Aktualnie sprawa wygląda tak, że w bazie jest user ID, session ID oraz klucz. Klucz to md5(sessID, login) i jest zapisywany w ciastku. Przy każdym sprawdzaniu zalogowania czyli zawsze podczas przeładowania strony sprawdzane jest i porównywane z bazą session ID oraz klucz w ciastku z kluczem w bazie. W przypadku kradzieży sessID klucz w ciastku nie będzie się zgadzał i wyloguje. Dodatkowo za każdym razem sessID oraz klucz są generowane i zapisywane w bazie na nowo. Czy takie rozwiązanie ma sens? Czy może wystarczy tylko samo wygenerowanie sessID na nowo i wtedy używanie klucza okaże się zbyteczne? Dodam jeszcze, że session_regenerate_id używam z parametrem true.

Demonical Monk napisał(a):

Embedowalny system logowania nie powinien wcale dostarczać gotowych widoków, a przynajmniej nie na takiej zasadzie.

Nie rozumiem o czym tutaj jest mowa :/

1 użytkowników online, w tym zalogowanych: 0, gości: 1