Пользователь
0,0
рейтинг
31 августа 2007 в 23:11

Разработка → Безопасный метод авторизации на PHP

PHP*
Примечание: мини-статья написана для новичков

Давайте посмотрим вокруг: форумы, интернет магазины, гостевые книги и т.д. используют регистрацию и последующую авторизацию пользователей. Можно даже сказать, что это почти необходимая функция каждого сайта (только если это не домашняя страничка Васи Пупкина или не визитная карточка, какой-нибудь небольшой компании). Сегодня я хочу поделиться со всеми новичками информацией, о том, как лучше это все реализовать.


1. Модель (клиент)
Регистрация
— логин (a-z0-9)
— пароль
Вход
— логин
— пароль
Cookie
— уникальный идентификатор юзера
— хэш

Модель (сервер)
MySQL
 Таблица users
   user_id (int(11))
   user_login (Varchar(30))
   user_password (varchar(32))
   user_hash (varchar(32))
   user_ip (int(10)) по умолчанию 0

При регистрации в базу данных записываеться логин пользователя и пароль(в двойном md5 шифровании)

При авторизация, сравниваеться логин и пароль, если они верны, то генерируеться случайная строка, которая хешируеться и добавляеться в БД в строку user_hash. Также записываеться IP адрес пользователя(но это у нас будет опциональным, так как кто-то сидит через Proxy, а у кого-то IP динамический… тут уже пользователь сам будет выбирать безопасность или удобство). В куки пользователя мы записываем его уникальный индетификатор и сгенерированный hash.

Почему надо хранить в куках хеш случайно сгенерированной строки, а не хеш пароля?
1. Из-за невнимательности программиста, во всей системе могут быть дырки, воспользовавшийсь этими дырками, злоумышленик может вытащить хеш пароля из БД и подставить его в свои куки, тем самым получить доступ к закрытым данным. В нашем же случае, двойной хеш пароля не чем не сможет помочь хакеру, так как расшифровать он его не сможет(теоретически это возможно, но на это он потратит не один месяц, а может быть и год) а воспользоваться этим хешем ему негде, ведь у нас при авторизации свой уникальный хеш прикрепленный к IP пользователя.

2. Если злоумышленик вытащит трояном у пользователя уникальный хеш, воспользовать им он также не сможет(разве если только, пользователь решил принебречь своей безопастностью и выключил привязку к IP при авторизации).

2. Практика
-- <br>
-- Структура таблицы `users` <br>
-- <br>
CREATE TABLE `users` ( <br>
`user_id` int(11) unsigned NOT NULL auto_increment, <br>
`user_login` varchar(30) NOT NULL, <br>
`user_password` varchar(32) NOT NULL, <br>
`user_hash` varchar(32) NOT NULL, <br>
`user_ip` int(10) unsigned NOT NULL default '0', <br>
PRIMARY KEY (`user_id`) <br>
) ENGINE=MyISAM DEFAULT CHARSET=cp1251 AUTO_INCREMENT=1 ; <br>


register.php

<?

// Страница регситрации нового пользователя


# Соединямся с БД

mysql_connect("localhost""myhost""myhost");

mysql_select_db("testtable");



if(isset(
$_POST['submit']))

{

    
$err = array();


    
# проверям логин

    
if(!preg_match("/^[a-zA-Z0-9]+$/",$_POST['login']))

    {

        
$err[] = "Логин может состоять только из букв английского алфавита и цифр";

    }

    

    if(
strlen($_POST['login']) < or strlen($_POST['login']) > 30)

    {

        
$err[] = "Логин должен быть не меньше 3-х символов и не больше 30";

    }

    

    
# проверяем, не сущестует ли пользователя с таким именем

    
$query mysql_query("SELECT COUNT(user_id) FROM users WHERE user_login='".mysql_real_escape_string($_POST['login'])."'");

    if(
mysql_result($query0) > 0)

    {

        
$err[] = "Пользователь с таким логином уже существует в базе данных";

    }

    

    
# Если нет ошибок, то добавляем в БД нового пользователя

    
if(count($err) == 0)

    {

        

        
$login $_POST['login'];

        

        
# Убераем лишние пробелы и делаем двойное шифрование

        
$password md5(md5(trim($_POST['password'])));

        

        
mysql_query("INSERT INTO users SET user_login='".$login."', user_password='".$password."'");

        
header("Location: login.php"); exit();

    }

    else

    {

        print 
"<b>При регистрации произошли следующие ошибки:</b><br>";

        foreach(
$err AS $error)

        {

            print 
$error."<br>";

        }

    }

}

?>




<form method="POST">

Логин <input name="login" type="text"><br>

Пароль <input name="password" type="password"><br>

<input name="submit" type="submit" value="Зарегистрироваться">

</form>



login.php

<?

// Страница авторизации



# Функция для генерации случайной строки

function generateCode($length=6) {

    
$chars "abcdefghijklmnopqrstuvwxyzABCDEFGHI JKLMNOPRQSTUVWXYZ0123456789";

    
$code "";

    
$clen strlen($chars) - 1;  
    
while (strlen($code) < $length) {

            
$code .= $chars[mt_rand(0,$clen)];  
    
}

    return 
$code;

}



# Соединямся с БД

mysql_connect("localhost""myhost""myhost");

mysql_select_db("testtable");


if(isset(
$_POST['submit']))

{

    
# Вытаскиваем из БД запись, у которой логин равняеться введенному

    
$query mysql_query("SELECT user_id, user_password FROM users WHERE user_login='".mysql_real_escape_string($_POST['login'])."' LIMIT 1");

    
$data mysql_fetch_assoc($query);

    

    
# Соавниваем пароли

    
if($data['user_password'] === md5(md5($_POST['password'])))

    {

        
# Генерируем случайное число и шифруем его

        
$hash md5(generateCode(10));

            

        if(!@
$_POST['not_attach_ip'])

        {

            
# Если пользователя выбрал привязку к IP

            # Переводим IP в строку

            
$insip ", user_ip=INET_ATON('".$_SERVER['REMOTE_ADDR']."')";

        }

        

        
# Записываем в БД новый хеш авторизации и IP

        
mysql_query("UPDATE users SET user_hash='".$hash."' ".$insip." WHERE user_id='".$data['user_id']."'");

        

        
# Ставим куки

        
setcookie("id"$data['user_id'], time()+60*60*24*30);

        
setcookie("hash"$hashtime()+60*60*24*30);

        

        
# Переадресовываем браузер на страницу проверки нашего скрипта

        
header("Location: check.php"); exit();

    }

    else

    {

        print 
"Вы ввели неправильный логин/пароль";

    }

}

?>

<form method="POST">

Логин <input name="login" type="text"><br>

Пароль <input name="password" type="password"><br>

Не прикреплять к IP(не безопасно) <input type="checkbox" name="not_attach_ip"><br>

<input name="submit" type="submit" value="Войти">

</form>



check.php

<?

// Скрипт проверки


# Соединямся с БД

mysql_connect("localhost""myhost""myhost");

mysql_select_db("testtable");


if (isset(
$_COOKIE['id']) and isset($_COOKIE['hash']))

{   

    
$query mysql_query("SELECT *,INET_NTOA(user_ip) FROM users WHERE user_id = '".intval($_COOKIE['id'])."' LIMIT 1");

    
$userdata mysql_fetch_assoc($query);


    if((
$userdata['user_hash'] !== $_COOKIE['hash']) or ($userdata['user_id'] !== $_COOKIE['id'])<br> or (($userdata['user_ip'] !== $_SERVER['REMOTE_ADDR'])  and ($userdata['user_ip'] !== "0")))

    {

        
setcookie("id"""time() - 3600*24*30*12"/");

        
setcookie("hash"""time() - 3600*24*30*12"/");

        print 
"Хм, что-то не получилось";

    }

    else

    {

        print 
"Привет, ".$userdata['user_login'].". Всё работает!";

    }

}

else

{

    print 
"Включите куки";

}

?>



Для защиты формы логина от перебора, можно использовать <a href=«captcha.ru target=»_blank">капчу.

Хочу отметить, что здесь я рассматривал авторизацию основоную на cookies, не стоит в комментариях кричать, что сессии лучше/удобнее и т.д. Спасибо.
Алексей @jiexaspb
карма
2,0
рейтинг 0,0

Похожие публикации

Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (229)

  • +1
    Хорошая статья, надеюсь вокруг станет меньше "неправильных" login-форм.

    1. mysql_select_db("testtable"); // нелогичное название базы, testdb лучше
    2. Нужно использовать mysql_real_escape_string()
    3. Убирать лишние пробелы нет смысла, так как их не пропустит вот эта строка:
    if(!preg_match("/^[a-zA-Z0-9]+$/",$_POST['login']))
    • 0
      1. ок :)
      2. Впринципе, меня всегда полностью устраевала mysql_escape_string. Но на будущее учту.
      Функция идентична mysql_real_escape_string(), исключая то, что mysql_real_escape_string() принимает параметром ещё и указатель на соединение и экранирует в зависимости от кодировки. mysql_escape_string() не делает этого и результат работы не зависит от кодировки, в который вы работаете с БД.

      3. Да, я ещё когда писал код, хотел убрать trim, но потом забыл. Исправлю.
      • +1
        > указатель на соединение и экранирует в зависимости от кодировки.

        В пренебрежении управления кодировками большая проблема. Вы никогда не переносили базы из MySQL 4.0 в MySQL 4.1 и старше? Знаете, что бывает, если не задавать кодировку, в которой данные записываете в базу и в которой ожидаете обратно? Правильно, CP1251/koi8-r или любая другая кодировка интерпретируется как latin1 и все русские символы преобразовываются в "?".
        • 0
          Переносил, использовал для этого http://sypex.net/ и всё было нормально.
        • 0
          Если не сложно, укажите ссылки на решение этой проблемы.
          Проблема не только с записью данных в базу, но и с поиском русского текста в ней.
          Заранее спасибо.
          • 0
            В /etc/my.cnf я написал (это не мешает работе баз на других языках, если с ними уже всё в порядке):

            [server]
            default-character-set = cp1251
            [client]
            default-character-set = cp1251

            А в сами скрипты после каждого mysql_connect() нужно добавить:
            mysql_query("set names cp1251;");

            Скажу честно, я не специалист MySQL и возможно существуют решения покрасивее, но это единственное решение (из тех что я пробовал), которое работает для всех баз и скриптов.

            Да, кстати, никто не расскажет, как всё-таки правильно создавать базу в нужной кодировке и работать с ней в MySQL 4.1+? Например, я хочу отдавать данные в базу в UTF-8 и принимать тоже в UTF-8. Как это сделать наиболее правильно с точки зрения работы с MySQL?
      • –1
        4. # Убераем лишние пробелы и делаем двойное шифрование
        $password = md5(md5(trim($_POST['password'])));

        Опасно как-то пароль изменять, пользователь пусть лучше два раза пароль вводит при регистрации.
        • +2
          Это уже со стороны юзабилити. Но я не когда не слышал, чтобы использовали пробелы в начале или конце пароля. Пробелы могут появиться к примеру, если у человека длинный пароль в виде "jox8fwJamUaX72" и он его сохраняет на ПК в текстовом документе. А при копировании его случайно копирует пробел, который после пароля или до пароля. Разные ситуации бывают.
          • 0
            Я так делаю уже больше двух лет. Выделяю даблкликом :)
            • 0
              далеко не все такие опытные :)
            • +1
              Небезопасно.
              Почему бы не использовать какой-нибудь KeePass, он и пароли хранит зашифровано, и копи-пастить правильно сам умеет (:
            • 0
              Даблклик имеет свойство захватывать пробел, следующий за словом. Очень от софта зависит.
              • 0
                xterm, mrxvt... работает нормально.
          • 0
            Я иногда в качестве «хоть какого-то» пароля использую два пробела.
      • 0
        >Хочу отметить, что здесь я рассматривал авторизацию основоную на cookies,

        основоную?

        >не стоит в комментариях кричать, что сессии лучше/удобнее и т.д. Спасибо.

        Вообще-то браузеры без кук почти не используются (насчет lynx я не уверен). Правда, если речь идет о мобильных сайтах (для WAP, PDA), то там куки могут и не работать, поэтому там лучше ИД сессий, тем более, что их индексация роли не играет.
        • 0
          lynx поддерживает cookies.
      • 0
        Убирать пробелы по краям, ИМХО, стоит. Юзер может спокойно копировать свой логин/пароль откуда-нибудь и благополучно зацепить в наследство этих самых пробелов. В то же время непробельная часть логина/пароля будет верной. Так зачем же мучить пользователя и тыкать его носом в повисшие пробелы?
      • 0
    • 0
      И ещё не зыбать про гадость типа magic_quotes, а то начнут плодиться слеши.
  • 0
    Спасибо большое за статью. Я недавно изучаю PHP, но свою форму регистрации уже написал. Благодоря вашей статье буду её перерабатывать. Ещё раз спасибо.
  • 0
    Спасибо. Как раз сейчас пишу форму. Спасибо за напоминание. Плюс в карму однозначно.
  • 0
    Всё, что отдайт MD5 заведомо имеет длину 32 байта ( 16 если точнее, но это частности )

    Поэтому поля pass и hash необходимо из varchar(32) перевести в char(32)
    • +3
      Так в таблице присутствуют другие столбцы varchar, то изменять тип одного из них на char смысла не имеет - БД все равно создаст столбец с типом varchar. Т.е. в одной таблице нельзя смешивать varchar и char - так было в MyISAM, не уверен насчет InnoDB.
      • 0
        в ИнноДБ можно
  • –1
    Зря, вы, господин jiexaspb, взялись за обучение новичков не разобравшись в деле досконально.

    Потом с сайтов этих новичков пароли будут тырить только в путь.

    Пока что более оптимального варианта с хранинем сессии для каждого аккаунта не придумали.
    • 0
      Можно узнать, чем данный метод хуже сессии?
      • 0
        Данный метод лучше =) Но только если он реализован нормально.
        А сессии это лузерство.
        • 0
          Не могли бы вы мне постучать в ICQ 863898, написав теоритически все основные недостатки, чтобы я переписал статью, описав в ней идеальный метод.
          • –1
            Не мог бы =)
            Во-первых, я не очень общительный и новые контакты завожу неохотно.
            Во-вторых, логику я описал уже внизу, дальше фантазируйте сами. Perl я не знал, да ещё и забыл.
            В-третьих, есть отличная статья.

            А лучше данный метод тем, что меньше соблазнов делать абсолютно немасштабируемое приложение.
            • 0
              > Во-вторых, логику я описал уже внизу, дальше фантазируйте сами. Perl я не знал, да ещё и забыл.

              это не Perl, а PHP
              • 0
                А, ну тогда всё ещё хуже =) На PHP я вообще ни строчки кода не написал =)
            • 0
              Отличная статья по указанному урлу не открывается. Поправьте плиз, отличные статьи очень нужны
  • 0
    отличная статья, спасибо
    заплюсовала, карму подняла
    но единственная просьба - посвятите, пожалуйста, 15 минут своего времени изучению правил написания "ться" и "тся" в глаголах, оно несложное.
    очень глаз режет :-(
    • 0
      Правило знаю, только это моя первая статья, поэтому при изложение мысли, трудновато соблюдать орфографию. Буду стараться исправить ситуацию.
      • 0
        Глаз в самом деле режет. Займитесь, пожалуйста.
  • 0

    Из-за невнимательности программиста, во всей системе могут быть дырки, воспользовавшийсь этими дырками, злоумышленик может вытащить хеш пароля из БД и подставить его в свои куки, тем самым получить доступ к закрытым данным.


    смешно. если есть доступ к базе, то в неё можно и пароль новый записать, а потом и войти и сделать что угодно.

    Также двойной ли хеш он, тройной ли - неважно. От этого сложность и скорость расшифровки хеша не будет зависеть.

    Также как вы будете вспоминать пользователя? По привязке к случайной строке? Мало..мало информации вы привязали о конкретном пользователе в куку. Туда желательно ещё привязать время последнего входа.

    да и незачем создавать две куки. Это лишнее.

    Лично я применяю вспоминание пользователя по куке, где логин уже примешан.

    Также советую применять сессию для 24-ёх минутного запоминания пользователя (или пока браузер не закроет) - снижает нагрузку на БД.

    Лишнее поле в БД - хеш. Можно использовать время последнего доступа.

    Всем успехов.
    • +1
      > смешно. если есть доступ к базе, то в неё можно и пароль новый записать, а потом и войти и сделать что угодно.

      SQL injection бывают разные.


      > Также двойной ли хеш он, тройной ли - неважно. От этого сложность и скорость расшифровки хеша не будет зависеть.
      "Time-space tradeoff" гуглите. MD5(абсолютно_любая_строка_меньше_некоторой_длины) я расшифрую _вмиг_ с его помощью без полного перебора, а вот MD5(MD5(...)) — уже нет.
      • 0
        спасибо за поддержку
      • 0
        Интересно как ))) Если для вас это так просто можно небольшой тест? 6a050fe5329c44c53f2c7f1795b3f322
        • 0
          К сожалению, результата нет. Не могли бы вы раскрыть исходную строку? Как вы получили хеш?
          • 0
            Может из-за кириллицы? Строка была "лябози" (без ковычек). Получил простым echo md5('лябози');
            • 0
              Да, таблицы у меня без кириллицы.
              • 0
                Ну давайте без кириллицы попробуем :) 8c930a2f0660e102d9ee7a1eeda504f4
                • 0
                  "bionium" без кавычек.
                  • 0
                    Не ожидал :) круто. Сам писал или готовое решение нашёл? Думаю если бы присутствовали цифры и знаки пунктуации врядли бы нашлась отгадка но всё равно уважуха :)
                    • 0
                      http://en.wikipedia.org/wiki/Rainbow_table — что это такое
                      http://rainbowtables.shmoo.com/ — скачать исходные коды программ

                      Ответ можно найти всегда (99.9%), нужно только сгенерировать таблицы с нужными параметрами. Только вот таблицы для длинных строк или больших наборов символов будут занимать уж очень много места.
                      • 0
                        Спасибо :)
    • 0
      md5(md5($pass)) - можно расшифровать, но сложно
      md5(md5($pass.$addkey)) - еще сложнее (где $addkey = const)
      Так просто, на заметку ;)
      • 0
        Чем сложнее расшифровывать md5(md5()) по сравнению с md5(), при услови что код открытый (т.е. известно как вычислялся хеш)? ведь в обоих случаях можно взять словарь паролей, вычислить по нему хеши и посмотреть есть ли совпадения.

        С addkey это уже действительно другой разговор, но как обеспечить его секретность, злоумышленник ведь может получить доступ к файлам на сервере?
        • 0
          Только тем, что в словаре врядли будут хэши для 32 разрядных паролей
          • 0
            а зачем они там, злоумышленник значет что у нас хеш md5(md5()) и им же генерирует хеши, по обычным паролям
          • 0
            Time-space tradeoff — это восстановление зашифрованной строки при помощи таблиц, которые генерируются заранее. Таблицы генерируются для определённого набора символа и длины исходного текста, отсюда и название time-space tradeoff, то есть мы время полного брутфорса размениваем на генерацию таблиц (rainbow tables).

            addkey называется соль. Если соль будет включать, например, большие и маленькие буквы и спецсимволы, то таблицы ranbow tables тоже должны быть сгенерированы для этого набора символов, хотя пусть сам пароль состоит из одних только цифр. А таблицы для больших наборов символов занимают очень много места. А для длинных строк — ещё больше, на порядки. Увеличиваем длину соли и в какой-то момент получается так, что даже 6-значный пароль из одних цифр уже не реально восстановить из хеша с солью по таблицам.
            • 0
              Спасибо за хорошую информацию =)
              А что из этого следует? )))
              • 0
                Нужно просто забыть про MD5 и SHA1, и использовать SHA-256, SHA-384, SHA-512, или RIPEMD-160 или Whirlpool, который является стандартом ISO/IEC 10118-3.
        • 0
          Вообще, рекомендую обратить внимание на сайт http://www.openwall.com/phpass/
          Автор сайта 10 лет в шифровании =) Реально интересно и полезно
      • 0
        md5($pass.$addkey.time()) также сложно.
        • 0
          и нахрен нам нужен такой хэш? который берется по рандомной величине?
  • 0
    1. >В нашем же случае, двойной хеш пароля не чем не сможет помочь хакеру
    "береженого бог бережет, думала монашка надевая презерватив на свечку".

    2. иногда лучше жевать, чем говорить. лучш не давайте советов, если не полностью разбираетесь в вопросе. и тем более не давайте в таких случаях советов новичкам.
    • 0
      иногда лучше не просто говорить, а приводить аргументы.
      • 0
        1. выше и ниже моего поста вполне достаточно аргументов.
        2. учить надо тому, в чем реально разбираешься. стиль кода и вопросы типа "чем данный способ хуже сессий" - выдают с головой.
        3. не надо учить как проще. надо учить как правильно.
        4. я вполне могу оценить уровень "статьи" и сказать что она чертовски далека до уровня http://phpfaq.ru/

        хватит?
      • 0
        long имел в виду, что такие статьи лучше писать в свой блог, а не коллективный =)
        • 0
          и лучше чтоб этот блог был в каком-нибудь труднодоступном месте :)
    • 0
      Гуглите "time-space tradeoff".
      • 0
        если иметются ввиду коллизии, то это никакого отношения к востановлению зашифрованной записи не имеет. они будут вне зависимости от уровня вложенности. иначе у нас бы получился суперархиватор. реально может спасти только добавление к исходному зашифровываемому тексту некой строки. см. общий алгоритм работы с эцп.
        • 0
          Это не коллизии. Это восстановление зашифрованной строки при помощи таблиц, которые генерируются заранее. Таблицы генерируются для определённого набора символа и длины исходного текста, отсюда и название time-space tradeoff, то есть мы время полного брутфорса размениваем на генерацию таблиц (rainbow tables).
        • 0
          >>они(коллизии) будут вне зависимости от уровня вложенности. иначе у нас бы получился суперархиватор.

          Коллизии будут обязательно, но хэш-функция может быть такой, что H(t1)=H(t2), при t1!=t2 и при t1 много меньше t2. То есть, для конкретной задачи, нам не важно, что хэш от пароля в восемь символов совпадёт с хэшем от пароля в 100 символов, ибо длина пароля в любом случае должна быть ограничена разумным числом символов. Значит мы никогда не обнаружим коллизий в конкретной реализации.
  • 0
    В вашем условии (в последнем if) закралась одна ошибка.
    Поясню.

    Вот это условие:

    if(($userdata['user_hash'] !== $_COOKIE['hash']) or ($userdata['user_id'] !== $_COOKIE['id'])
    and (($data['user_ip'] == $_SERVER['REMOTE_ADDR']) or ($data['user_ip'] == "0"))) { ....

    Порядок его вычисления следующий:
    1. проверяем условие слева от оператора AND: "$userdata['user_id'] !== $_COOKIE['id']" - предположим ID совпали, т.е. условие дает FALSE
    2. выражение справа от AND даже проверяться не будет, т.к. FALSE AND "что-то" = FALSE
    т.е. теряется всякое условие на IP!!!

    Вот так наверно правильнее:

    if(($userdata['user_hash'] !== $_COOKIE['hash']) or ($userdata['user_id'] !== $_COOKIE['id'])
    or ($data['user_ip'] !== $_SERVER['REMOTE_ADDR']) and ($data['user_ip'] !== "0")) { ....
    • 0
      Да, вы правы. Исправлю
  • –2
    Жуть.
    Автор, это не ты случаем предлагаешь на http://supercreativ.narod.ru/ обучение занедорого? =)

    Во-первых, тестил, небось, на двух записях в таблице, да? Где уникальный индекс на поле с логином? Знаешь, во сто крат надёжнее, правильнее и красивее иметь уникальный индекс, а не выделываться с лотереей "а вдруг никто не успеет между select и insert вставить запись с таким же логином". И select в функции регистрации нафиг не нужен сразу становится, а чем меньше запросов к БД, тем дольше живёшь.

    Во-вторых, что это за такой мегарулез, каждый раз лазить в базу для аутентификации? Дешевле зашифровать в куку подписанные нужные данные. Масштабировать БД существенно сложнее, чем масштабировать BL. В случае, когда кто-нибудь начнёт присылать вам в куках какой-нибудь мусор, ни один аутентифицированный пользователь не сможет зайти на сайт, потому что забьют мусором коннекшены к БД пользователей. Дешифровать небольшую строку и проверить подпись существенно быстрее, чем сделать запрос к БД.

    В-третьих, сколько уже можно писать код для SQL-инъекций? Я не верю, что DBI перестал поддерживать параметры (мегаязык Perl не использую уже лет пять). Это _единственный_ способ обезопасить себя от SQL-инъекции. А смехопанорамные проверки регэкспами и эскейпы ерунда.

    В-чётвёртых, строка $login = $_POST['login']; идёт хрен знает какой по всему коду. Я не понимаю, зачем везде использовать "$_POST['login']", это же каждый встреченный раз даёт возможность на опечатку!

    В-пятых, в-шестых, в-седьмых, ..., в-стовосемдесятдевятых - всё это есть в куче книг и статей. Ё-маё, уже в переводах есть практически любая западная литература. Почему вы бросаетесь решать какие-то мегапроблемы мегазащит от мегахакеров не умея кодировать?!

    Да ещё авторизация. Я плачу. Какая это авторизация? То, что описано выше, это аутентификация. А авторизация начнётся, когда автор сделает аналог ACL. В самом тривиальном случае авторизации Id пользователя надо хоть с чем-нибудь сравнить, чтобы понять, давать ему доступ к функциональности или не давать.
    • 0
      > Во-вторых, что это за такой мегарулез, каждый раз лазить в базу для
      > аутентификации? Дешевле зашифровать в куку подписанные нужные данные.
      > Масштабировать БД существенно сложнее, чем масштабировать BL. В случае,
      > когда кто-нибудь начнёт присылать вам в куках какой-нибудь мусор, ни
      > один аутентифицированный пользователь не сможет зайти на сайт, потому
      > что забьют мусором коннекшены к БД пользователей. Дешифровать небольшую
      > строку и проверить подпись существенно быстрее, чем сделать запрос к БД.

      А можно подробнее об этом?

      > В-третьих, сколько уже можно писать код для SQL-инъекций? Я не верю, что DBI
      > перестал поддерживать параметры (мегаязык Perl не использую уже лет пять). Это
      > _единственный_ способ обезопасить себя от SQL-инъекции. А смехопанорамные
      > проверки регэкспами и эскейпы ерунда.

      Параметры,.. это то что называют плейсхолдерами?
      • 0
        Да вот мне тоже интересно по поводу подписи данных...
        • 0
          Цифровая подпись данных в данном случае - пролетает.
          Полюбому нужно сравнивать с БД. Иначе, пользователь один раз залогинится, его удалят из БД, а он все-равно будет как бы свой
          • 0
            Ну зачем такой жесткач. Либо всё время доверять кукам, либо всё время лазить в БД.
            Совместное использование неприемлемо? Сравнивайте с БД на здоровье. Но не каждый хит, а, скажем, раз в час.

            Но Вы, вероятно, не об этом, да? Цифровая подпись может быть в виде CRC32, например. Цифовая подпись и сертификат, выданный в каком-нибудь Verisign, не синонимы.

            Задача цифровой подписи - подтвердить валидность данных. В случае с электронным письмом, например, имеет значение не только гарантия валидности данных (неизменности текста письма), но и гарантия валидности отправителя - сертификат решает обе эти задачи.
            • 0
              Спасибо за информацию! =)
              На самом деле, я понимаю, что цифровая подпись. Просто меня немного смущает, что, скажем, отрублю я человеку доступ, а он еще час (или может даже 5 минут - неважно) будет админствовать по цифровой подписи. ;)
              • 0
                Такие вещи решаются другими средствами =)
                Не давайте доступ к администрированию людям, которым не доверяете. Потому что доступ будет отрубаться уже после того, как человек напортачит и лаг в 1 час ничего не изменит =)
        • 0
          Подпись нужна, чтобы узнать, что данные не мусор.
      • 0
        Подробнее о чём? И что за подробности нужны?

        Про плейсхолдеры - это кто по что горазд. Обычно они вопросами вставляются в запросы. Вероятно, терминология в разных фреймворках различается.
        • 0
          О том, как шифровать и подписывать, если лень расказывать, дайте, пожалуйста ссылку, иил ключевые слова, по которым искать.
          • 0
            Как шифровать - по реализации не знаю, я на PHP ни строчки кода не написал.

            А алгоритм простой - сначала сериализуете данные в массив байт, добавляете подпись (например, CRC32), шифруете (например, AES), кодируете в base64 и полученную строку пишите в куку.

            Обратно, соответственно, декодируете из base64, дешифруете, разворачиваете в массив байт и подпись, проверяете валидность данных (для CRC32 заново считаете его и сравниваете со значением после дешифрования) и дальше продолжаете работу с данными, если они валидные. Или не продолжаете, если они невалидные.
            • 0
              +1 base64 ещё к тому же можно искусственно "сломать", но с возможностью обратного восстановления. Тут однако следует предотвратить возможность кражи куки.

              Вообще сей способ можно описать проще "юзеру выдается некий доверительный сертификат, содержащий все необходимые данные о нём, но который должен быть неработоспособен при искусственном перемещений в другой браузер/на другую машину".
              • 0
                Возможность кражи куки это не очень страшно. Все деструктивные действия (редактирование профиля, работа с деньгами и так далее) делаются с подтверждением пароля или ещё как-нибудь так.

                А чтобы куку нельзя было украсть навечно, можно что-нибудь придумать. Я уже не припомню, чего мы для этого делали.
    • +2
      >Я не верю, что DBI перестал поддерживать параметры (мегаязык Perl не использую уже лет пять).

      DBI не перестал. Только это не мегазяык Perl, а супермегаязык пхп :). Его и так недоучки делали, которые не поняли что и для чего было сделано в перле, но что касается работы с БД - тут они сами себя превзошли. Там отродясь для каждой БД свой собственный набор функций. А до того, чтобы передавать параметры запроса параметрами функции эти умники за 10 лет мегабурного развития языка так и не додумались. Поэтому каждый, кому выпало пхп-шное щастье либо прикручивает какое-то самодельное подобие (да, да, с помощью смехопанорамных регэкспов и эскейпов), либо... не прикручивает и все мы в курсе количества сайтов, ломаемых инъекцией :).
      • 0
        Ага, уже понял. Заголовок сообщества почему-то ушёл из внимания, а код похож на перловый. Чтобы в следующий раз не забывал, куда пишу, в карму уже наминусовали =)
      • 0
        Вообще-то для этого есть PEAR::DB
      • 0
        А еще грамотные пхпшники используют нормальные средства такие как PDO или в крайнем случае mysqli. И то и другое уже года с бородатого php5.0 есть.
  • 0
    Представьте, что я — злоумышленник.
    Что помешает мне на моем сайте получить значения id и hash из cookie, когда вы ко мне зайдете, а потом подделать cookie на своей машине и зайти королем на ваш сайт?
    Потом, как уже говорилось выше, каждый раз лазить в БД за данными об авторизации — это слишком накладно.
    Хотя, я в общем-то рад, что появляются такие статьи. Это значит, что люди учатся. Некоторые на своих ошибках, некоторые на чужих.

    Как говорится, если у человека есть желание, он ищет возможности, если нет желания, он ищет причины...
    Спасибо за то, что ищете для себя возможности.
    • 0
      А как Вы собираетесь на своём сайте получить куки прописанные для другого сайта?
      • 0
        А в предложенном коде они "прописаны" для другого сайта?
        • 0
          вообще то они автоматом прикручиваються к домену. http://ru2.php.net/setcookie
        • 0
          т.е. вы хотите сказать, что так установленные куки будут транслироваться на все сайты, на которые заходит пользователь дальше?
        • 0
          в данном коде кука ставится на текущий домен проекта, где установлены скрипты, потому что путь не указан в функции setcookie().
  • 0
    Я придумал атаку.

    Давайте представим, что злоумышленник таки украл базу. Получается, что он знает $hash вообще всех пользователей, а так как проверяется именно он, то его нужно просто подставить в cookie, а пароль даже и не нужен. Выход:
    * в базу сохранять MD5(случайная строка), а пользователю в куки отдаём эту случайную строку. Потом сверяем. Если кто-то украдёт базу, то получит только MD5 от случайных строк, которые ни перебором, ни словарём, ни rainbow tables (при условии достаточной длины) не расколоть.
    * в базу сохранять время действия логина, не надеяться на то, что куки будут затёрты автоматически
    • 0
      > то его нужно просто подставить в cookie, а пароль даже и не нужен.
      IP он тоже просто подставит в cookie? Вы забыли про прикрепление к IP
      • 0
        Её можно ведь отключить, верно?
        • 0
          Ага. Понял вашу идею.
  • +10
    Хоть сама статья и не дала мне ничего нового, комментарии набросившихся на бедного автора людей иногда было интересно читать, ценные замечания. Расстраивает только, что некоторые всеми способами норовят оскорбить, как будто этим доказывая свое превосходство и ум, как дети, ей-богу. А автору спасибо, что поднял тему.
  • 0
    Непонятно для чего так изголялся. Кто мешает использовать более стойкий хеш sha1? Можно так-же к паролю на входе перед хешированием добавлять некую secret string.
    • 0
      А как этот string сделать secret, я уже где-то на хабре предлагал генерировать нестандартный хеш или стандартный хеш от измененного пароля и меня просветили, что все, что лежит на сервере может в любой момент стать несекретным.

      Я, конечно, не спец, но пароли пользователей оригинальность обычно не отличаются и можно по хорошему словарю нагенерировать хешей (хоть sha1, хоть md5) заранее, а когда в руки попадет база, только проверить, нет ли в ней известных хешей, а для md5(md5(pass)) генерить придется слишком дофига, это немного компенсирует невозможность заставить пользователя выбирать пароль по всем правилам.
    • 0
      что-то я затупил насчет md5(md5()) подобрать его не сложнее остальных
      • 0
        Скажите, как вы подбираете md5? Я, может, что-то пропустил и появился некий универсальный быстрый декодер?=)
        • 0
          Универсального декодера, конечно, нет, но для прикладных задач, подходят вот такие http://passcracking.ru/ru/index.php сервисы с готовыми базами хешей, в том числе md5(md5(pass)), ведь у реальных пользователей простые пароли.
          • 0
            Человеческую непонятливость не перебить md5, да
            Спасибо)
  • –3
    Пока PHP программисты из Виллабаджо изобретают велосипеды, Python программисты из Вилларибо уже давно на них катаются.
    • 0
      кто на что молится...:)
    • 0
      PHP программисты тоже уже давно на них катаются... или эта статья руководство к программированию?
  • 0
    А вообще все подобные вопросы давно рассмотрены и хорошо представлены на http://phpclub.ru/ (особенно полезно почитать форум)
    • 0
      Зная, как на PHPClub'е относятся к новичкам, я бы туда только врагов отправлял...
      • 0
        Там относятся плохо ко всем, кто задает вопросы. Там PHPClub`е считают, чтов се ложны к ним приходить и рассказывать мегакреитивные идеи, о которых не кто еще не знает.
        • 0
          ооо... тогда все ясно. не признанный гений.
  • 0
    Мелочь:

    if(!preg_match("/^[a-zA-Z0-9]+$/",$_POST['login']))

    я бы заменил на

    if(!ctype_alnum($_POST['login']))

    Действовать будет быстрее, а эффект почти не изменится.
  • 0
    В последнем своем проекте использовал HTTP Аутентификацию.
    С точки зрения программиста это удобней, так как надо значительно меньше кода, но зато логин идет в попап окне, и контроля над процессом нет.
    Может ктонибудь может сказать а с точки зрения безопасности. Устойчива ли HTTP аутентификация?
    • 0
      Да, я думаю она устойчивая, только не гибкая и не удобная
    • 0
      Ну, с одной стороны, это встроенный инструмент сервера. Т.е. трудозатраты на реализацию минимальны.
      С другой стороны, как вы правильно отметили, нет контроля на процессом.
      Что же касается устойчивости, то такая аутентификация в исходном своем виде не защищает от, например, брутфорс атак. И тут уже приходится извращаться (например, вешать обработчик на 401 ошибку).
      В общем, если вам нужно закрыть мембер зону на порно сайте, то это подойдет (кому нужно, все равно взломают, что туда не прикрути). А если вы закрываете панель управления от серьезной системы, тот тут лучше не полениться написать действительно хороший алгоритм авторизации.
    • 0
      смотря какая. если basic, то не очень. пароль передаётся в открытом виде ПРИ КАЖДОМ запросе страниц.
      если digest, то чуть надёжнее - пароль шифруется в хеш-функции , да ещё есть дополнительная защита по времени и по домену.

      надёжнее всего авторизация через https-соединение, затем установка сессии, куки. и дальнейшая работа.
  • –2
    Ужасное написание кода. Про ООП когда-нить слышали?
    • 0
      Слышал. Глаза вверх поднимите, и прочитайте что написано первой строкой.
      • –2
        Нужно изначально подготавливать молодое поколение на написание нормального кода, а не, извините, этого говна.
        Элементарно - завтра я захочу поменять, например, пароль доступа к базе данных, и что - нужно будет переписывать везде пароль? Это бред. Как минимум чего не хватает в статье - это одного общего файла
        • 0
          Код этот - просто пример, а не готовый скрипт.
          Каждый его напишет так, как ему нужно.
          • –7
            даже если это и пример - вам незачот.
            • +1
              Вы просто нашли к чему придраться. Я думаю людям важнее увидить, как примерно это должно выглядеть, а не то где осуществляеться коннект к БД. Но всё равно учту на будущее.
    • 0
      Тут-же сказано для начинающих! Врятли начинающий на PHP, я подчеркиваю на PHP, будет знать о ООП.
      • 0
        Ну... не сказал бы. ИМХО сразу надо изучать объектный подход. Не обязательно его везде использовать, но читать "объектный" код надо уметь. В конце концов не так уж это и сложно...
    • +4
      Нормально написание.
      Для простого случая ООП будет только увеличивать колличество строк и усложнять логику. Тем более статья показывает как делается авторизация, метод, принцип, логика etc.
      Да и какое здесь может быть ООП, в этом пимере? Писать класс, чтобы реализовать в нем один метод? Если вы спец, сами внесете эту логику в структуру классов своего проекта.
      Не надо делать из ООП фетишь
    • 0
      ООП здесь к чему? ;) Один файл кода или четыре, к ООП никакого отношения не имеет =)
  • 0
    Я бы $hash = md5(microtime().getmypid());
    • 0
      или md5(uniqid(rand(),true));
      • 0
        Нет (простой) возможности выбрать длину строки.
        • 0
          Поясните, в чем состоит проблема, если речь идет о хэше.
          • 0
            Человека А устраивает, чтобы аргументом для MD5 была строка в 12 симовлов, состоящая только из цифр. Человек Б хочет, чтобы там была строка [a-zA-Z0-9] из 64 символов.

            Почему эта строка должна быть достаточно длинной, см. http://www.habrahabr.ru/blog/php/24389.html#comment345781
            • 0
              А человек С хочет, чтобы хэш отвечал условию уникальности. Об этом и был мой комментарий.
              • –1
                А объясните мне, какой смысл в уникальности?
    • 0
      А смысл??? ))) Хэш мы получаем, чтобы сверять с БД.
      Можно вообще тогда получать $hash=md5(rand(0,10000))
      =)
  • 0
    спасибо за статью:)
  • –1
    Ошибок-то в комментариях — мама дорогая.
  • 0
    спасибо огромное!
    как раз мне сейчас это требуется!
  • 0
    Спасибо за статью!
    Комментарии наводят на мысли в верном направлении. Без автора не было бы и комментариев.
  • 0
    после того как попробуете статью переходите изучайте PEAR:Auth
  • 0
    спасибо за статью,
    Немного не в тему вопрос, а почему от перебора нужна капча, можно ведь просто ограничить количество попыток (например, 10 достаточно в любом нормальном случае), а потом 5 минут не принимать, пароль с этого IP?
    • 0
      Не однократно, сам забывал пароль, и весьма напрягает когда на 15 минут тебя отрубает, а восстанавливать и ждать на мыло пароля может быть нет желания
      • 0
        Если каждый раз нужно вводить капчу то проще восстановить пароль,
        а 10 вполне достаточно, чтобы вспомнить
        • 0
          после пяти неудачных лучше что бы появилась картинка (но человеческая, а не где глаз ломаешь), чем ты полностью будешь заблокирован на 15 минут.
          • 0
            отличный вариант
    • 0
      ну а если там большой список прокси, который меняться через каждые 5 паролей?
      • 0
        очень просто отследить, при неудачном входе запоминаем логин и ip и в течении 5 минут не пускаем с других ip
        • 0
          Вот и способ организовать DoS:
          1. выбираем жертву
          2. постоянно делаем неударные попытки входа со своего IP
          3. жертва не может зайти со своего IP
          • 0
            блин :)

            Какие есть предложения, имхо, от капчи избавиться необходимо, это удар по юзабилити, лишние телодвижения.

            Выше был предложен вариант показывать капчу после нескольких неудачных попыток, а через час можно снова пускать без капчи.
  • 0
    для дезинформарования злоумышленников можно использовать вместо md5() такой код:


    function md_5($what){
    return substr(sha1($what),0,32);
    }
    • 0
      Раньше я использовал,что-то похожее, но это абсолютно бесполезно, потому что злоумышленник может получить доступ к коду и тогда, никаких проблем с подбором паролей у него не будет, хотя в определенной ситуации это может быть полезно
      • 0
        если злоумышленник получает доступ к бд, то он будет думать, что такая строка хеш md5, и любые попытки взлома ему не помогут.

        имхо, доступ к коду, в большинстве случаев, можно получить, если существует недостаточная фильтрация входных данных
        • 0
          я признаю, что в некоторых случаях это будет полезно

          но как защищать систему с открытым кодом

          неужели вся надежда, на то, что удасться недопустить злоумышленника к базе
          • 0
            скажем, можно фильтровать сложные sql-запросы (с union, множественным селектом) на уровне класса работы с БД
  • 0
    Хорошая статья. Только я использую session_id(); - куки глючная штука.
    Хранение в двойном md5 - тоже неплохая идея. Только нужно понимать.
    Если человек имеет доступ к БД (чужой человек). И от туда узнает ваш md5 - пароля. Ему легче поменять на свой пароль. :) Наделать глупостей. А потом вернуть ваш старый. Так-что двойном md5 - не спасёт.
  • 0
    Хорошая статья. Запомнил себе. Буду применять подобное решение вместе с сессиями.
    За http://captcha.ru/ - отдельное спасибо.
  • 0
    Не плохо, но растянуто =) чет как-то шибко много кода.
    setcookie("id", "", time() - 3600*24*30*12, "/");
    =) кука убьется обычным setcookie("id", "");
    • –2
      куки удалится только в том случае, если время создания её будет в прошлом. поэтому нужно писать не только time() - время_жизни_куки, но и time() - (время_жизни_куки+1).
      • 0
        учите матчасть. Достаточно обнулить куку, любой бразуер подобную пустоту тут же прибьет.
        не верите мне - поставте себе FF, установите плаг Tamper Data и глянте что в заголовках отдаст web сервер, который получает от php setcookie с пустым значением.

        P.S. даже если представить, что вы правы, какого черта мне заставлять машину считать какое-то время, если явно 0 меньше времени создания куки? Т.е. зачем городить time()-3600*24*31 и прочее, когда в вашем случае, если уж вас не устраивает пустое значение, достаточно поставить 0?
        бред.
        • 0
          цитирую учебник

          "When deleting a cookie you should assure that the expiration date is in the past, to trigger the removal mechanism in your browser"

          и это не голословное утверждение. можно не то что представлять, что я прав, а просто попробовать, перед тем, как утврждать что-то, да ещё и судить.

          успехов вам.
          • 0
            Во-первых, проверяйте сами. 5 лет делаю веб и всегда именно так тру куки, никогда небыло проблем. Во-вторых, цитата из учебника говорит о одном из способ удаления куки — установки даты expiries в прошедшем (хочу заетить, при этом достаточно поставить дату 0 или time()-1, если вам так нравится функция time(), но никак не то, что вы предлогаете).
            Ну и на последок, помните, все что пишут учебники — это теория. На практике часто web сервера и клиентские браузеры ведут себя по разному. И не факт, что то, что написано в чебнике, будет работать у всех поголовно пользователей.
            • 0
              да при чём тут учебник. на днях у меня кука не удалялась ни в какую путём установки её просто в пустоту. потом попробовал ставить -время. ни в какую. удалилась только пр и установки времени в -(время+1). завтра попробую поэкспериментировать ещё разок.
              • 0
                =))) слушай, ты сам свой учебник процитировал. Есть лишь 3 объяснения вашему неудачному опыту с пустой кукой. Первое — у вас экзотический браузер, который игнорирует заголовки сервера. Второе — у вас был экзотический web сервер, который игнорирует то, что ем шлет php. Третье — все это вы выдумали.
                Я в силу своей профессии использовал кучу браузеров и изюзал кучу хостов, в данный момент у меня свой сервак. и никогда с этим небыло проблем. Поэтому я сколняюсь более к третьему объяснению.
                • +1
                  проверил все варианты.

                  итог
                  кука удаляется при условиях, что:
                  1.пустое значение куки или FALSE.
                  2.остальные параметры те же самые, что и при установке куки (то есть путь, домен). время может быть любым.

                  просто вы не ставили у куки путь при установке, поэтому при удалении можно было путь и не указывать - она отлично удаляется, проверил.
                  • 0
                    немного не верно. в случае setcookie('id', '') удалится для всех путей на данном домене. Наконец-то вы предпочли практику теории =) ура. Плюс вам.
                    • 0
                      Вы писали, что "в случае setcookie('id', '') удалится для всех путей на данном домене". Отвечаю - не удалится.

                      Повторяю более подробно.

                      Удалятся только те куки, которые были установлены без путей. Если же кука была поставлена с путём, то при удалении её без указания пути она НЕ УДАЛЯЕТСЯ. ох..

                      Успехов.
                      • 0
                        Да, соврал, если указать путь то мой способ не подходит =)
                        тем не менее проектов, которые пишут куки в конкретный путь - по пальцам пересчитать.

                        P.S. "Отвечаю - не удалится" я разве спрашивал вас о чем-то?
                        P.P.S. "Повторяю более подробно" более подробно расскажите зачем время ставить? именно это вы так упорно доказывали.
                        • 0
                          время ставить нужно, например, для галки "запомнить" при логине на сайт.
                          ну и другие варианты. запомнить некую информация на определённое время.
                          • 0
                            мы про удаление говорим. Позвольте я вас процитирую: "куки удалится только в том случае, если время создания её будет в прошлом. поэтому нужно писать не только time() - время_жизни_куки, но и time() - (время_жизни_куки+1)."
                            • +1
                              и это верно
                      • 0
                        Удалится только если эта кука была поставлена этим же скриптом, или в скриптом, находящимся в той же виртуальной папке что и текущий.
  • 0
    Вот несколько (в принципе лежащих на поверхности) мыслей, обобщенных в общую тему о том, как обезопасить пользователей от использования потерянной куки/сессии, если пользователь выбрал опцию "запомнить": Persistent Login Cookie Best Practice, и комментарии с улучшениями идеи Improved Persistent Login Cookie Best Practice
    • 0
      Извините, первый раз пишу - ссылки не вставились. Вот они:
      http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice
      http://jaspan.com/improved_persistent_login_cookie_best_practice
      • 0
        Отличные статьи. Спасибо!
        • 0
          было бы ещё лучше, если бы их перевели для незнающих английский
  • НЛО прилетело и опубликовало эту надпись здесь
    • –2
      а руби рулит, дя? =D
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          ыыы =) скушай рекурсивный салат и релакс =)
    • –1
      Аффтар каммента, убей себя посредством утопления в реке.
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          Да, конечно, я ведь пишу на PHP, а PHP — говно.
  • 0
    Осталось описать безопасные методы авторизации средствами Perl, ASP, и QBasic, подключенного через CGI к апачу .)
  • 0
    > Какие есть предложения, имхо, от капчи избавиться необходимо, это удар по юзабилити, лишние телодвижения.

    Зачем избавляться? Самый ИМХО лучший вариант... зделать просто хорошо читабельным, я например себе сделал большой шрифт и генерирую не просто набор символов, а слова из словаря:) для человека это будет легко я для компа...
  • 0
    хм классная статья!Особенно хотел бы увидеть статью о переходе с учебника к практике на php или хотя бы советы
  • 0
    porca madonna putana!
  • 0
    К предыдущим комментаторам насчет безопасности строки -

    можно ведь прицепиться, к примеру, к ip+пароль связке

    т.е. делать

    md5(sha1(SERVER[REMOTE_ADDR].pass))

    и проверку на вход - что-то типа

    select * from users where hash=^^^^
    каждый раз при пользовательском обновлении страницы.

    ну или чтоб базу не грузить - в те же куки записать и по кукам проверять. Или в сессии записать.
  • 0
    Никогда не понимал подобных авторизаций...

    Надо хеш - есть md5(uniqid(rand()));

    Есть задача проверять "не украли ли куку"/действительна ли авторизация -

    session_start();

    // в куке (при дефолтных настройках пхп) появляется PHPSESSID, уже сгенеренное
    // присваиваем готовые значения

    $_SESSION['USER_PHPSESSID'] = $_COOKIE['PHPSESSID'];
    $_SESSION['USER_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];

    // айпишник "записываем" как раз на случай украденных кук
    // и проверяем так же просто...

    если разные айпи - логаут.
    если айди сессии != тому айди, что в куках - логаут.

    в таком случае не нужно что-то генерить, а тем более хранить в базе или совать и читать оттуда каждый раз(!) при вызове странички, где предполагается авторизация..А если страничек тысяча? Мемкеш можно, но зачем? :) Просто напишите функцию и вызывайте в хедере каждой страницы, где нужна проверка.

    Да, проверку на длину строки/заглавные или строчные буквы можно прямо в регулярке делать.

    И никогда не стоит делать что-то вроде md5(md5(...)); т.к. это хэш-функция, т.е. перебор будет осуществляться без особых осложнений (единственный минус - с каждым md5 увеличивается процессорное время, необходимое на генерацию хеша), просто выбирайте более продвинутые алгоритмы.
    • 0
      «И никогда не стоит делать что-то вроде md5(md5(...))»

      Почему не стоит делать md5(md5()) или лучше sha1(md5())? Ведь, теоретически, это усилит сложность подбора пусть не в два раза, но значительно. Разве нет?
      • 0
        Я же написал, это хэш-функция, а не функция шифрования строки :)
        Если вы закодировали строку как md5(), то и брутофорсить я ее буду как md5(), примерно так:

        $hash = md5('dwadad');

        for($i=a;$i<=zzzzzz;$i++) {
        if(md5($i) == $hash) {
        echo 'Found: ' . $i . "\n";
        }
        }

        А если md5(md5()), то

        ...
        if(md5(md5($i)) == $hash) { }
        ...

        вы думали, что сначала будет раскрываться первый md5, а потом второй? :)

        еще раз, это хеш-функция, как и sha1...единственная польза от увеличения (разумного) числа хеш-функций при получении хеша строки состоит в том, что будет тратиться процессорное время на генерацию хеша строки, т.е. раскодировать md5 будет быстрее по времени, чем sha1(md5())...
        • +1
          Ну так откуда злоумышленник знает, каким образом я получил хэш? :-) Если он влез в мои исходники, значит, он может и пароль к БД подсмотреть, а там уже и поменять хэш пароля на нужный проблем не составит.
          • 0
            я могу влезть в ваши исходники, если это публичный opensource и подсмотреть метод получения хешей :)

            очень часто на хостингах веб-сервер запускается под nobody и имеет права чтения на всей файлы, с которыми он работает, а вот на изменение - нет, разве что найти пароль от базы и там уже подсмотреть хеш, но менять же глупо с точки зрения "чтобы не засекли" :), поэтому придется брутофорсить.
            • 0
              а что мешает скопипастить текущий хеш, поменять, а потом вернуть "где росло"...
        • 0
          Ну и, кстати, даже если знать, сколько (N), каких и в какой последовательности хэшей взято, процессорной мощносто потребуется в N раз больше, стало быть, и времени уйдёт в два раза больше. Если не принимать в расчёт коллизии, конечно…
          • 0
            Палка о двух концах.
            С одной стороны вы оставляете меньше шансов злоумышленнику, получившему хеш, с каждым n+1, а с другой - при регистрации/авторизации ваши пользователи будут нагружать ваш сервер (точнее, вы сами будете это делать при их помощи).
            • 0
              Это понятно, поэтому я не предлагаю двадцать пять раз получать хэш :-) Но два, причём, разными алгоритмами — по-моему, в самый раз. Ну или один раз, но используя, скажем, sha256 :-)
              • 0
                я так и делаю :)

                я указывал на ошибки, а не писал "делайте все как я" :)
    • 0
      если разные айпи - логаут.
      если айди сессии != тому айди, что в куках - логаут.

      таким методом вы создаёте возможность "кикнуть" пользователя для стороннего.
      • 0
        пояснить можно что имелось в виду?
        • 0
          Если вы делаете logout когда сторонний подбирает пароль или ключ сессии, то вы выключаете самого пользователя, который всё сделал правильно. Таким образом вы даёте любому симулировать атаку для того, чтобы просто выбить (kick) пользователя из форума/магазина и пр.
        • 0
          Имелось ввиду что если злоумышленник заходит с чужими cookies — происходит логаут (удаление сессии).
          Но когда добропорядочный юзер зайдёт со своими cookies после злоумышленника — то ему опять придётся авторизовываться, ведь его сессия удалена.
          • 0
            Открыл страницу достаточно давно и не обновил страницу перед отправкой комментария =\
          • 0
            Немного не корректно написал в заглавном комментарии, в коде у меня происходит редирект на главную, сессия и кука не убиваются.

            А если есть какие-то другие идеи - поделитесь :)
  • 0
    Вопрос к автору не совсем по теме: почему вы не используете mysql_free_result()? Я всегда считал, что сами собой ресурсы БД, полученные через mysql_query(), не очищаются. Может, я просто заблуждался?
    • 0
      mysql_free_result() нуждается в вызове только в том случае, если вы всерьёз обеспокоены тем, сколько памяти используют ваши запросы к БД, возвращающие большое количество данных. Вся память, используемая для хранения этих данных автоматически очистится в конце работы скрипта.
      • 0
        Уф… :-) А то некоторые маньяки даже unset() всех исползованных переменных делают — вот я и засомневался :-)
  • 0
    При попытке авторизироватся, у меня вылазит ошибка:
    Warning: Cannot modify header information - headers already sent by (output started at /home/sleshforev/domains/genya.net.ua/public_html/aut/login.php:1) in /home/sleshforev/domains/genya.net.ua/public_html/aut/login.php on line 43

    Warning: Cannot modify header information - headers already sent by (output started at /home/sleshforev/domains/genya.net.ua/public_html/aut/login.php:1) in /home/sleshforev/domains/genya.net.ua/public_html/aut/login.php on line 44

    Warning: Cannot modify header information - headers already sent by (output started at /home/sleshforev/domains/genya.net.ua/public_html/aut/login.php:1) in /home/sleshforev/domains/genya.net.ua/public_html/aut/login.php on line 47

    Чего так...(( ?
  • 0
    Как вариант, кстати, можно не получать хэш из случайной строки, а генерировать хэш сразу :-)



    Интересно, на что уйдёт больше ресурсов на 32 рандома, или на один md5 + 10 рандомов… :-)
    • 0
      function generate_сode ($length=32)
      {
          $chars = "abcdef0123456789";
          $code = "";
          $clen = strlen($chars) - 1;

          while (strlen($code) < $length)
          {
              $code .= $chars[mt_rand(0,$clen)];
          }

          return $code;
      }
      • 0
        Можно и так :)
        $a = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        for ($i=0; $i
        • 0
          В моём варианте смысл в том, что код того же вида, что и в случае получения md5-хэша :-)
  • 0
    Не понимаю, раз уж используется двойной md5, неужели его нельзя подсолить? Хотя, тогда в таблице появится дополнительный атрибут с солью, но оно того стоит - подбор пароля станет заметно сложнее.
  • 0
    По проверке формы:
    1.Проверять логин в базе нужно только в том случае, если он прошел проверку на символы и длину. Иначе это бессмысленно. Если он не прошел по используемым символам, то его и не будет в базе.
    2. Лучше не удалять пробелы в пароле. Использование пробелов - хороший способ улучшить свой пароль.
    См. книгу "Как создать идеальный пароль" - http://www.yugzone.ru/Books/perfect_passwords.zip
  • 0
    Кстати, IE7 не удаляет куки в прошедшем времени.

    Ну и если генерить хеш для пароля, то в функции не должно участвовать никакого random, иначе как сформировать такой же хеш из пароля, который ввел пользователь для сравнения с хешем в БД.

    Идея создавать токен лично для меня тоже сомнительна. Чем не устраивает хеш пароля с именем пользователя, солью и перцем? Если сайт требует серьезной аунтификации, то достаточно выдать клиенту таблицу переменных кодов (один код на одну операцию), как это принято в платежных системах банков.
    • 0
      А как таблицу переменных кодов разумнее использовать? Можно ли осуществить такую схему.
      -используется http-авторизация.
      -В таблице юзеров поля login, password и id. Добавляем колонку salt.
      -создаём дополнительно таблицу, например тоже salt, содержащую набор переменных кодов.(3 символа?)(100 строк?)(периодически обновлять?)
      -при авторизации забираем md5 от пароля, рандомом забираем какой-либо код из таблицы salt, добавляем к хэшу и пишем в переменную PHP_AUTH_PASSWORD.
      -инсертим соль в колонку salt и работаем с какой-либо таблицей по id юзера, cверяя хэш+соль из переменной PHP_AUTH_PASSWORD c записями из таблицы паролей password+salt.
      -при следующей авторизации апдэйтим код соли в колонке salt, забираяя его рандомом из таблицы кодов.

      Такая схема имеет право на существование? Минусы, плюсы? Пользователей порядка 100, максимум до 1000 вырастет. В данный момент всё работает без соли, стоит ли усложнять?
      • 0
        Сперва надо задать вопрос. На сколько требуется серьезная и подлинная авторизация. Если у вас простой форум, то достаточно просто сравнивать логин и хеш пароля. Если платежная система или крайне ценная информация, доступ к которой должен быть ограничен, то лучше замудрить. На примере одного крупного банка. Они выдают карточку с токенами, там 100 5-7 значных чисел. Перед каждой операцией надо вводить очередное число.

        md5 легко ломается, лучше брать что-то типа sha(md5(user_email+login + salt)). Я не силен в http-авторизации, но обычную сделал бы так:

        - users_table: uid, login, hash(password), token
        - users_token_table: uid, token (int)

        вторая таблица содержит переменные коды, выданные пользователю. При регистрации пользователя заносим его первый токен в таблицу users из таблицы token.
        При авторизации удаляем токен из token и кидаем следующий в users. Если с токенами связываться лень (а это для большинства интернет-авторизаций и не надо), можно сделать проще: в users хранить token (какой-нибудь 128битовый казябрик), каждый раз после логина его менять, а в сессию и куки писать не пароль и логин (как это принято), а этот токен, который будет действовать до следующего удачного входа.
        • 0
          Спасибо, то что нужно)
  • 0
    Ошибочка вышла)
    Этот запрос

    $query = mysql_query("SELECT *,INET_NTOA(user_ip) FROM users WHERE user_id = '".intval($_COOKIE['id'])."' LIMIT 1");

    при сравнении

    if(($userdata['user_hash'] !== $_COOKIE['hash']) or ($userdata['user_id'] !== $_COOKIE['id'])
    or (($userdata['user_ip'] !== $_SERVER['REMOTE_ADDR']) and ($userdata['user_ip'] !== "0")))


    ($userdata['user_ip'] !== $_SERVER['REMOTE_ADDR'])

    $userdata['user_ip'] будет в двоичной форме, а $_SERVER['REMOTE_ADDR'] нет
    можно использовать так

    $query = mysql_query("SELECT *,INET_NTOA(user_ip) as user_ip FROM users WHERE user_id = '".intval($_COOKIE['id'])."' LIMIT 1");

    или просто выбрать все колонки которые нужны с присваиванием INET_NTOA(user_ip) as user_ip
    • 0
      спасибо, только что сам до этого дошел, потом догадался еще раз прошерстить комментарии.
  • 0
    А вот аргумент "Хм, что-то не получилось" не прокомментировали... все сделал как здесь написано, но вылезает надпись. Я так полагаю одно из условий не выполнилось. Ни фига не пойму ПОЧЕМУ? У кого нибудь была такая проблема?
    • 0
      Так проверь их все вручную - выведи на экран. Выяснишь, какое не выполнилось. тогда можно будет дальше думать, в чем дело.
      если условие не выполняется, то выводят все участвующие в нем операнды.
      лучше - с помощью var_dump()
      • 0
        Не стал заморачиваться. Сделал на сессиях, теперь все работает без проблем. Спасибо за отклик.
        • 0
          Ну, при авторизации на сессиях тоже желательно проверять IP адрес. Записывая его, соответственно, в сессию в момент авторизации.
          Правда, проверка будет проще, поскольку нужна будет только одна - без проверки на 0.
          • 0
            Согласен, но т.к. проект локальный и связи с интернетом не будет иметь, то думаю это лишнее . Ломать нет смысла, да и не кому :)
  • 0
    Неплохой метод авторизации. Вот только нашел одно неудобство: авторизуемся например в ФФ, потом открываем ИЕ и авторизуемся в нем, после этого меняется хеш авторизации в базе пользователя и соответственно слетает авторизация в ФФ.
  • 0
    Странно, что за столько времени никто не обратил внимане на то, что при проверке существования логина он искейпится, а при вставке - нет.

    Хороший код. С заложенной инъекцией.
    • 0
      см. if(!preg_match("/^[a-zA-Z0-9]+$/",$_POST['login'])) выше, ага
  • 0
    А какая разница между $a===$b и $a==$b?
    • 0
      А посмотреть документацию?
  • 0
    Почему все не перешли еще на crypt()?
  • 0
    $insip = ", user_ip=INET_ATON('".$_SERVER['REMOTE_ADDR']."')";


    Если кто-то будет авторизоваться, используя ipv6, а не ipv4, вас ждёт неприятный сюрприз в этом месте, ибо в $_SERVER['REMOTE_ADDR'] будет ip шестой версии, а функция INET_ATON не сработает и возвратит не число.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.