并发请求导致的业务处理安全风险及解决方案

http://drops.wooyun.org/papers/831


0x00 背景


一段简单的购买程序,看起来没有任何问题。

剩余余额、商品库存、购买权限等判断面面俱到,从头到脚包装的严严实实。

但是为何人一多就频频漏点呐?何解?

0x01 问题分析


还是以商城购买为例,商城网站是web程序和数据库两部分,业务处理流程:

1
用户金额是否大于商品价格—>商品库存是否充足—>购买操作:生成订单—>扣除用户金额—>商品库存减1

2013122510571541794.png

流程的每一部分都是web与数据库打交道,查询或者操作数据库。

程序示例(PHP+MySQL)

1
2
3
4
5
6
7
8
9
10
11
12
13
$goods = $db ->FirstRow( "SELECT * FROM " .Tb( 'goods' ). " WHERE goods_id='{$goods_id}'" );
if ( empty ( $goods )) ShowError( '商品不存在' );
/* 金额是否充足 */
if ( $user ->money< $goods [ 'price' ])  ShowError( '金额不足,请充值' );
/* 商品库存 */
if ( $goods [ 'num' ]==0)  ShowError( '库存不足' );
/* 购买操作 begin */
//生成订单
CreateOrder( $goods , $user ,time());
$user ->Update( 'money' => $user ->money- $goods [ 'price' ]); //用户金额减少
$db ->Execute( "UPDATE " .Tb( 'goods' ). " SET num=num-1 WHERE goods_id='{$goods_id}'" ); //商品库存-1
ShowSuccess( '购买成功' );
/* 购买操作 end */

正常来看这个业务处理是没有问题的,下面想象下多人同时购买(并发请求,如秒杀活动)的情境可能会引发的问题?

如果一个用户同时有两次购买请求,一次购买已进行到添加订单但未扣除用户金额,另一次购买在第一步用户金额判断便不准确了。

当商品库存仅为1时,同时有多个请求,而当前没有一个请求走到商品库存减少位置,多次购买都能成功,而商城却无货可发。

2013122510574019546.png

总结来说,当有大量的购买操作同时进行,如果数据库的处理速度跟不上程序的请求速度,就会出现判断不准确的问题,造成用户以单个商品的金额购买多个商品、某些用户付款了但得不到商品等,算是一个安全风险。

0x02 解决方案:


核心思想:将一次业务处理流程(如购买操作)作为一个最小操作单元,同一时间只能有一个操作。

1.  整个操作加内存锁。如在memcache里,开始购买时设置购买状态为进行中,购买结束后清除购买状态,程序开始时即从memcache里判断是否有正在进行的购买操作,如有则退出。
2.  限制每个用户的购买间隔,如10秒内仅允许购买一次,最好也是放在内存里。
3.  当然,优化数据库及程序以加快处理速度也是有必要的。

解决方案程序示例(PHP+MySQL+Memcached)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
  * 通过memcache解决并发购买问题
  */
$goods = $db ->FirstRow( "SELECT * FROM " .Tb( 'goods' ). " WHERE goods_id='{$goods_id}'" );
if ( empty ( $goods )) ShowError( '商品不存在' );
$mmc =memcache_init();
$lastBuyTime = $mmc ->get( 'lastBuyTime_' . $user ->userId);
if ( $lastBuyTime >0 && $lastBuyTime >time()-10)  ShowError( '10秒内只能进行一次购买' );
$buying = $mmc ->get( 'buying' );
if ( $buying ==1)  ShowError( '有正在进行的购买,请稍候' );
/* 金额是否充足 */
if ( $user ->money< $goods [ 'price' ])  ShowError( '金额不足,请充值' );
/* 商品库存 */
if ( $goods [ 'num' ]==0)  ShowError( '库存不足' );
/* 购买操作 begin */
//生成订单
CreateOrder( $goods , $user ,time());
$user ->Update( 'money' => $user ->money- $goods [ 'price' ]); //用户金额减少
$db ->Execute( "UPDATE " .Tb( 'goods' ). " SET num=num-1 WHERE goods_id='{$goods_id}'" ); //商品库存-1
/* 购买操作 end */
$mmc ->set( 'buying' ,0);
$mmc ->set( 'lastBuyTime_' . $user ->userId,time());
ShowSuccess( '购买成功' );

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值