关于支付安全的一些总结
背景
支付(内购)的测试一直是一个比较棘手的问题,数据加密、第三方平台都是其中的难点。即便使用了沙盒等测试方式,各种中间环节的数据依然是不可控。测试人员大多只能做一些最基本的流程测试,比如正常走通流程、流程未完成等,有一些安全意识的测试人员还会尝试修改一些请求参数。
原理
要想进行全面深入的测试,首先要了解支付系统的实现原理。典型的实现如下:
无服务端
比如一个单机游戏,由APP直接向支付平台(以APPLE为例)发起支付请求,用户完成APPLE的支付流程后,APP会收到APPLE返回的支付结果,通常包含一个返回码和一个票据,返回码用来标识支付是否成功。APP根据返回码来决定下一步流程,比如发放物品给用户。
那么这里我们可以看到,只要在APP前拦截到支付响应并更改状态码为成功,就可以不花钱获得商品了。所以呢,这种实现方式是没有安全可言的,只要有些网络基础就可以对其破解。
不过大家应该不会遇到上面的这种实现(只有没有任何经验的开发者才会写出这种实现吧),因为通常支付平台都会提供一种本地验证数据有效性的方式。上面不是说到响应结果中包含一种票据么?这个票据是经过重重加密的,其中包含了很多信息,比如支付结果、购买的商品信息等等,只有经过正确的解密才可以看到。解密算法是平台提供给开发者的,于是一个合理的流程是,APP拿到响应结果,解密票据,判断其中的信息是否正确,然后再决定是否发放物品。
这样,我们就无法像之前那样轻易的免费获取物品了。然而这样子还不能叫做安全,因为只要你了解了加密解密的算法,同样可以构造出合法的票据,只不过技术门槛变高了。
更进一步的说,任何客户端数据都可以进行破解。要想安全,一定要有服务端,接下来才是我们的重点。
有服务端
标准的实现方式是这样的
客户端的支付步骤和之前是一样的,但是APP得到支付结果后需要回调服务端,把支付结果告诉服务端。服务端再用票据去APPLE进行验证,根据验证结果来执行相应的流程。
这样,所有的数据处理都是放在服务端,客户端层面的任何数据篡改都会导致服务端的验证失败。
好好理解这个流程,然后再来想一想这样的一个系统有哪些测试点或者安全隐患,作为测试我们有哪些能做的?
注:
实际客户端向平台发送支付请求之前,一般需要先向服务端下订单,服务端返回商品信息并开启一个新的购买流程,这里为了简洁省略掉了。
回调逻辑可能与大家预想的不一样,我之前也以为回调应该是由支付平台发起。但APPLE, GOOGLE均为图中逻辑,PAYPAL则为平台回调,所以要去看各平台的开发者文档。
传统测试
客户端层面的测试,一定是只能验证基本流程的,完成一次支付流程看看是否正确获得物品、中途中断、或者再继续完成支付等几个场景。
即使是通过一些接口测试的手段,因为核心的数据交互在服务端和支付平台之间(上图4和5),也是无能为力的。只能发现更改了回调数据以后,服务端返回支付失败。
那么这样是否测试充足了呢?
测试场景
先来想象一些典型的测试场景,再考虑通过何种方式测试。这也是我经常对新人说的,最重要的是知道自己想做什么,然后再想怎么做。
下面这些场景都是传统测试无法实现的:
支付验证结果有多少种状态?每种是否触发不同的服务端逻辑?
异常情况如何处理?比如验证超时、无响应等
更复杂的商品如订阅(自行了解),如何操纵过期时间?
如何实现自动化?客户端层面去做也不是不可能,但是实在牵连太多
新的测试(mock)
考虑一下上面的问题就会发现,核心的核心就是我们需要控制支付平台到服务端的数据交互,这样也就间接控制了服务端的各种逻辑。
自动化测试同理,我们想要达到的效果是,不通过真实的购买就可以走完服务端处理流程。比如自己构造一个收据,服务端就可以验证成功。
要做到这种控制,只能通过mock来实现,将系统结构变为如下。
这时,已经没有外部支付平台的地位了,所有的数据流都在我们的控制之内。如何实现?很简单,只要把服务端代码的外部验证地址放到配置中,测试时修改配置就可以了。
mock server主要实现两块逻辑,一是根据不同的地址返回不同平台的数据格式;二是实现控制逻辑,可以设定某个单据成功或是失败,或者是模拟各种异常状态。
那么,一个典型的测试用例就可以是这样的:
成功支付
客户端(测试代码)向服务端下一个订单,获取到商品信息
调用mock server的控制接口,声明某个票据或订单需要返回成功
直接构造支付成功的票据发往服务端
验证服务端返回的结果是否成功,并检查物品发放情况
再来个复杂一些的:
订阅过期
客户端完成一个商品订阅(比如有效期一个月,并验证成功)
调用mock server控制接口,设置某个订单的订阅状态为已过期
客户端使用此订阅商品的功能(一定是与服务端交互的)
验证服务端是否返回失败的状态
典型用例
到此,测试可以通过代码来实现,自动化就要发挥威力了。下面列一些典型的测试用例,都是发现过真实问题的哦,可以想一想如何实现用例。
使用支付成功的收据多次回调(不要小瞧这个用例,很多人栽在这里)
使用支付成功的收据并发回调
使用其他成功订单的收据回调
使用未回调的小金额订单收据回调大金额订单
验证服务无响应、数据异常等
关于安全
所有的一切,我们都是基于两个假设:
支付平台是安全的。即查询某个订单返回支付成功,那这个订单就一定是已经支付的。
服务端本身是安全的。即用户无法拦截篡改服务端的数据,否则服务端也成了“客户端”。
所以这里也可以稍微引申一些安全测试的层次,通常可按如下分:
业务安全
应用安全
主机安全
网络安全
我们上面所涉及的种种测试只可能属于业务安全。而像黑客登录了服务器(假设2),篡改了验证结果,这种属于主机安全,是完全不同的领域。
业务安全上,也有太多的问题不是通过代码逻辑就可以简单解决的。最知名的就是刷单,也就是黑产,正常购买商品后再通过平台的渠道退款,商品是不会自动退回的。如果你们的业务很火,那么会有大量的人做这件事,需要花心思去避免这种损失。(我公司的一款产品每天被退款的商品超过1W刀)
一些建议
读支付平台的开发文档!!!
支付平台的开发文档,一般都会提供最佳实现方式。平台比个人更在意安全性。
看服务端源码
看得懂逻辑,才更可能了解逻辑的漏洞
比如服务端代码中有验证失败时重试的逻辑,如果不看代码是如何也想不到这里的测试点的。