Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Logowanie na bazie MySQL, a nie na sesjach - bezpieczne?
Forum PHP.pl > Forum > PHP
infoo1
Mam pomysł na zrobienie takiego logowania na bazie:
1. Nie ma sesji, ani cookies.
2. Struktura tabeli z sesjami:


Kod
+---------+------------+--------------+---------+------------+-------------+
| user_id | session_id | user_browser | user_ip | start_time | remember_me |
+---------+------------+--------------+---------+------------+-------------+


Objaśnienia:
  • user_id:
    id użytkownika z tabeli prefix_users
  • session_id:
    "sid". Planuję go przekazywać w adresie (index.php?mode=blablabla&sid=tu_sid). //W PHP była funkcja generująca sid. Jak się nazywała?
  • user_browser:
    $_SERVER['HTTP_USER_AGENT'];
  • user_ip:
    $_SERVER['REMOTE_ADDR'];
  • start_time:
    time(); przy logowaniu. W konfiguracji ustalam po jakim czasie ma wylogowywać (tabela prefix_config).
  • remember_me:
    auto-logowanie
3. Przy każdym odświeżeniu strony sprawdzam, czy zgadza się user_browser, user_ip, start_time, session_id.
4. Remember_me: działa, jeśli jest dozwolone w konfiguracji.
Pytania:
1. Czy to bezpieczne? Coś pominąłem?
2. Czy sprawdzać oprócz tego $_SERVER['X_FORWARDED_FOR']; //I czemu nie znalazłem tego w manualu?
3. Na ile powinienem ustawić domyślny czas trwania sesji (bo może być albo domyślny, albo na zawsze (remember_me może być 0 lub 1)).
Darti
A że tak zapytam, jakie są powody do rezygnowania ze zwykłej sesji ?
infoo1
1. 2 (i więcej) sesje się zlepiają w jedną, czyli user jest zalogowany w dwóch oknach na tej samej stronie, ale jako inny użytkownik (nie, nie multi-account. To jest konieczne, a nie chcę robić dodatkowych identyfikatorów.).
2. Zwykłe sesje są nieco przestarzałe.
3. Łatwiej hacknąć stronę na zwykłych sesjach, niż dobrze zrobioną na bazie (sprawdzałem).
erix
Cytat
1. 2 (i więcej) sesje się zlepiają w jedną, czyli user jest zalogowany w dwóch oknach na tej samej stronie, ale jako inny użytkownik (nie, nie multi-account. To jest konieczne, a nie chcę robić dodatkowych identyfikatorów.).

Ale to przecież zależy od identyfikatora, a nie od miejsca zapisu danych. smile.gif Baza jest w tym wypadku jak nośnik - nieważne, czy płyta CD, czy DVD, zero, to zero, jeden, to jeden.

Cytat
2. Zwykłe sesje są nieco przestarzałe.

Heh, też mi argument. Pliki są szybsze w działaniu niż baza.

Cytat
Łatwiej hacknąć stronę na zwykłych sesjach, niż dobrze zrobioną na bazie (sprawdzałem).

Udowodnij. Można mieć zrobioną na bazie i bardziej podatną na ataki niż w przypadku standardowego mechanizmu. To zależy, co i jak sprawdzasz, ale nie od miejsca zapisu! To, że zdumpuję dane do bazy, to nic nie zmieni. Sam fakt innego miejsca zapisu niewiele tu da.
Darti
hmm
ok, z Twojego rozwiązania wychodzi mi jedynie, że jedynym zabezpieczeniem to i tak jest IP (zabezpieczenie przed używaniem dwóch kont na raz).

1) jak będę chciał korzystać z dwóch kont to użyję proxy - prosty hack
2) jak zechcę legalnie pograć z sąsiadem i dzielimy jedno łącze z kablówki to nie pogramy równocześnie - poważny minus

A tak poza tym - Twoje rozwiązanie czy sesja to praktycznie to samo (kiedyś nr sesji przekazywany był w pasku adresu, czyli tak jak u Ciebie) a ile więcej zachodu ? Syzyfowa praca.
Łatwiej ustaw sobie sessid przekazywane w pasku adresu w konfiguracji PHP i wyjdzie Ci dokładnie to samo.
infoo1
1. Nie, nie pograć. To program, nie gra.
2. Tu mogą być "multi-accounts". A nawet są. Celowo. Nie chce mi się tłumaczyć dlaczego, za dużo do tłumaczenia.
3. Wiem, że można
4. Nie mogę, nie jestem Adminem serwera (a funkcja ini_set() jest zablokowana).
5. Pliki łatwo zniszczyć.
6. Nie chce mi się tutaj wpisywać wszystkiego, co zrobiłem aby hacknąć. Za dużo pisania. Poza tym napisałem "dobrze zrobioną", więc nic tego nie hacknie dry.gif .
7. Wiem, że zależy od id, ale w bazie łatwej to zrobić.
8.
Cytat
Pytania:
1. Czy to bezpieczne? Coś pominąłem?
2. Czy sprawdzać oprócz tego $_SERVER['X_FORWARDED_FOR']; //I czemu nie znalazłem tego w manualu?
3. Na ile powinienem ustawić domyślny czas trwania sesji (bo może być albo domyślny, albo na zawsze (remember_me może być 0 lub 1)).

9. Czy to jest optymalne (generowanie sid):
  1. <?php
  2. $chars  = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890';
  3.      $number = 40;
  4.      $strlen = strlen($chars)-1;
  5.  
  6.      $i   = 0;
  7.      $sid = '';
  8.  
  9.      while ($i < $number)
  10.      {
  11.        $id   = rand(0,$strlen);
  12.        $sid .= mb_substr($chars,$id,1);
  13.        $i++;
  14.      }
  15. ?>
Darti
Ależ wszystko w porządku, sam nie polecam zachowywania danych w sesjach po stronie klienta, chodzi mi jedynie o to, że identyfikator sesji generowany domyślnie przy starcie sesji jest na tyle dobry, że nie trzeba odkrywać ameryki i wprowadzać swojego, 32 bajty czy 40 bajtów - ilość kombinacji w obu przypadkach astronomiczna.
Niezaprzeczalny plus (czy też minus) Twojej metody to przenośność między przeglądarkami - otworzę stronę w FF, zaloguję się, skopiuje adres do IE i mam ciągle tą samą sesję.
infoo1
Nie do końca smile.gif. Jeszcze jest $_SERVER['HTTP_USER_BROWSER']; winksmiley.jpg
Ogólnie nigdzie nie ma być niczego z $_SESSION, session_start() itd., czyli standardowych sesji.
A teraz wróćmy do moich 3 pytań
Darti
IE nie przekazuje sesji między oknami (czy też FF bo już nie pamiętam), ale to wciąż ta sama przeglądarka.

1) Bezpieczne, o ile porządnie zabezpieczysz pozostałą część serwisu (z tym że wywołania http (adres z numerem sesji) są łatwiejsze do zdobycia niż nagłówki http dla kogoś kto nasłuchuje (niewiele ale zawsze), z tą uwagą że cache przeglądarki pamięta adresy a nie zmienne sesyjne
2) Można, ale nie zawsze dostaniesz te dane bo nie zawsze są wysyłane (nie ma na to RFC dlatego nie ma w manualu)
3) ja ZAWSZE daje czas domyślny trwania sesji (boję się kafejek internetowych i głupoty niewylogowujących się userów jak ognia)
infoo1
0) Ale jak sprawdzam browsera, to pokaże coś innego. Przykład:
pod FX mam:
Cytat
Mozilla/5.0 (Windows; U; Windows NT 6.0; pl; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4

a pod badzIEwiem:
Cytat
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)

1) Sprawdzam browsera, IP, sesję w $_GET['sid'] i XFF. Przykłady:
  1. <?php
  2. function get_user_id()
  3.      {
  4.        global $db;
  5.        $user_browser = $_SERVER['HTTP_USER_AGENT'];
  6.        $user_ip      = $_SERVER['REMOTE_ADDR'];  
  7.        $sid          = $_GET['sid'];
  8.  
  9.        $sql   = "SELECT user_id FROM ".SESSIONS_TABLE." WHERE session_id='".$sid."' AND user_browser='".$user_browser."' AND user_ip='".$user_ip."'";
  10.        $query = $db->sql_query($sql);
  11.        $arr   = $db->sql_fetcharray($query);
  12.  
  13.        while ($rec = $arr){$user_id = $rec[0];}
  14.  
  15.        return $user_id;
  16.      }
  17.  
  18.      function is_session()
  19.      {
  20.        $sql   = "SELECT user_id FROM ".SESSIONS_TABLE." WHERE session_id='".$sid."' AND user_browser='".$user_browser."' AND user_ip='".$user_ip."'";
  21.        $query = $db->sql_query($sql);
  22.        $arr   = $db->sql_fetcharray($query);
  23.  
  24.        $is_session = false;
  25.        while ($rec = $arr){$is_session = true;}
  26.  
  27.        return $is_session;
  28.      }
  29. ?>
get_user_id - pobiera id użytkownika; is_session - sprawdza, czy user jest zalogowany
2) Wiem, że nie zawsze dostanę te dane
3) Fakt, tutaj userzy mogą umożliwić innym hacknięcie. Na szczęście do mojego programu dostęp jest tylko z jednego budynku (serwer, a do niego podłączone komputery). Zastanawiam się nad 30 minutami. To powinno wystarczyć.
Darti
oj ... miałem na mysli dwa okna IE = dwie różne sesje (bądź dwa okna FF to dwie różne sesje), nie pamiętam czy ten efekt jest pod FF czy pod IE ale chyba w obu ...

poza tym funkcja is_session() zawsze zwróci true, o ile nie zawiesi procesu (to samo w get_user_id)
i błąd SQL (chyba że korzystasz z magicznych zmiennych)
infoo1
Cytat
oj ... miałem na mysli dwa okna IE = dwie różne sesje (bądź dwa okna FF to dwie różne sesje), nie pamiętam czy ten efekt jest pod FF czy pod IE ale chyba w obu ...
Mówiłem o tym:
Cytat
otworzę stronę w FF, zaloguję się, skopiuje adres do IE i mam ciągle tą samą sesję.


Cytat
poza tym funkcja is_session() zawsze zwróci true, o ile nie zawiesi procesu (to samo w get_user_id)
Dlaczego? Co zrobić, aby działało?


Cytat
magicznych zmiennych
A co to? Chodzi o define("SESSIONS_TABLE", $config['prefix'].'sessions');, a potem w zapytaniu:
Cytat
$sql = "SELECT user_id FROM ".SESSIONS_TABLE." WHERE session_id='".$sid."' AND user_browser='".$user_browser."' AND user_ip='".$user_ip."'";
Darti
Cytat(infoo1 @ 13.12.2008, 00:34:39 ) *
Dlaczego? Co zrobić, aby działało?


chodzi mi o to:
  1. <?php
  2. while ($rec = $arr){$is_session = true;}
  3. ?>


przypisanie $rec = $arr zawsze da true bo się udało, więc pętla while będzie nieskończona ....
http://pl.php.net/manual/pl/control-structures.while.php

no i skąd bierzesz zmienne $sid, $user_browser itd w funkcji is_session ?
infoo1
Cytat
no i skąd bierzesz zmienne $sid, $user_browser itd w funkcji is_session ?
Mam to, co w get_user_id();, ale nie podawałem tego samego.

Cytat
przypisanie $rec = $arr zawsze da true bo się udało, więc pętla while będzie nieskończona ....
Zawsze stosuję takie coś:
  1. <?php
  2. $query = $db->sql_query($sql);
  3.       $arr   = $db->sql_fetcharray($query);
  4.  
  5.       while ($rec = $arr){
  6. //
  7. }
  8. ?>
I działa.


Edit:
Więc bezpieczne?
pest
Więc cała ta zabawa ma na celu:
  1. przeniesienie miejsca przechowywania danych sesyjnych z pliku do bazy
  2. ograniczenie ilości zmiennych przechowywanych w sesji tylko do zmiennej user_id
  3. ograniczenie możliwości przesyłania zmiennej session_id tylko do parametru _GET
  4. dodanie dodatkowych informacji do sesji takich jak IP i USER_AGENT
Coś pominąłem ?

Podsumowując (bezpieczeństwo i użyteczność):
  1. - ani grzeje ani ziębi, jak ktoś tutaj już napisał, to tylko miejsce przechowywania
  2. - jeśli to ma służyć tylko do przechowywania id usera, a wszystkie jego zmienne byłyby wczytywane dopiero w kolejnym zapytaniu to w porównaniu do mechanizmu sesji są już 2 zapytania do bazy zamiast jednego odczytu pliku
  3. - po co ograniczać możliwość przesyłania session_id tylko do _GET, skoro sesja wbudowana w php może być przekazywana zarówno w _GET jak i _COOKIE (przy czym _COOKIE jest dużo bezpieczniejsze ze względu na brak identyfikatora w adresie)
  4. - niby fajny pomysł przy kradzieży session_id należałoby mieć też ten sam IP... niestety są użytkownicy w sieci co mają zmienne IP i może im się zmieniać kilka razy na zalogowanie, co do danych USER_AGENT, to już lepiej bo "nie szkodzi", ale też dużo nie pomaga, wystarczyłoby mieć tą samą przeglądarkę (wersję)
ucho
Strasznie to wszystko skomplikowane - do podstawowego celu, czyli wiele sesji w jednej przeglądarce wystarczy pewnie
Kod
session.use_trans_sid = 1
session.use_cookies = 0
session.use_only_cookies = 0

w php.ini lub ustawione za pomoca ini_set(). Ale imho przechowywanie identyfikatora sesji w SID jest dość niebezpieczne - świetnie je widać choćby w logach wszelakich proxy. Ponadto użytkownicy zawsze wysyłając linki znajomych podają je z SIDem - a szansa, że są w jednej sieci, i mają identyczną przeglądarkę mimo wszystko jest całkiem duża.
infoo1
To rezygnuję z sid w linku.

Cytat
# ograniczenie ilości zmiennych przechowywanych w sesji tylko do zmiennej user_id
Nie, całkowite wywalenie standardowych sesji.
Cytat
# ograniczenie możliwości przesyłania zmiennej session_id tylko do parametru _GET
Z tego rezygnuję.
Cytat
dodanie dodatkowych informacji do sesji takich jak IP i USER_AGENT
To tylko ma służyć do identyfikacji użytkownika.


Jak to jest zrobione w phpBB3? W bazie jest IP, USER_AGENT itp. Po zmianie IP, wyczyszczeniu cookies itd. sesja nadal istnieje (jeśli ma się włączoną opcję "zapamiętywania").
infoo1
Nie zawsze ma się dostęp do takich rzeczy


Jednak wracam do starego sposobu:
  1. <?php
  2. class user
  3.  {
  4.    var $userdata = array();
  5.  
  6.  
  7.    function session_begin()
  8.    {
  9.      $this->assign_userdata();
  10.      $this->assign_session_userdata();
  11.    }
  12.  
  13.    function session_kill()
  14.    {
  15.      unset($_SESSION);
  16.      unset($this->userdata);
  17.    }
  18.  
  19.    function assign_userdata()
  20.    {
  21.      global $db;
  22.      $sql = "SELECT * FROM ".USERS_TABLE." WHERE user_id='".$this->get_user_id()."'";
  23.      $sql = $db->sql_query($sql);
  24.      $i++;
  25.      while ($row = $db->sql_fetcharray($sql))
  26.      {
  27.        foreach ($row as $key => $value)
  28.        {
  29.          $this->userdata[$key] = $value;
  30.        }
  31.      }
  32.  
  33.      if($this->userdata['user_id'] !== 0)
  34.      {
  35.        $this->userdata['is_registered'] = true;
  36.      }
  37.      else
  38.      {
  39.        $this->userdata['is_registered'] = false;
  40.      }
  41.    }
  42.  
  43.    function assign_session_userdata()
  44.    {
  45.      $_SESSION['user_id']    = $this->userdata['user_id'];
  46.      $_SESSION['user_login'] = $this->userdata['user_login'];
  47.      $_SESSION['user_pass']  = $this->userdata['user_pass'];
  48.      $_SESSION['user_grade'] = $this->userdata['user_grade'];
  49.    }
  50.  
  51.    function get_user_id()
  52.    {
  53.      if(isset($_SESSION['user_id']))
  54.      {
  55.        return $_SESSION['user_id'];
  56.      }
  57.      else
  58.      {
  59.        $_SESSION['user_id'] = 0;
  60.        return 0;
  61.      }
  62.    }
  63.  
  64.    function login($login,$pass)
  65.    {
  66.      global $db, $main;
  67.      $browser = $_SERVER['HTTP_USER_AGENT'];
  68.      $ip      = $_SERVER['REMOTE_ADDR'];
  69.      $sid     = $this->generate_sid();
  70.  
  71.      $sql = "SELECT user_id FROM ".USERS_TABLE." WHERE user_login='".$login."' AND user_pass='".$password."'";
  72.      $sql = $db->sql_query($sql);
  73.      while ($row = $db->sql_fetcharray($sql))
  74.      {
  75.        $_SESSION['user_id'] = $row['user_id'];
  76.      }
  77.      $main->redirect($main->generate_url('UCP',true,$sid));
  78.      return true;
  79.    }
  80.  
  81.    function register()
  82.    {
  83.      global $main;
  84.  
  85.      if(empty($_POST['login'])){
  86.        $errcodes[] = 'EMPTY_LOGIN';
  87.      }
  88.      if(strlen($_POST['login']) > $main->config['login_max_strlen']){
  89.        $errcodes[] = 'LONG_LOGIN';
  90.      }
  91.      if(strlen($_POST['login']) < $main->config['login_min_strlen']){
  92.        $errcodes[] = 'SHORT_LOGIN';
  93.      }
  94.  
  95.  
  96.      if(empty($mail))
  97.      {
  98.        $errcodes[] = 'EMPTY_MAIL';
  99.        $check_mail = false;
  100.      }
  101.      if ($check_mail && !eregi("^[_a-z0-9-]+(.[_a-z0-9-]+)*@[a-z0-9-]+(.[a-z0-9-]+)*(.[a-z]{2,4})$", $_POST['mail']))
  102.      {
  103.        $errcodes[] = 'INVALID_MAIL';
  104.      }
  105.  
  106.  
  107.      if(empty($_POST['pass'])){
  108.        $errcodes[] = 'EMPTY_PASS';
  109.      }
  110.      if(strlen($_POST['pass']) > $main->config['pass_max_strlen']){
  111.        $errcodes[] = 'LONG_PASS';
  112.      }
  113.      if(strlen($_POST['pass']) < $main->config['pass_min_strlen']){
  114.        $errcodes[] = 'SHORT_PASS';
  115.      }
  116.  
  117.  
  118.      if(count($errcodes) > 0)
  119.      {
  120.        $sql = "";
  121.      }
  122.      else
  123.      {
  124.        global $template, $lang;
  125.  
  126.        foreach ($errcodes as $key => $value)
  127.        {
  128.          /*switch ($value)
  129.           {
  130.             case 'EMPTY_LOGIN':
  131.               $value2 = $lang->register['EMPTY_LOGIN'];
  132.             break;
  133.  
  134.             case 'LONG_LOGIN':
  135.               $value2 = $lang->register['LONG_LOGIN'];
  136.             break;
  137.  
  138.             case 'SHORT_LOGIN':
  139.               $value2 = $lang->register['SHORT_LOGIN'];
  140.             break;
  141.  
  142.             case 'INVALID_MAIL':
  143.               $value2 = $lang->register['INVALID_MAIL'];
  144.             break;
  145.  
  146.             case 'EMPTY_MAIL':
  147.               $value2 = $lang->register['EMPTY_MAIL'];
  148.             break;
  149.  
  150.             case 'EMPTY_PASS':
  151.               $value2 = $lang->register['EMPTY_PASS'];
  152.             break;
  153.  
  154.             case 'LONG_PASS':
  155.               $value2 = $lang->register['LONG_PASS'];
  156.             break;
  157.  
  158.             case 'SHORT_PASS':
  159.               $value2 = $lang->register['SHORT_PASS'];
  160.             break;
  161.  
  162.             default:
  163.               $value2 = $lang->register['DEFAULT'];
  164.             break;
  165.           }
  166.           $errmsgs[$key] = $value2; */
  167.          $errmsgs[$key] = $lang->register[$value];
  168.        }
  169.  
  170.        foreach ($errmsgs as $key => $value)
  171.        {
  172.          $template->assign_block_vars('registration_errors',array(
  173.            'ERROR' => $value
  174.          ));
  175.        }
  176.      }
  177.    }
  178.  
  179.    function generate_sid()
  180.    {
  181.      $chars  = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890';
  182.      $number = 40;
  183.      $strlen = strlen($chars)-1;
  184.  
  185.      $i   = 0;
  186.      $sid = '';
  187.  
  188.      while ($i < $number)
  189.      {
  190.        $id   = rand(0,$strlen);
  191.        $sid .= mb_substr($chars,$id,1);
  192.        $i++;
  193.      }
  194.      return $sid;
  195.    }
  196.  }
  197. ?>

2 pytania:
  1. Bezpieczne?
  2. Optymalne?
Mize
Klasa User, u mnie jest modelem, a do obsługi mechanizmu sesji mam osobna klase dfSession.
Warto to rozdzielić, również u Ciebie.
Poza tym, jeśli masz dostęp do PHP5 to warto by napisać to właśnie pod tą wersje.
infoo1
Cytat
Klasa User, u mnie jest modelem, a do obsługi mechanizmu sesji mam osobna klase dfSession.
Zrobię tak.
Cytat
Poza tym, jeśli masz dostęp do PHP5 to warto by napisać to właśnie pod tą wersje.
Mam. Co tam jest nie z PHP5?
Mize
Dużo by pisać.

http://www.google.pl/search?q=php5
http://php.net.pl/manual/pl/index.php

Ale pierwsze co się rzuca w oczy to brak konstruktora i destruktora, a poza tym...

  1. <?php
  2. var $userdata = array();
  3.  
  4. /* Powinno byc raczej... */
  5.  
  6. private $userdata; */
  7. ?>


Oraz....

  1. <?php
  2. function login();
  3.  
  4. /* Powinno byc. */
  5.  
  6. public function login();
  7. ?>


Co do budowania modelu to raczej warto zrobić to troszkę inaczej.

  1. <?php
  2. /* Nie jeden kontener. */
  3.  
  4. private $userdata;
  5.  
  6. /* Tylko rozdzielić to na poszczegolne własnosci. */
  7.  
  8. private $Name;
  9.  
  10. private $ID;
  11.  
  12. private $IP;
  13.  
  14. /* I dostęp do nich poprzez metody. */
  15.  
  16. $User->getName();
  17. ?>


Jest to wygodne w zastosowaniu razem z obiektem do obsługi sesji.

  1. <?php
  2. $UserID = $Session->Get('userID');
  3. $User = new User($UserID);
  4.  
  5. /* Albo. */
  6.  
  7. $UserID = $Session->Get('userID');
  8. $User = new User;
  9. $User->loadByID($UserID);
  10. ?>


Powinieneś się połapać jak to zaimplementować. smile.gif

Pozdrawiam.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.