首先,一点背景:我正在为CodeIgniter实现一个auth + auth系统并不是什么秘密,到目前为止我赢了(可以这么说).但是我遇到了一个非常重要的挑战(大多数auth库完全错过了,但我坚持正确处理它):如何智能地处理大规模,分布式,可变用户名的暴力攻击.
我知道所有常用的技巧:
限制每个IP /主机的失败尝试次数并拒绝违规者访问(例如Fail2Ban) - 由于僵尸网络变得越来越智能,它们不再起作用
将上述内容与已知的"坏"IP /主机(例如DenyHosts)的黑名单相结合- 它依赖于僵尸网络下降为#1,它们越来越不会
IP /主机白名单与传统的身份验证相结合(对于动态IP用户而言,在大多数网站上都是无用的)
在N分钟/小时内设置失败尝试次数的站点范围限制,并在此之后限制(暂停)所有登录尝试,持续数分钟/小时(DoS攻击你的问题变为僵尸网络儿童游戏)
没有登录/密码选项的所有用户的强制性数字签名(公钥证书)或RSA硬件令牌(毫无疑问是坚如磐石的解决方案,但仅适用于封闭的专用服务)
强制执行的超强密码方案(例如> 25个带有符号的无意义字符 - 再次对临时用户来说太不切实际)
最后,CAPTCHA(在大多数情况下都可以工作,但对用户来说很烦人,对于一个坚定的,足智多谋的攻击者几乎无用)
现在,这些只是理论上可行的想法.有很多垃圾想法可以打开网站(比如琐碎的DoS攻击).我想要的是更好的东西.更好的是,我的意思是:
它必须安全(+)抵御DoS和暴力攻击,并且不会引入任何新的漏洞,这些漏洞可能会让稍微狡猾的机器人继续在雷达下运行
它必须是自动化的.如果需要人工操作员验证每次登录或监控可疑活动,那么它无法在真实场景中运行
它必须适用于主流网络使用(即可由非程序员执行的高流失率,高流量和开放式注册)
它不会妨碍用户体验到临时用户会感到烦恼或沮丧(并可能放弃网站)
它不能涉及小猫,除非它们真的是非常安全的小猫
(+)'安全',我的意思是至少和偏执的用户保密密码一样安全
所以 - 让我们听听吧!你会怎么做?你知道我没有提到过的最佳实践(哦,请你说)吗?我承认我对自己有所了解(结合3和4的想法),但我会让真正的专家在让自己尴尬之前说话;-)
好吧,足够拖延; 这是我到目前为止所提出的
(对不起,前面很长的帖子.勇敢,朋友,旅途将是值得的)
将原始帖子中的方法3和4组合成一种"模糊"或动态白名单,然后 - 这就是诀窍 - 不阻止非白名单IP,只是将它们限制在地狱和后面.
请注意,此措施仅用于阻止这种特定类型的攻击.当然,在实践中,它可以与auth的其他最佳实践方法结合使用:固定用户名限制,每IP限制,代码强制强密码策略,无节制cookie登录,在保存之前散列所有密码等价物,从不使用安全问题等
关于攻击情形的假设
如果攻击者针对的是变量用户名,则我们的用户名限制不会触发.如果攻击者使用僵尸网络或访问大型IP范围,我们的IP限制将无能为力.如果攻击者预先刮过我们的用户列表(通常可能在开放注册的Web服务上),我们就无法根据"未找到用户"错误的数量来检测正在进行的攻击.如果我们强制执行限制性系统范围(所有用户名,所有IP)限制,任何此类攻击都会在攻击持续时间和限制期间对我们的整个站点进行DoS.
所以我们需要做点别的事.
对策的第一部分:白名单
我们可以相当肯定的是,攻击者无法检测并动态欺骗数千名用户的IP地址(+).这使白名单成为可能.换句话说:对于每个用户,我们存储用户之前(最近)登录的(散列)IP列表.
因此,我们的白名单方案将起到锁定的"前门"的作用,用户必须从他认可的"好"IP之一连接才能登录.对这个"前门"的暴力攻击实际上是不可能的(+).
(+)除非攻击者'拥有'服务器,我们所有用户的盒子或连接本身 - 在这种情况下,我们不再有'身份验证'问题,我们有一个真正的特许经营大小拉 - -plug FUBAR情况
对策的第二部分:系统范围内对无法识别的IP进行限制
为了使白名单用于开放注册Web服务,用户频繁切换计算机和/或从动态IP地址连接,我们需要为从无法识别的IP连接的用户保持"猫门".诀窍在于设计门,以便僵尸网络卡住,因此合法用户尽可能少地受到打扰.
在我的方案中,这可以通过设置一个非常严格的最大失败登录尝试次数来实现,例如,3小时(根据服务类型,使用更短或更长的时间可能更明智),以及使这种限制成为全球性的,即.对于所有用户帐户.
即使是缓慢的(尝试之间1-2分钟)蛮力也会被检测到并且使用这种方法快速有效地阻止.当然,一个非常缓慢的蛮力仍然可以被忽视,但速度太慢会击败蛮力攻击的目的.
我希望通过这种限制机制实现的目标是,如果达到最大限制,我们的"猫门"砰然关闭了一段时间,但我们的前门对于通过常规方式连接的合法用户仍然开放:
通过连接其中一个已识别的IP来实现
或者通过使用持久登录cookie(从任何地方)
在攻击期间受影响的唯一合法用户 - 即.当限制被激活时 - 将是没有持久登录cookie的用户从未知位置或使用动态IP登录.这些用户将无法登录,直到节流消失(这可能需要一段时间,如果攻击者保持他的僵尸网络运行,尽管节流).
为了让这一小部分用户能够挤过密封的猫门,即使机器人还在敲打它,我也会使用带有CAPTCHA的"备份"登录表格.因此,当您显示"抱歉,但目前无法从此IP地址登录"消息时,请添加一个链接,其中显示" 安全备份登录 - 仅限人类(机器人:不撒谎) ".除了笑话,当他们点击该链接时,给他们一个reCAPTCHA认证的登录表单,绕过站点范围的限制.这样,如果他们是人类并且知道正确的登录名+密码(并且能够读取CAPTCHA),他们将永远不会被拒绝服务,即使他们是从未知主机连接而不是使用自动登录cookie.
哦,只是为了澄清:因为我确实认为CAPTCHA通常是邪恶的,所以'备份'登录选项只会在限制活动时出现.
无可否认,像这样的持续攻击仍然会构成DoS攻击的一种形式,但是在所描述的系统到位的情况下,它只会影响我怀疑是用户的一小部分,即不使用"记住我"cookie并且恰好在攻击发生时登录并且没有从他们常用的IP登录并且无法读取CAPTCHA.只有那些能够拒绝所有这些标准的人 - 特别是机器人和真正不幸的残疾人 - 将在机器人攻击期间被拒之门外.
编辑:实际上,我想到了一种让"CAPTCHA"挑战用户在"锁定"期间通过的方法:代替或作为备份CAPTCHA登录的补充,为用户提供一次性使用的选项,用户特定的锁定代码发送到他的电子邮件,然后他可以用来绕过限制.这绝对超过了我的"烦恼"门槛,但由于它仅用作一小部分用户的最后手段,并且因为它仍然被锁定在您的帐户之外,所以这是可以接受的.
(另请注意,如果攻击比我在此处描述的令人讨厌的分布式版本更复杂,则不会发生这种情况.如果攻击来自几个IP或只触及几个用户名,那么它将会更早被挫败,并且没有整个网站的后果)
所以,这就是我将在我的auth库中实现的对策,一旦我确信它是合理的,并且没有一个我错过的更简单的解决方案.事实上,有很多微妙的方法可以在安全方面做错事,而且我不会做出错误的假设或无可救药的错误逻辑.所以,请高度赞赏任何反馈,批评和改进,微妙之处等.
几个简单的步骤:
将某些常见用户名列入黑名单,并将其用作蜜罐.管理员,访客等...不要让任何人创建具有这些名称的帐户,因此如果有人尝试登录这些名称,您就知道有人做了他们不应该做的事情.
确保网站上有实权的任何人都拥有安全密码.要求管理员/主持人拥有更长的密码,其中包含字母,数字和符号.拒绝普通用户的简单密码和解释.
您可以做的最简单的事情之一是告诉别人有人试图登录他们的帐户,并给他们一个链接报告事件,如果不是他们.当他们登录时发出一条简单的消息,例如"有人试图在星期三凌晨4点20分登录到您的帐户,等等等等.如果不是您,请点击此处." 它可以让你保留一些攻击统计数据.如果您发现欺诈性访问突然增加,您可以加强监控和安全措施.
如果我正确理解暴力攻击的MO,则会连续尝试一个或多个用户名.
有两条建议,我认为我还没有看到过:
我一直认为标准做法是在每次错误登录后为每个用户提供短暂的延迟(大约一秒钟).这可以阻止蛮力,但我不知道一秒钟的延迟会在多长时间内阻止字典攻击.(10000字的字典== 10,000秒==约3小时.嗯.还不够好.)
而不是站点范围内的减速,为什么不是用户名限制.每次错误尝试都会越来越苛刻(达到极限,我想真正的用户仍然可以登录)
编辑:响应对用户名限制的评论:这是一个特定于用户名的限制,而不考虑攻击的来源.
如果用户名被限制,那么即使是协调的用户名攻击(多IP,每个IP的单个猜测,相同的用户名)也会被捕获.即使攻击者在超时期间可以自由尝试其他用户/通行证,个别用户名也会受到限制.
从攻击者的角度来看,在超时期间,您可以第一次猜出100个密码,并快速发现每个帐户一个错误的密码.您可能只能在同一时间段内进行50秒的猜测.
从用户帐户的角度来看,即使猜测来自多个来源,它仍然需要相同的平均猜测次数才能打破密码.
对于攻击者来说,最好的情况是打破100个帐户和1个帐户一样,但由于你不是在网站范围内进行限制,你可以很快地提高限制.
额外的改进:
检测猜测多个帐户的IP - 408请求超时
检测猜测相同帐户的IP - 在大量(比如100)猜测之后的408请求超时.
UI想法(可能不适合这种情况),也可能会改进上述内容:
如果你控制密码设置,那么向用户显示他们的密码有多强,鼓励他们选择更好的密码.
如果您控制登录页面,在对一个用户名进行少量(例如10个)猜测之后,请提供CAPTCHA.
身份验证有三个因素:
用户知道某事(即密码)
用户有东西(即钥匙扣)
用户是某种东西(即视网膜扫描)
通常,网站只执行政策#1.甚至大多数银行只执行政策1.他们依赖于"了解其他"方法进行双因素身份验证.(IE:用户知道他们的密码和他们母亲的婚前姓名.)如果你有能力,添加第二个身份验证因素的方法并不太难.
如果你可以生成大约256个字符的随机性,你可以在16×16表中构造它,然后让用户给你例如单元格A-14表中的值.当用户注册或更改其密码时,请向他们提供表格并告诉他们将其打印并保存.
这种方法的难点在于,当用户忘记密码时,您不能只提供标准"回答这个问题并输入新密码",因为它也容易受到暴力破解.此外,您无法重置它并通过电子邮件发送新的电子邮件,因为他们的电子邮件也可能会受到影响.(请参阅:Makeuseof.com及其被盗域名.)
另一个想法(涉及小猫)是BOA所谓的SiteKey(我相信他们注册了名称).简而言之,您可以让用户在注册时上传图像,当他们尝试登录时,要求他们从8或15个(或更多)随机图像中选择图像.因此,如果用户上传他们的小猫的照片,理论上只有他们确切地知道他们的所有其他小猫(或花或其他)的图片.这种方法唯一真正的可用性是中间人攻击.
另一个想法(虽然没有小猫),是跟踪用户访问系统的IP,并要求他们从他们避开的地址登录时执行其他身份验证(验证码,选择小猫,从此表中选择一个密钥)以前.此外,与GMail类似,允许用户查看他们最近登录的位置.
编辑,新想法:
验证登录尝试的另一种方法是检查用户是否来自您的登录页面.您无法检查引荐,因为它们很容易被伪造.您需要的是在用户查看登录页面时在_SESSION var中设置密钥,然后检查以确保密钥在提交登录信息时存在.如果bot没有从登录页面提交,它将无法登录.您还可以通过在流程中使用javascript来实现此目的,方法是使用它来设置cookie,或者在加载后向表单添加一些信息.或者,您可以将表单拆分为两个不同的提交(即,用户输入他们的用户名,提交,然后在新页面输入他们的密码并再次提交.)
在这种情况下,关键是最重要的方面.生成它们的常用方法是用户数据,IP和提交时间的某种组合.
我以前回答了一个非常类似的问题:如何限制PHP中的用户登录尝试.我将重申这里提出的解决方案,因为我相信你们中的许多人会发现看到一些实际的代码是有用的和有用的.请记住,使用CAPTCHA可能不是最佳解决方案,因为现在CAPTCHA破坏者使用的算法越来越准确:
您不能通过将限制链接到单个IP或用户名来简单地防止DoS攻击.好吧,你甚至不能真正阻止使用这种方法的快速登录尝试.
为什么? 因为攻击可以跨越多个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 ) engine=InnoDB charset=UTF8;
根据给定时间内登录失败的总数确定某些延迟.您应该根据从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'); // assume query result of $sql is stored in $row $sql = 'SELECT MAX(attempted) AS attempted FROM failed_logins'; $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)'; // 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将确保来自多个前端的攻击最小化,并且正常站点用户不会遇到合法失败登录尝试的显着延迟.我无法保护预防,因为已经扩展了CAPTCHA可以被破坏.还有其他解决方案,可能是"命名这种动物"的变体,它可以很好地替代它.
我不得不问你是否对这个问题进行了成本效益分析; 听起来你正试图保护自己免受攻击者的攻击,因为攻击者有足够的网络存在来猜测一些密码,每个IP可能发送3-5个请求(因为你已经解除了IP限制).这种攻击会花费多少(大致)?它比您要保护的帐户的价值更贵吗?有多少庞大的僵尸网络想要你拥有的东西?
答案可能是否定的 - 但如果是,我希望你得到某种安全专家的帮助; 编程技能(和StackOverflow得分)与安全技术诀窍无关.
将Jens的方案概括为伪状态转换图/规则库:
用户+密码 - >输入
用户+!密码 - >拒绝
用户+ known_IP(用户) - >前门, // never throttle
user + unknown_IP(user) - > catflap
(#denied> n)通过catflaps(站点) - >油门catflaps(站点) // slow the bots
catflap + throttle +密码+验证码 - >条目 // humans still welcome
catflap + throttle +密码+!captcha - >否认 // a correct guess from a bot
观察:
永远不要踩下前门.Elbonian州警察把你的电脑放在你的房子里,但却无法审问你.蛮力是您计算机的可行方法.
如果您提供"忘记密码?" 链接,然后您的电子邮件帐户成为攻击面的一部分.
这些观察结果涵盖了对您试图反击的攻击的不同类型的攻击.