作为一个金融Web应用的开发人员,我对安全问题一直尤为关注。在过去的两年里,我参与的一些Web应用在进入生产模式之前,都会经过全面严格的安全检查,以确保它们在完全投入使用后的安全性。
在这次的经历中,也让我学到了很多关于安全的知识 – 如身份验证,潜在的危险请求,注入等等 – 以及如何设计更为安全的应用程序。
安全是我的激情所在,而吃又是我的另一种激情。午餐时间是我一天中最享受的时刻之一。 El Tenedor (在西班牙)/ The Fork 是预订餐厅的最方便的应用程序,其中包含很多的折扣,可以帮我省下不少的钱。
在本文中,我将向你展示我是如何发现Web App Yummy Days的安全漏洞的,以及如何构建一个简单的自动客户端,让我获得Yummy Days促销的奖品。
免责声明:本文中表达的观点是作者自己的观点,并不等同The Fork公司的观点。我已通过电子邮件通知了The Fork,他们已采取适当措施解决了该问题。此外,我还隐藏了URL等敏感信息。
注意:文中某些部分可能需要你具备一定的技术知识进行理解。
The Yummy Days
对于那些不熟悉的人来说,The Yummy Days是一个为期一周的促销活动,只要你在每日游戏中保持一致,你就可以赢得高达120€的免费餐和Yums的一套奖品,这些奖品将被添加到你的帐户并可兑换折扣(1000 Yums相当于餐厅账单10欧元的折扣)。
促销活动开始后,可以在The Fork app上看到一个活动banner。打开后的界面如下所示:
要参与游戏,你需要提供你的电子邮件,以获取游戏资格,然后单击“PLAY”按钮。提交此表单时,你必须要单击按钮才能触发动画并查看你是否赢得了奖品。
你可以每天玩一次,连续玩7天,来赢得奖品。
如果你够幸运,你会从沙拉中取出一个Yum,这表明你获得了奖品,你将获得一个代码可以在下一个预订中使用,Yums会被添加到你的帐户。反之,你则会从沙拉中取出紫色的生菜(或其他紫色的东西),这表示你没有中将。
我玩了三四天这个游戏,获取到了大概300个Yums!
对表单的思考
就在Yummy Days的最后一天,询问我电子邮件地址的表单无意中引起了我的注意和思考。促销页面是在某种嵌入式浏览器中打开的,我可以很容易地看到正在访问的URL( 隐藏在上图中)。
我很好奇,所以我在我的计算机上打开了一个URL,其中启用了谷歌浏览器及其开发者工具选项,以记录我在Yummy Days促销中的最后一次游戏中的所有请求。
很不幸,但我可以分析请求日志,以了解在每日游戏中发生的事情。似乎用户界面正在向Restful API服务器发出请求,所以我保存了请求和响应,我尝试再次使用我的电子邮件地址,我被重定向到了一个说我已经玩过游戏的提示页面。
然后,我尝试再次使用我的另一个电子邮件地址,而不是在The Fork应用程序中注册,看看会发生什么,令人惊讶的是我能够再玩一次! 这意味着API未验证插入的电子邮件是否已在应用程序中注册。这意味着我可以使用随机电子邮件地址无限次地玩游戏,获取更多的奖品,但我不能够这么做。
有人可能会认为这不是一个严重的问题,因为,这需要我们手动填写一个随机的电子邮件地址,接受促销条件,在幸运的情况下保存代码,并反复重复整个过程。虽然这个人会获得一些奖品,但这不会对促销的结果产生太大影响,但我要是将这个过程自动化并在每秒钟重复一次呢?
自动化执行
有很多不同的方法可以来自动化这个过程,但我最喜欢的是Postman。Postman是一个客户端,它允许我们向API发出HTTP请求,并在每个请求前后执行代码片段。天空彩
你还记得之前我使用Google Chrome Developer Tools记录的游戏过程中的所有请求吗?现在我们就要用到这些请求了。为此,我创建了一个包含三个请求的集合(Get Cookies,Fill Form 和 Play)。
第一个请求Get Cookies,它是HTTP GET到Yummy Dayspage的url请求。在Test选项卡中,你可以放置一段将在请求之后执行的代码,我设置了两个Postman环境变量,其中包含响应附带的两个Cookie的值,位于Set-Cookie header中,有效期为请求后15分钟。
在第二个请求Fill Form中,我想复制表单提交,即HTTP POST到url。我创建了一个简单的预请求脚本,一个在请求之前执行的代码,用于设置一个随机生成的电子邮件地址的环境变量。
我还使用这个生成的电子邮件设置了POST的JSON body,如下所示:
第一次尝试返回500状态码(内部服务器错误),表明该请求有一些问题。查看Google Chrome中记录的请求,我将之前存储的两个Cookie与其他Cookie一起设置为了header,这次响应码为200,太棒了!
最后在Play请求中,我将复制触发动画按钮的行为,以检查你是否赢得奖品。这是对URL的简单GET,使用前一个请求的相同标头。
我添加了一个test,以检查是否已赢得奖品,过滤尝试没有任何奖品或重复的电子邮件地址。如果在该尝试中有奖品,则对该请求的响应将记录在Postman控制台中。
我已收集了三个可执行的请求,以便用一个随机的电子邮件地址来玩游戏,因此我可以在N次执行的迭代中执行这个请求。
使用Collection Runner,我跑了100次游戏,但并没有获奖,所以我决定尝试更多的迭代次数,可以看到一个Playrequest的测试通过,并且以下JSON被记录到了控制台,表这明我赢得了奖品!
{
“id”: 16490,
“code”: “******”, //Code has been hidden
“date_to_win”: “2018–06–10 17:36:56”,
“type”: “300”, //300 Yums price
“value”: 5,
“user_id”: 217467,
“country_id”: 1,
“created_at”: null,
“updated_at”: “2018–06–10 17:36:57”
}
到这,我已经证明我可以自动获取Yummy Days的促销奖品,这似乎是基于时间和国家的(通过date_to_win和country_id属性就可以看出),并严重影响促销的结果(updated_at与date_to_win只相差了一秒!)。
天使还是恶魔?
想象一个“邪恶”的场景:
如果我在促销期间不间断执行这些请求会发生什么?如果我为所有国家执行这些请求又会发生什么呢?
只要我愿意,我将能够获得大量的Yummy Days促销奖品。
另外,我需要知道的是它是否会对来自同一IP的大量请求有某种限制。恶魔总是贪婪的,我创建了一个简单的脚本,使用Newman在shell中无限循环运行导出的Postman collection:
while true;
do newman run TheForkYummyDays.postman_collection.json;
done
我不间断地执行了将近45分钟,但并没有被服务器阻止。在这段时间里,我共获得了35个奖品,300个Yums,其中包括1个1000的Yums和1个2000的Yums。
这时在我右肩的天使开始提醒我,并说服我让我停止,现在是时候通知The Fork有关漏洞的信息了。我给他们发了一封电子邮件,其中包含我赢得的不同奖品的折扣代码以及技术细节。
几天后,我收到了他们的邮件回复,并告知我说他们已将问题报告给技术部门解决,为此他们奖励了我1000 Yums的折扣码表示感谢!
缓解措施
在服务器端检查电子邮件是否是在The Fork上注册的
这应该是解决这个问题的最好方法,无论在UI上进行哪些检查,都应始终在服务器端完成安全检查。这将不允许任何未注册的电子邮件地址参与促销活动。
在The Fork应用程序中嵌入促销页面
如果Yummy Days促销页面嵌入在The Fork app中,而不是在嵌入式浏览器中打开,那么想要查看The Yummy Days的URL就会非常困难。如果你要嵌入iframe,那么我建议使用X-Frame-Options标头,只将此页面嵌入到允许的URL中,从而防止Clickjacking攻击。
阻止超过请求数限制的IP地址
如果同一IP在规定的时间段内超过一定数量的请求,应当进行限制或阻止。