使用客户端拼图协议的战斗机器人

2020-09-23 22:52:13

1999年,阿里·朱尔斯(Ari Juels)和约翰·布雷纳德(John Brainard)提出了一种针对拒绝服务攻击的优雅保护,称为客户端拼图协议。他们的想法获得了专利(美国专利7197639),这可能会阻碍其被采纳。然而,该专利已于2020年初到期,因此现在任何人都可以免费使用。它应该被使用。

客户端拼图协议并不广为人知,也没有实现,但我们确实注意到Akamai在他们的机器人管理器的专利到期后不久就学会了这个概念。Akamai增加了高级智能(备注:Clouflare似乎也在做同样的事情,参见Kasada),但基本的客户端拼图协议很容易实现,任何人都可以不用花一分钱就能使用。

解释Juels-Brainard客户端难题协议背后的基本思想(略有简化),重点放在Web应用程序上。

客户端拼图协议的目标是降低机器人的速度,使它们接近人类的速度。放慢它们的速度会阻止许多不同的攻击,例如Web刮除、暴力攻击和某些类型的拒绝服务。

为了实现这一点,使用了工作证明概念。现在可能很多人都在想比特币,但在比特币发明前十多年,密码文献中就已经有了工作证明的概念。事实上,阿里·朱尔斯是这一概念的先驱之一。

客户端难题协议向客户端提供密码难题,在服务其请求之前必须解决该难题。这个谜题可能只需要几分之一秒就能解开-这对合法的人类几乎没有影响,但会显著减慢机器人的速度。拼图解决方案的验证速度非常快,因此协议对服务器的影响可以忽略不计。此外,该协议完全是无状态的。

具体地说,该难题通常涉及找到密码散列函数的前映像的一部分。请参见下图。

在此构造中,使用了两级散列。首先,对客户端请求数据(查询参数/请求正文)以及服务器端机密和时间戳进行散列。这将产生散列H1,该散列被散列以产生散列H2。拼图由H2和H1的大部分部分组成。

加密散列函数被设计为抵抗前映像,因此如果给您提供h2,则查找h1将非常困难/耗时。另一方面,如果您获得h2和h1的大部分位,那么只要剩余位的数量不是太大,您就可以强行使用剩余位。您只需尝试剩余比特的每种可能性,对候选预映像进行散列,并查看散列是否与h2匹配。如果缺少k个比特,则这需要多达2k次试验。对于小k来说,这很容易做到,但需要一些计算工作。

在客户端难题协议中,h2和h1的大部分位被提供给客户端。客户端必须暴力破解剩余的比特。当客户端成功时,它会发回谜题解决方案(H1)、服务器提供的时间戳和请求数据。

现在考虑服务器如何验证结果。这是最优雅的部分,因为服务器不需要记住H2。相反,它只是通过散列h1的秘密以及客户端提供的请求数据和时间戳来重新计算h1。如果散列与客户端提供的匹配,那么问题就解决了!

只要客户不知道服务器密码,他们就不能伪造假谜题。提供假拼图的尝试很容易被捕获,并从刚才描述的计算中被拒绝。对于服务器,这种计算既快速又容易:不使用数据库的单一散列计算。类似地,客户端不能在时间戳上撒谎,因为散列计算不会检出。

时间戳的用途是什么?您可以设置必须在此之前解开谜题的过期时间。这提供了在解决方案花费太长时间时拒绝请求的选项,如果请求的响应可能会随着时间的推移而更改,这一点尤其有用。例如,考虑电子商务网站上商品的价格。没有时间戳,攻击者可以每周提供相同的谜题解决方案,而无需重新计算以获得每周的价格更新。有了时间戳,他每次都必须重新计算。

当Juels和Brainard在1999年撰写关于此协议的文章时,他们描述了如何防御TCP SYN洪水、电子邮件炸弹等威胁。从那时起,互联网蓬勃发展,今天更清楚的是,这项技术的许多应用不仅用于防止拒绝服务攻击,而且更广泛地用于阻止机器人活动,如Akamai使用它的方式。

我不是专业程序员,我只是在学习Node.js,但尽管如此,构建概念证明对我来说并不费力。您可以在GitHub上看到源代码。该PoC接受搜索查询的用户输入,并从Giphy返回与搜索查询相关的随机GIF。服务器端使用Giphy API密钥。通过使用客户端难题协议,每次通过服务器向Giphy发出请求时,客户端都需要进行计算,Giphy控制通过服务器发送到Giphy的请求的数量。

服务器端的主要函数是COMPUTE_PUSIT()和CHECK_PUSPLE_Solution(),前者用于根据原始请求计算难题,后者用于验证解决方案。客户端拥有暴力破解谜题的代码。

您可以在这里看到演示。拼图强度设置为2 17,过期时间为10秒。在我的大多数设备上,这通常只需要不到一秒的时间,除了我的旧iPad2,它可能需要几秒钟。请注意,每个请求都使用我的Giphy开发API密钥,该密钥受Giphy的速率限制。虽然我不知道速率限制设置是什么,但我指望客户端拼图协议(希望)将我的匿名可访问演示应用程序保持在最低级别。

如果您要使用我的GitHub代码,这里有几点您应该知道的:

代码在启动时从cryption.随机性字节()生成秘密,如果您只有一台后端服务器,这是很好的。如果您使用多个服务器,则应该在它们之间共享密码,以处理响应难题解决方案的服务器可能与创建难题的服务器不同的情况。

整个系统的安全性取决于秘密的强度,所以不要使用像P@55w0rd123这样愚蠢的东西。这个秘密需要很长时间,而且不能强行实施。

该程序最具娱乐性的是Giphy API密钥,您可以按照此处的指导快速免费获得该密钥。获得密钥后,将其设置为环境变量GIGPHY_API_KEY。

您还可以设置拼图强度(环境变量PUSPHY_STANCE,默认情况下为16,表示2 16努力)和过期时间(环境变量TIME_LIMIT,默认情况下为5000毫秒)。

最终,客户端JavaScript代码应该得到优化。这是因为合法用户将使用JavaScript代码,而优秀的黑客会使用自定义代码来尽可能快地解决难题。他使自己的代码比JavaScript代码快得越多,对他就越有好处(更多细节将在下面的最后一节中介绍)。要优化合法的客户端代码,重点应该放在尽可能快地生成sha256上。

速率限制非常重要,但在某些攻击场景中,速率限制不能提供足够的保护。

如果您有可匿名访问的API,则速率限制的典型方法是按IP地址进行限制。然而,现在的黑客很容易通过使用Fireprox等工具轮换他们的IP地址来绕过这一限制。黑客还可以伪造地理位置,很容易混入其中,看起来像是合法用户。因此,很难区分坏人和合法用户-您要么让坏人请求免费进入,要么冒着放弃合法用户请求的风险。

在对抗访问匿名可访问内容的恶意客户端时,如果可以确定客户端是否为恶意客户端,则速率限制是最有效的。对于一个聪明的对手,这种确定性是不存在的。

客户端难题协议通过要求每个客户端解决难题来更好地处理不确定性。合法用户(人)每秒不会发出大量请求,因此他们看不到什么影响。另一方面,恶意机器人将看到巨大的影响,因为它们被迫为其众多请求中的每一个进行计算,而且这会积累起来。使用客户端拼图协议,发出请求不再是免费的。

当然,可能有合法的机器人需要每秒执行许多请求。可以通过多种方式(APIKEY、已知IP地址等…)识别这些地址。并被列入白名单让他们通过。其他每个人都必须根据请求进行计算。

在讨论增强功能之前,我们必须先讨论限制。客户端拼图协议不会阻止机器人,它只会减慢它们的速度。恶意实体仍然可以让请求通过,但是突然它必须为每个请求“付费”,其中付费是按计算时间支付的。如果该实体具有强大的计算能力,它可能会抹去该协议提供的大部分好处。

在最初的出版物中,Juels和Brainard建议仅在服务器受到攻击时使用该协议,并根据攻击的严重程度调整参数。他们还考虑了诸如TCP SYN洪泛之类的攻击,在这种情况下,一台服务器一次只能处理这么多连接。我们的重点更多地放在应用程序层,因此我们将讨论特定于应用程序的增强功能。当然,根据严重程度调整参数仍然是有效的保护措施。

类似于Akamai/Cloudflare/Kasada,我们可能有一些关于我们对手的情报,允许我们根据请求是恶意的可能性来调整拼图强度。例如,也许我们知道攻击者正在使用特定的API密钥或用户代理头-我们可以为这些请求提供比其他请求更难的谜题。

如果我们试图防御攻击者野蛮逼迫特定目标,那么我们可以缓存失败的尝试,并在每次失败时增加这些特定目标的拼图强度。这些力量的增加应该是暂时的:足够长的时间来挫败敌人,而不是太长的时间来把好人拒之门外。

我们总是可以把已知的好客户列入白名单,让他们不费吹灰之力就能通过,而对于未知的情况则需要更高的拼图强度。

备注:虽然它会降低影响,但客户端难题协议本身并不足以防止凭据填充攻击,因为攻击者每个请求都有很大的成功概率。有关更恰当的辩护,请参阅我们的OT2FA博客。客户端拼图协议确实有助于阻止其他形式的暴力,在这些情况下,每个请求成功的可能性较小。

客户端难题协议的缺点之一是攻击者可能比合法用户拥有更多的计算资源。这就是众所周知的资源不平衡问题。导游益智游戏协议就是为解决这一差异而设计的。我还没有研究它们的实用性或知识产权方面的考虑。

在私下交流中,阿里·尤尔斯建议,客户谜题可能被用来挖掘加密货币,如Monero(具有抗ASIC的工作证明方案),“这意味着用户实际上是在为服务付费。”在进行了一些数字运算后,他表示,不幸的是,它不会产生太多货币,因为总体来说,客户没有做大量的工作。阿里还指出,在1999年与马库斯·雅各布森(Markus Jakobson)的一篇相关论文中,他们将这一想法应用于一种名为MicroMint的加密货币协议,并对此进行了讨论。