今天发现自己项目一个漏洞:先为一账户充值100元,然后瞬间发送10次提现请求(都是提现100,提现接口是有做余额不足校验的),其中大约有四五次都是成功的,剩下的会报余额不足。期望是,只有一次可以成功完成提现,分析到能部分请求能通过余额不足校验原因是,由于是瞬间发出的提现请求,这些请求中拿到的余额数据都是余额扣减之前的数据。
以上场景可以提炼出两个关键步骤:
- 查询余额并校验,select * from account where user_id = 123;
- 扣减余额并支付,update account set balance...
根据以上步骤,可知:1.在两条SQL语句执行的中间这段时间,由于重复请求攻击,可能会出现多次请求的第一步操作成功,并继续执行第二步,最后导致资金损失。2.由于第一步操作是查询操作,没有数据库会限制重复读取数据,数据库层面是没有可能解决这个问题的,所以不用在这个上面浪费时间。
目前的解决方案是:为接口上锁。已经有人做了轮子,比如redis-lock。以用户ID为key,某个uuid为值。将类似提现这样的接口上锁。同一用户在访问添加了该中间件的接口时,第一次没有执行完毕,拒绝执行第二个请求。第一次执行完毕时,释放锁,即清除redis缓存的键值对。同时可设定,缓存时长,以防中途宕机,锁未释放等问题。具体实现可以参考npm包redis-lock文档。