关于web资金系统提现安全保护,防止极快的重复并发请求导致重复提现的解决思路
关于WEB金融系统中的提现安全问题很多人没有深入思想,导致有漏洞,常常会遇到有些人遇到被攻击到导资金损失的麻烦, 其实要彻底解决重复并发请求 导致重复提现问题,是需要花点心思的,并没有看起来的那么 简单,即使是最直观简单的语句都是有漏洞的比如:
-----------------------------------------场景1--------------------
发现很多朋友的项目一个漏洞:先为一账户充值100元,然后瞬间发送10次提现请求(都是提现100,提现接口是有做余额不足校验的),其中大约有四五次都是成功的,剩下的会报余额不足。期望是,只有一次可以成功完成提现,分析到能部分请求能通过余额不足校验原因是,由于是瞬间发出的提现请求,这些请求中拿到的余额数据都是余额扣减之前的数据。
以上场景可以提炼出两个关键步骤:
- 查询余额并校验,select * from account where user_id = 123;
- 扣减余额并支付,update account set balance...
根据以上步骤,可知:1.在两条SQL语句执行的中间这段时间,由于重复请求攻击,可能会出现多次请求的第一步操作成功,并继续执行第二步,最后导致资金损失。2.由于第一步操作是查询操作,没有数据库会限制重复读取数据
-----------------------------------------场景2----------------------------------
重复提交,表面上是重复提交,威力不大,但实际。。。我们来分析分析:
假设一个用户,余额100,平台恰好有个提现的地方,理所当然用户最多只能提取100元。
我们来分析下程序在生成提现数据的过程:
开启事务;
用户发起一次提现请求,到达应用后,程序判断用户余额是否够用,如果不够就跳出事务了;
然后扣除100元,
然后再提现数据表中插入一条数据,
到这里还没结束,因为事务还没提交,当上面进行顺利时,到达这里就应该commit提交了,如果上面操作任何一步异常,就rollback回滚了。
看起来挺完美的过程,其实!弱暴了!
为啥?
假如用户发起两个请求,而且同一时间(1/1000秒级)请求到服务器,
再走一次上面的逻辑:
请求一达到服务器 请求二达到服务器
开启事务 开启事务
余额检查->通过 余额检查->通过
扣除余额->done 扣除余额->done
插入提现记录->done 插入提现记录->done
提交->commit(); 提交->commit();
两边几乎同时进行一样的操作,为什么没被拦截掉只处理一个请求呢?因为余额检查时,别的请求的事务未提交,在此请求内select的数据还未生效,所以两个请求处理都通过了检查。
那怎么防御呢?
token?
扯J8蛋!token用来防御这原子级别的攻击?别说session了,即使你重写php底层,让session动态调用php的内存也无济于事。原因自己脑补;
队列是终极解决方案。
然后有一个临时方案,提现的表中肯定会有time/datetime之类的字段,在建表时将这个表中的time/datetime
+ userId
设置为联合主键,然后事务在插入提现数据时,因为时间同一秒且同一用户所以数据冲突,只会成功一条,然后事务报错启动回滚,近乎完美。唯一的瑕疵就是假如前后误差1ms,
然后恰好前一个时间是xxxx1,后一个时间是xxxx2,这样就扯痛蛋了。。。千分之一的概率。
-----------------------------------------原因-----------------------------------
有人人甚至认为无解 ,其实是对数据库理解不够深,如事务级别(脏读,读提交,不可重复读,序列化级,快照级,)、并发机制、锁(共享锁,更新锁,X独占锁,行级锁,页级锁、意向锁),这么多底层知识有足够的理解才能解决这个问题,因为这些方便资料很少,愿意花精力去研究的人更不多,更郁闷的是微软数据库对查询作了优化,文档和实际执行效果是不一样的,比如微软文档明文写着,共享锁 与更新锁是可以相关排斥的,select语句默认是发布hold共享锁,如果你真信就完了,你实际执行结果是共享锁和updlock不会排斥,除非你显示指定,select * from account with(holdlock) 文档和实际不一致,只有遇到坑后请求微软技术支持才他技术人员才知道你,微软对select做了特别优势默认不是被排斥很多锁的,瞬间被坑,当年还记有个携程的主程序员不懂锁乱用,给一个查询加了with(nolock), 订票资金出现重大事故教训。 所以我提供以下几个常用的解决方法。不是不可能其实也很简单。
数据在数据库层面解决这个问题很简单,反相用了ORM EntityFramework之类的才不好解决数据库解决方案
解决方案1:使用显示事务
begin tran
select * from account with(rowlock updlock) where user_id = 123; --发布行级更新锁,第二并发请求到这里严格排序,不管有多快,这里有个技巧,因为第二个并发撞进来第一句也是updlock所以两个updlock之间会排斥
update account set balance...
commit
解决方案2:在代码层使用分布事务
using (TransactionScope ts = new TransactionScope()) //用这个需要本地单独开MSDTC (Distributed Transaction Coordinator)服务,并不一定通用 有门槛
{
exesql("update account set balance=balance where user_id =123"); //这一句很重要,事务中开头一句update让数据库先发布一个x锁,后面的并发将被严格排队
exesql("select * from account where user_id = 123;");
exesql("update account set balance..");
ts.Complete();
}
方案3 ,在代码入口使用线程锁
public static object lockObj =new object();
public void Withdraw(int user_id,int amount){
lock (lockObj){ //让提现操作在线程线严格排序,不管并发有多快,缺点是不同的用户 提现也得按顺序排序,但一般提现操作是小概率操作,不会很密集,正常提现的没阻塞感知,但是攻击者可以反复发起请求,导致正确用户提现变慢或阻塞
exesql("select * from account where user_id = 123;");
exesql("update account set balance.."); }
}
一个很重要的技巧是在一个事务内,第一句先 写一个无意义的update Account with(rowlock) set balance=balance where userid=123 ; 这个技巧在任何时候都适用,强制让数据库在事务期内发布x级独占行级锁锁,后面的操作被严格排队,就算攻击者重复请求也只会阻塞他自己的用户查询,不会阻塞别人的
总结:最可行的是存储过程方案1,缺点是不灵活,在如今ORM满天飞的情况下新一代人很少有会写存储过程SQL了,
方案2,是一个折中方案,一般可控性还好
方案3,使用最简单,基本是零成本,零难度,但是会有潜被拒绝服务攻击的功能,但保证最重要的数据安全
关于web资金系统提现安全保护,防止极快的重复并发请求导致重复提现的解决思路的更多相关文章
- C#不用union,而是有更好的方式实现 .net自定义错误页面实现 .net自定义错误页面实现升级篇 .net捕捉全局未处理异常的3种方式 一款很不错的FLASH时种插件 关于c#中委托使用小结 WEB网站常见受攻击方式及解决办法 判断URL是否存在 提升高并发量服务器性能解决思路
C#不用union,而是有更好的方式实现 用过C/C++的人都知道有个union,特别好用,似乎char数组到short,int,float等的转换无所不能,也确实是能,并且用起来十分方便.那C# ...
- OS.js – 开源的 Web OS 系统,赶快来体验
OS.js 是一个开源的 Web OS 系统,可以在浏览器中运行,提供了窗口管理器,应用程序API,用户界面开发套件和抽象的文件系统等.可以部署在 Node 或者 PHP 环境中运行.OS.js is ...
- 【转发】构建高可伸缩性的WEB交互式系统(下)
原文转自:http://kb.cnblogs.com/page/504518/ 本文是<构建高可伸缩性的WEB交互式系统>系列文章的第三篇,以网易的NEJ框架为例,对模块的可伸缩性进行分析 ...
- 【转发】构建高可伸缩性的WEB交互式系统(中)
原文转自:http://kb.cnblogs.com/page/503953/ 在<构建高可伸缩性的WEB交互式系统>的第一篇,我们介绍了Web交互式系统中平台的可伸缩性.本文将描述模块的 ...
- 【转发】构建高可伸缩性的WEB交互式系统(上)
原文转自:http://kb.cnblogs.com/page/503460/ 可伸缩性是一种对软件系统处理能力的设计指标,高可伸缩性代表一种弹性,在系统扩展过程中,能够保证旺盛的生命力,通过很少的改 ...
- android系统掉电保护
/************************************************************************ * android系统掉电保护 * 说明: * An ...
- Walle 瓦力 web部署系统
Walle 一个web部署系统工具,可能也是个持续发布工具,配置简单.功能完善.界面流畅.开箱即用! 安装步骤: 1. git clone 首先配置成功(去百度找答案) 打开git bash命令窗口执 ...
- 基于Web的系统测试方法
基于Web的系统测试与传统的软件测试既有相同之处,也有不同的地方,对软件测试提出了新的挑战.基于Web的系统测试不但需要检查和验证是否按照设计的要求运行,而且还要评价系统在不同用户的浏览器端的显示是否 ...
- 谈Web应用系统的可维护性
每一个软件开发人员都十分清楚, 当软件构建得越来越复杂时, 可维护性就成了一个很突出的问题. 如何在构造软件系统的过程中始终保持可控制的可维护性呢? 一. 整体组织 ...
随机推荐
- 计算机网络相关:应用层协议(二):HTTP
前言 复习下计算机网络的知识并记录 正文 定义:HTTP协议(HyperText Transfer Protocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传送协议. 一.HTT ...
- PAT1013: Battle Over Cities
1013. Battle Over Cities (25) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue It ...
- urllib使用
1.基本方法 urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=Fals ...
- 如何使用RedisTemplate访问Redis数据结构
RedisTemplate介绍 spring封装了RedisTemplate对象来进行对redis的各种操作,它支持所有的 redis 原生的api. RedisTemplate在spring代码中的 ...
- Mybatis概述
mybatis概述 1 mybatis产生的意义 传统的jdbc, 及其存在的问题 package cn.rodge.jdbc;import java.sql.Connection;import ja ...
- mybatis一对一映射配置详解
听说mybatis一对一有三种写法,今天我试了一下. 数据库表准备 为了偷懒,我直接就拿用户权限菜单里的菜单表和菜单与权限的中间表做实现,他们原来是多对多的关系,这边我假设这两张表是一对一. 表 g ...
- Redis 5种主要数据类型和命令
redis是键值对的数据库,有5中主要数据类型: 字符串类型(string),散列类型(hash),列表类型(list),集合类型(set),有序集合类型(zset) 几个基本的命令: KEYS * ...
- Thread部分总结以及小例子
Thread总结:一直以来用thread比较多,一般会在同步以及ui中用到.以下对于经常用作为简单介绍.一 实现方法: 一种直接new thread,另外一种是实现Runnable接口,在创建thre ...
- 怎么轻松学习JavaScript
js给初学者的印象总是那么的“杂而乱”,相信很多初学者都在找轻松学习js的途径.我试着总结自己学习多年js的经验,希望能给后来的学习者探索出一条“轻松学习js之路”.js给人那种感觉的原因多半是因为它 ...
- MongoDB与python交互
1.Pymongo PyMongo是Mongodb的Python接口开发包,是使用python和Mongodb的推荐方式.官方文档 2.安装 进入虚拟环境 sudo pip install pymon ...