一位用户利用了我们后台系统的竞态条件来操控帖子的点赞数量。用户应该只能对某个帖子点赞从0次到50次,但这个漏洞让他们能够突破这些限制(无论是高于还是低于)。我们通过这样的方式解决了这个问题,利用了DynamoDB条件表达式和一致性读取来阻止在读取后、写入前这段时间里已被篡改的数据的更新。此外,我们还实施了一个点赞修正方案,用于那些已经被这个漏洞影响的帖子。
抓bug我们通过一位用户得知了这个问题,这位用户提供了非常详细的关于操纵掌声次数的绕来绕去的方法的报告。非常感谢这位用户的帮助,也很高兴他们告诉我们这个问题。
搞清楚几件事用户报告中的某些说法需要回应以防混淆:
本质上,Medium的合作伙伴计划的支付直接依赖于读者的点赞数。你获得的点赞越多,你的收入就越高。
这实际上并不正确。合作伙伴计划(V4)根据点赞的人数而不是点赞的次数来奖励帖子。 不考虑其他任何因素,一个获得10次点赞但点赞人数为10的帖子,会比一个获得50次点赞但点赞人数只有5的帖子获得更多奖励。更多关于合作伙伴计划的信息,您可以在这里查看:here。
此外,我们的推荐算法依赖于点赞的人数,而不是点赞的数量。
更进一步地,想象不仅清零当前记录,还永久清零未来所有记录,该用户的任何未来喜欢或好评都不会显示。
确实,这种漏洞可以让帖子的点赞数在后台显示为-200。如果接下来有3个不同的用户各自点赞50次,点赞数在后台仍为-50(所有的负点赞数在UI中都会显示为0)。但是,点赞该帖子的3个用户并未被忽略,因此,帖子的实际收益与显示全部150个点赞时相同。这就是所说的“这个漏洞仅影响界面显示”。
这个 bug 严重到什么程度?用户声称这个 bug 很严重。我们对此不太同意。
用户的观点有一定道理,即“用户可能会觉得不太愿意对那些没获赞的文章进行投票或点击等操作。”对于作者来说,如果文章显示为0赞,而统计页面却显示有>1个点赞者的情况,这种不一致也会让作者感到沮丧。
不过,由于该bug不影响帖子的收入或推荐,我们认为它没用户说的那么严重。
我们也得指出,普通用户在UI中无法重现这一点,但这只能通过软件脚本实现。
发生了什么事?在我们的后端系统中,当拍手接口被调用时,会触发一些操作。
- 我们检查用户之前是否为该帖子鼓掌过。如果是,我们就会将原有的鼓掌数加上这次的鼓掌数(最多累计到50)。如果没有点赞过,那么这将是一个全新的鼓掌记录。
- 我们会在数据库中更新点赞数,使其为 _existing_clap_count + incoming_clapcount。 问题就在这里。 我们来看看具体情况。
这种类型的写操作会先读取数据库中存在的值之后,根据你的命令进行更新或写入,保存更新后的结果。如果有两人同时想要更新同一个项目,会发生什么情况?
见下文,爱丽丝和鲍勃都在更新一个物品的价格。他们最初读到了相同的价格,但是由于一些因素(其中可能超出我们控制的因素,如网络延迟问题),鲍勃的写入只是因为碰巧发生在爱丽丝之后而成功,而不是因为其他因素。
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.ConditionalUpdate (条件更新的相关文档链接)
现在我们切换到点赞次数。假设一个用户已经为某个帖子点了30次赞,我们收到了10个几乎同时到达的请求,每个请求都是用户为该帖子再增加20次点赞。其中一些请求(假设为5个)会读取到现有记录中的30次点赞。对于这5个请求来说,再增加20次点赞是完全有效的。这样,用户就为该帖子点了130次赞!
另外5个请求可能会在写入之后看到130次鼓掌的记录,但这不是一个有效的数字(因为大于50),因此没有任何操作。
那么,我们怎么搞定这个问题呢?有没有方法在现有记录包含我们期望的值时更新该记录?换言之,如何确保当同时收到多个请求时不会超过50次鼓掌(claps)?
答案是肯定的!没错!Dynamo的条件表达式正好用于我们的条件写入需求,这正好满足了我们处理并发问题的需求。回想Alice和Bob的例子,只有当价格等于10时才会执行条件写入,这样就解决了上面提到的竞争条件问题。
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.ConditionalUpdate ("带条件更新操作的内容")
你可以想象这对我们增加点赞数也起到了非常好的效果。从我们上面的例子来看,如果现有的点赞数还是初始的30次,我们只想在此基础上加上20次点赞数。对于每个请求,我们都会进行处理:
- 从数据库中读取现有的鼓掌次数。我们使用强一致性读取操作来确保数据是最新的。
- 使用条件表达式,仅在鼓掌次数等于步骤1中读取的次数时更新记录。这确保在这次请求的读取和写入操作间,没有其他进程对数据进行了修改。
所以,我们五个同时进行的请求都从数据库中读到了30个鼓掌声。运行最快的请求会成功并把值改成50。后续尝试写入的请求将会失败,因为它们发现值已经变为了50,而不是原来的30。
修复烂掉的帖子“虽然我们都不喜欢过多地纠结于过去的错误,有时候回溯这些错误是确保未来更顺利的唯一方法。”——ChatGPT
显然,这是因为已经有几个人开始动了帖子的脑筋。有了条件写入功能,就能防止未来再出现类似情况,那过去的呢?
有必要运行一个回填程序来修复出问题的帖子。该程序简单地识别了数据库中包含的点赞数量超出[0, 50]区间范围的记录,并进行了清理修正。然而,由于我们管道系统的工作方式,只有当帖子发生新的动作,例如阅读、浏览或点赞时,这些更改才会反映出来。我们估计,这将会影响大约14000名用户的帖子点赞数,要么增加,要么减少(尴尬)。
结果部分结果真是太燃了 🔥 🔥。
处理条件判断错误我们最近发现日志中的该错误出现得更频繁了,
(条件请求错误): 请求未满足条件
也就是说,我们的条件判断有效了!
现在,这并不意味着我们突然发现了一大群一直在我们眼皮底下的“黑客”。我们运行在一个容易发生网络故障和重试的分布式平台上。因此,大多数情况下这些很可能是由于网络故障引起的无恶意错误。捕捉到这些错误仍然是重要的,因为即使是无恶意的错误也可能导致数据不准确的问题。
历史掌声整理这里有一个非常特别的案例,我们开发的清理工具解决了这个问题。
老的那套6看数,8读数,和2B+万掌声的套路…
这是内部流传的一个有趣的话题,如下所示:以下是到目前为止的指标截图。
在这之前……这是真正的现实生活吗??
不幸的是(或者说幸运的是…?),我们终于揭开了真相:
在掌声稍微减弱后
展望未来和往常一样,产品也在不断进化。这个 bug 引起了我们的注意,并促使我们重新思考“点赞功能应该是什么样的?”
如果此解决方案更新了您的帖子的拍手数,请不要担心!您的合作伙伴计划收益不会受到影响!但我们希望您也能和我们一样重视数据质量的维护。
写得开心!
共同学习,写下你的评论
评论加载中...
作者其他优质文章