Как я могу ограничить попытки входа пользователя в PHP - PullRequest
53 голосов
/ 19 января 2010

Я только что читал это сообщение Полное руководство по аутентификации веб-сайтов на основе форм по предотвращению попыток быстрого входа в систему.

Рекомендация № 1: небольшая задержка, которая увеличивается с увеличением количества неудачных попыток, например:

1 неудачная попытка = без задержки
2 неудачные попытки = задержка 2 с
3 неудачные попытки = задержка 4 с
4 неудачные попытки = задержка 8 секунд
5 неудачных попыток = 16 секундная задержка
и т. д.

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

Мне любопытно, как я мог бы реализовать что-то подобное для моей системы входа в PHP?

Ответы [ 12 ]

75 голосов
/ 19 января 2010

Вы не можете просто предотвратить DoS-атаки, связав регулирование до одного IP-адреса или имени пользователя. Черт, вы даже не можете предотвратить попытки быстрого входа в систему с помощью этого метода.

Почему? Поскольку атака может охватывать несколько IP-адресов и учетных записей пользователей, чтобы обойти ваши попытки регулирования.

Я уже писал в другом месте, что в идеале вы должны отслеживать все неудачные попытки входа на сайт и связывать их с отметкой времени, возможно:

CREATE TABLE failed_logins (
    id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(16) NOT NULL,
    ip_address INT(11) UNSIGNED NOT NULL,
    attempted DATETIME NOT NULL,
    INDEX `attempted_idx` (`attempted`)
) engine=InnoDB charset=UTF8;

Краткое примечание к полю ip_address: вы можете хранить данные и извлекать данные соответственно с помощью INET_ATON () и INET_NTOA (), которые по сути равносильны преобразованию IP-адреса в целое число без знака и из него.

# example of insertion
INSERT INTO failed_logins SET username = 'example', ip_address = INET_ATON('192.168.0.1'), attempted = CURRENT_TIMESTAMP;
# example of selection
SELECT id, username, INET_NTOA(ip_address) AS ip_address, attempted;

Определите определенные пороговые значения задержки на основе общего числа неудачных входов в систему за указанный промежуток времени (15 минут в этом примере). Вы должны основывать это на статистических данных, извлеченных из вашей таблицы failed_logins, так как она будет меняться со временем в зависимости от количества пользователей и количества из них, которые смогут вспомнить (и ввести) свой пароль.


> 10 failed attempts = 1 second
> 20 failed attempts = 2 seconds
> 30 failed attempts = reCaptcha

Запросите таблицу при каждой неудачной попытке входа в систему, чтобы найти количество неудачных попыток входа в систему за определенный период времени, скажем, 15 минут:


SELECT COUNT(1) AS failed FROM failed_logins WHERE attempted > DATE_SUB(NOW(), INTERVAL 15 minute);

Если количество попыток в течение заданного периода времени превышает ваш лимит, либо принудительно дросселируйте, либо заставьте всех пользователей использовать капчу (т.е. reCaptcha), пока количество неудачных попыток за указанный период времени не станет меньше порогового значения ,

// array of throttling
$throttle = array(10 => 1, 20 => 2, 30 => 'recaptcha');

// retrieve the latest failed login attempts
$sql = 'SELECT MAX(attempted) AS attempted FROM failed_logins';
$result = mysql_query($sql);
if (mysql_affected_rows($result) > 0) {
    $row = mysql_fetch_assoc($result);

    $latest_attempt = (int) date('U', strtotime($row['attempted']));

    // get the number of failed attempts
    $sql = 'SELECT COUNT(1) AS failed FROM failed_logins WHERE attempted > DATE_SUB(NOW(), INTERVAL 15 minute)';
    $result = mysql_query($sql);
    if (mysql_affected_rows($result) > 0) {
        // get the returned row
        $row = mysql_fetch_assoc($result);
        $failed_attempts = (int) $row['failed'];

        // assume the number of failed attempts was stored in $failed_attempts
        krsort($throttle);
        foreach ($throttle as $attempts => $delay) {
            if ($failed_attempts > $attempts) {
                // we need to throttle based on delay
                if (is_numeric($delay)) {
                    $remaining_delay = time() - $latest_attempt - $delay;
                    // output remaining delay
                    echo 'You must wait ' . $remaining_delay . ' seconds before your next login attempt';
                } else {
                    // code to display recaptcha on login form goes here
                }
                break;
            }
        }        
    }
}

Использование reCaptcha при определенном пороге гарантирует, что атака с нескольких фронтов будет остановлена, и у обычных пользователей сайта не будет значительной задержки для законных неудачных попыток входа в систему.

5 голосов
/ 19 января 2010

У вас есть три основных подхода: хранить информацию о сеансе, хранить информацию о куки или хранить информацию об IP.

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

Если вы используете cookie-файлы, злоумышленник может просто отклонить cookie-файлы, в общем, это не является чем-то жизнеспособным.

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

4 голосов
/ 04 декабря 2012

Процесс входа в систему должен снизить скорость как для успешного, так и для неудачного входа. Сама попытка входа в систему никогда не должна быть быстрее, чем около 1 секунды. Если это так, перебор использует задержку, чтобы узнать, что попытка не удалась, потому что успех короче неудачи. Затем можно оценить больше комбинаций в секунду.

Количество одновременных попыток входа в систему на машину должно быть ограничено балансировщиком нагрузки. Наконец, вам просто нужно отследить, используется ли один и тот же пользователь или пароль более чем при одной попытке входа в систему. Люди не могут печатать быстрее, чем около 200 слов в минуту. Таким образом, последовательные или одновременные попытки входа в систему быстрее, чем 200 слов в минуту, с нескольких машин. Таким образом, они могут быть безопасно помещены в черный список, поскольку это не ваш клиент. Время черного списка на хост не должно превышать 1 секунду. Это никогда не доставит неудобства человеку, но разрушит его попыткой грубой силы, будь то в последовательном или параллельном режиме.

2 * 10 ^ 19 комбинаций со скоростью одна комбинация в секунду, выполняемых параллельно на 4 миллиардах отдельных IP-адресов, потребуют 158 лет для исчерпания пространства поиска. Чтобы длиться один день на пользователя против 4 миллиардов злоумышленников, вам нужен полностью случайный буквенно-цифровой пароль длиной в 9 мест как минимум. Рассмотрите возможность обучения пользователей парольным фразам длиной не менее 13 мест, комбинациями 1,7 * 10 ^ 20.

Эта задержка побудит злоумышленника украсть ваш хэш-файл пароля, а не перебор вашего сайта. Используйте утвержденные, именованные методы хеширования. Запрет всего населения интернет-IP на одну секунду ограничит эффект параллельных атак, без ущерба для человека. Наконец, если ваша система допускает более 1000 неудачных попыток входа в систему за одну секунду без какого-либо ответа на запрет систем, тогда у ваших планов безопасности есть большие проблемы для работы. Прежде всего исправьте этот автоматический ответ.

3 голосов
/ 19 января 2010

Хранить неудачные попытки в базе данных по IP. (Поскольку у вас есть система входа в систему, я полагаю, вы хорошо знаете, как это сделать.)

Очевидно, что сессии являются заманчивым методом, но кто-то действительно преданный может легко понять, что он может просто удалить свой файл cookie сеанса при неудачных попытках, чтобы полностью обойти дроссель.

При попытке войти в систему, узнать, сколько недавних (скажем, последних 15 минут) попыток входа в систему было, и время последней попытки.

$failed_attempts = 3; // for example
$latest_attempt = 1263874972; // again, for example
$delay_in_seconds = pow(2, $failed_attempts); // that's 2 to the $failed_attempts power
$remaining_delay = time() - $latest_attempt - $delay_in_seconds;
if($remaining_delay > 0) {
    echo "Wait $remaining_delay more seconds, silly!";
}
3 голосов
/ 19 января 2010
session_start();
$_SESSION['hit'] += 1; // Only Increase on Failed Attempts
$delays = array(1=>0, 2=>2, 3=>4, 4=>8, 5=>16); // Array of # of Attempts => Secs

sleep($delays[$_SESSION['hit']]); // Sleep for that Duration.

или как предложено Cyro:

sleep(2 ^ (intval($_SESSION['hit']) - 1));

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

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

Как правило, начальный код будет:

$count = get_attempts(); // Get the Number of Attempts

sleep(2 ^ (intval($count) - 1));

function get_attempts()
{
    $result = mysql_query("SELECT FROM TABLE WHERE IP=\"".$_SERVER['REMOTE_ADDR']."\"");
    if(mysql_num_rows($result) > 0)
    {
        $array = mysql_fetch_assoc($array);
        return $array['Hits'];
    }
    else
    {
        return 0;
    }
}
2 голосов
/ 20 января 2010

ИМХО, защита от DOS-атак лучше решается на уровне веб-сервера (или, возможно, даже на сетевом оборудовании), а не в вашем коде PHP.

2 голосов
/ 19 января 2010

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

Более надежный способ - хранить попытки и новые попытки в базе данных для этого конкретного ipaddress.

1 голос
/ 30 июня 2012

Короткий ответ: не делай этого.Вы не защитите себя от грубого принуждения, вы даже можете усугубить ситуацию.

Ни одно из предложенных решений не сработает.Если вы используете IP в качестве какого-либо параметра для регулирования, злоумышленник просто распространит атаку на огромное количество IP-адресов.Если вы используете сеанс (куки), злоумышленник просто удалит любые куки.Сумма всего, о чем вы можете подумать, это то, что злоумышленник не может преодолеть абсолютно ничего.

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

Это приведет к тому, что почти все ваши пользователи будут ограничены до максимального значения при попытке войти в систему. Ваш сайт будет бесполезен.Атакующий: успех.

Вы можете отложить проверку пароля примерно на 200 мс - пользователь сайта почти не заметит этого.Но перебор будет.(Опять же, он может распространяться по IP-адресам) Однако ничто из этого не защитит вас от грубого принуждения или DDoS - так как вы не можете программно.

Единственный способ сделать это - использовать инфраструктуру.

Вы должны использовать bcrypt вместо MD5 или SHA-x для хэширования ваших паролей, это сделает дешифрование ваших паролей ОЧЕНЬ труднее, если кто-то украдет вашу базу данных (потому что, я полагаю, вы находитесь на общем или управляемом хосте)

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

1 голос
/ 21 января 2010

Файлы cookie или методы, основанные на сеансах, в данном случае, конечно, бесполезны. Приложение должно проверить IP-адрес или временные метки (или обе) предыдущих попыток входа в систему.

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

Проверка временной метки имеет ту же проблему, что и выше: каждый может запретить другим входить в конкретную учетную запись, просто пытаясь несколько раз. Использование капчи вместо долгого ожидания последней попытки, вероятно, хороший обходной путь.

Единственное, что должна предотвратить система входа в систему, - это условия гонки при проверке попытки. Например, в следующем псевдокоде

$time = get_latest_attempt_timestamp($username);
$attempts = get_latest_attempt_number($username);

if (is_valid_request($time, $attempts)) {
    do_login($username, $password);
} else {
    increment_attempt_number($username);
    display_error($attempts);
}

Что произойдет, если злоумышленник отправит одновременных запросов на страницу входа? Вероятно, все запросы будут выполняться с одинаковым приоритетом, и есть вероятность, что ни один запрос не попадет в инструкцию increment_attempt_number до того, как остальные пройдут 2-ю строку. Таким образом, каждый запрос получает одно и то же значение $ time и $ попытки и выполняется. Предотвращение такого рода проблем безопасности может быть сложным для сложных приложений и включает в себя блокировку и разблокировку некоторых таблиц / строк базы данных, что, конечно, замедляет работу приложения.

1 голос
/ 19 января 2010

Как указано выше, сеансы, файлы cookie и IP-адреса не эффективны - злоумышленник может ими манипулировать.

Если вы хотите предотвратить атаки методом "грубой силы", то единственное практическое решение состоит в том, чтобы основывать количество попыток на предоставленном имени пользователя, однако учтите, что это позволяет злоумышленнику осмотреть сайт, заблокировав вход действительных пользователей.

, например

$valid=check_auth($_POST['USERNAME'],$_POST['PASSWD']);
$delay=get_delay($_POST['USERNAME'],$valid);

if (!$valid) {
   header("Location: login.php");
   exit;
}
...
function get_delay($username,$authenticated)
{
    $loginfile=SOME_BASE_DIR . md5($username);
    if (@filemtime($loginfile)<time()-8600) {
       // last login was never or over a day ago
       return 0;
    }
    $attempts=(integer)file_get_contents($loginfile);
    $delay=$attempts ? pow(2,$attempts) : 0;
    $next_value=$authenticated ? 0 : $attempts + 1;
    file_put_contents($loginfile, $next_value);
    sleep($delay); // NB this is done regardless if passwd valid
    // you might want to put in your own garbage collection here
 }

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

НТН

С

...