Какова наилучшая практика, чтобы обезопасить логин пользователя от грубой силы в PHP?
Мне нравится идея использовать recaptcha для предотвращения автоматического входа в систему. Это приведет к высоким затратам для атакующего. Также злоумышленник не может делать учетные записи пользователей DoS, умышленно используя неправильный пароль.
Это достаточно защиты? Рекомендуется ли в любом случае добавить блок «х минут» после «у попыток»? Существуют ли варианты защиты входа в систему?
Я знаю о 2FA *. Но это должна быть дополнительная функция, и я хочу защитить пользователей, которые тоже не используют 2FA
* двухфакторная аутентификация
Прогрессивные задержки обычно лучший компромисс между безопасностью и удобством использования.
В дирижабль, мы внедрили прогрессивную задержку Вот (соответствующий конфиг) когда неудачные попытки из определенной IP-подсети или в отношении конкретной учетной записи пользователя увеличат время, которое они должны ждать перед последовательными попытками.
Если вы ищете повторно используемый код для получения подсети с IP-адреса:
<?php
declare(strict_types=1);
class StackOverflowCopyPaste
{
/** @var int $v4MaskBits */
private $v4MaskBits;
/** @var int $v6MaskBits */
private $v6MaskBits;
/**
* @param int $v4MaskBits
* @param int $v6MaskBits
*/
public function __construct(int $v4MaskBits = 24, int $v6MaskBits = 48)
{
$this->v4MaskBits = $v4MaskBits;
$this->v6MaskBits = $v6MaskBits;
}
/**
* Return the given subnet for an IP and the configured mask bits
*
* Determine if the IP is an IPv4 or IPv6 address, then pass to the correct
* method for handling that specific type.
*
* @param string $ip
* @return string
*/
public function getSubnet(string $ip): string
{
if (\preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip)) {
return $this->getIPv4Subnet(
$ip,
(int) ($this->v4MaskBits ?? 32)
);
}
return $this->getIPv6Subnet(
$ip,
(int) ($this->v6MaskBits ?? 128)
);
}
/**
* Return the given subnet for an IPv4 address and mask bits
*
* @param string $ip
* @param int $maskBits
* @return string
*/
public function getIPv4Subnet(string $ip, int $maskBits = 32): string
{
$binary = \inet_pton($ip);
for ($i = 32; $i > $maskBits; $i -= 8) {
$j = \intdiv($i, 8) - 1;
$k = (int) \min(8, $i - $maskBits);
$mask = (0xff - ((1 << $k) - 1));
$int = \unpack('C', $binary[$j]);
$binary[$j] = \pack('C', $int[1] & $mask);
}
return \inet_ntop($binary).'/'.$maskBits;
}
/**
* Return the given subnet for an IPv6 address and mask bits
*
* @param string $ip
* @param int $maskBits
* @return string
*/
public function getIPv6Subnet(string $ip, int $maskBits = 48): string
{
$binary = \inet_pton($ip);
for ($i = 128; $i > $maskBits; $i -= 8) {
$j = \intdiv($i, 8) - 1;
$k = (int) \min(8, $i - $maskBits);
$mask = (0xff - ((1 << $k) - 1));
$int = \unpack('C', $binary[$j]);
$binary[$j] = \pack('C', $int[1] & $mask);
}
return \inet_ntop($binary).'/'.$maskBits;
}
}
Демо доступно на 3v4l.
Допустим, вы контролируете весь /24
и отправил 10 неудачных попыток 192.168.0.1
, Ваша задержка увеличится до максимума (30 секунд).
Если вы заблокировали только IP-адреса, вы можете отправить еще один запрос от 192.168.0.2
и не откладывайте. блокировка 192.168.0.0/24
не будет такой же слабости.
Эта проблема сильно усугубляется, если учесть, что выделения IPv6 обычно предоставляют /48
или же /64
подсети вместо одного IP, так что вы можете теоретически записать от 2 ^ 64 до 2 80 адресов на атаку методом грубой силы, прежде чем вам придется страдать от ограничения скорости.
Таким образом, чтобы обойти эти проблемы, рассматривая всю подсеть (которая настраивается; по умолчанию: /24
для IPv4, /48
для IPv6), поскольку тот же источник более устойчив к этим атакам. Поскольку задержки являются просто неудобством, пользователи с законными учетными данными никогда не будут по-настоящему заблокированы в своей учетной записи.
Других решений пока нет …