为了深入挖掘bug奖励计划范围内的各种漏洞,很有必要对于那些最终用户不可见的功能进行安全测试。例如,支付webhooks就是这样的一个例子,支付提供商(如Stripe或Braintree)使用它来向网站报告用户订阅详细信息的更新情况。最重要的是,用户从来不会与这些webhook端点直接打交道——所有的通信都发生在支付提供商和服务器之间。换句话说,bug赏金猎人很可能会忽视webhook,这样的话,就更谈不上测试其中潜在的安全漏洞了。
在针对按月订阅的网站进行测试时,我发现了一份公司内部发行的API文档。其中,最吸引我的一个端点是对/api/webhooks/stripe的PUT请求。根据以往针对付款服务提供商的安全测试经验,我立即想到,首先应该向该端点发送虚假请求,看看能否欺骗网站使其误认为我已付款。
当我发送一个空的JSON请求后,服务器返回了一个错误信息。在查看Stripe为webhooks规定的格式后,我又发送了以下内容:
{
"payment": {
"status":"success",
"provider":"stripe"
},
"id":"..."
}
这次,服务器返回的是一个成功的信息:
{
"id":"...",
"amount":1,
"status":"success",
"provider":"stripe"
}
就这样,我的帐户已成功授权,并显示为付费订阅。这让我不禁想到:到底有多少这样的漏洞被我们忽略了,而支付提供商到底为防止这种漏洞提供了哪些防御措施呢?
虽然支付服务提供商有能力防止该漏洞,但是奇怪的是,有的提供商并没有对终端提供相应的保护。不过,Braintree的做法就非常值得借鉴:用户必须通过Braintree的库来解析传入的webhook,它会自动验证请求并提取JSON正文。这样,webhook端点在默认情况下就是安全的,而不会被攻击者所欺骗。
Stripe是Braintree公司使用的支付服务提供商,但是它在webhook安全性方面做得却不那么完善。尽管Stripe的文档中明确指出会验证webhook签名,但这似乎只是一个旁注,因为它没有强调确保webhooks需源自Stripe的重要性。并且,文档中列出的示例代码也没有包含任何签名验证,而是直接解析JSON请求。
如果不能确保默认情况下的安全性的话,这就是一个很大的隐患。因为用户在集成支付功能时,通常会采用最省劲的方式,这意味着许多用户根本就不会对传入的签名进行相应的验证。
Recurly公司也是一家订阅支付提供商,它提供了HTTP基本认证,以便在服务器之间共享密钥。再次强调一下,采用这种措施之前,一定要考虑用户是否能够忍受验证共享密钥所带来的麻烦。此外,Recurly还提供了一个IP地址列表,并要求webhook请求必须源自该表中的地址。但是,仅仅采取这些措施还是不够的:例如,如果攻击者创建单独的Recurly帐户来发送恶意但有效的webhook呢?
最后,根据接受比特币付款的BitPay公司声称:“由于BitPay没有对IPN进行签名,所以,不应完全信任有效载荷中的信息。”也许有人会赞成这种做法,因为安全性的缺乏是众所周知的,所以用户在阅读这些信息时反而会提高警惕性。
在测试webhook与支付相关漏洞的时候,能得到的最直接线索就是公司提供的每月订阅。根据支付提供商(如上所述)的情况来看,该网站可能没有提供足够的防御措施。
下面给出查找webhook端点的方法:
· 通过“webhook”或“payment”搜索JavaScript文件。有的公司可能会无意中暴露内部终端。
· 通过被测试对象在GitHub上的代码库或类似的地方,查找对webhooks的引用。
· 由于webhook端点的格式通常具有相似的形式,因此,可以据此尝试访问不同的API端点,如 /api/stripe/webhook、 /api/payments/webhook、 /api/stripeWebhook等。
毫无疑问,对付款webhooks进行测试的时候,应该涵盖所有敏感操作的默认值。虽然有的支付提供商为其客户提供了针对此类攻击的防御措施,但不是所有的提供商都是这样做的,所以,用户仍要提供警惕性,做好自身的安全防护工作。
与此同时,还应该检查付款端点是否存在错误配置。实际上,这些测试不仅限于支付情形,相反,对于任何服务来说,所有传入的webhook消息都应该进行相应的安全检测。