menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right 007-papers chevron_right 0170-并发请求导致的业务处理安全风险及解决方案.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    0170-并发请求导致的业务处理安全风险及解决方案.md
    3.89 KB / 2021-07-17 00:01:36
        # 并发请求导致的业务处理安全风险及解决方案
    
    0x00 背景
    -------
    
    * * *
    
    一段简单的购买程序,看起来没有任何问题。
    
    剩余余额、商品库存、购买权限等判断面面俱到,从头到脚包装的严严实实。
    
    但是为何人一多就频频漏点呐?何解?
    
    0x01 问题分析
    ---------
    
    * * *
    
    还是以商城购买为例,商城网站是web程序和数据库两部分,业务处理流程:
    
    ```
    用户金额是否大于商品价格—>商品库存是否充足—>购买操作:生成订单—>扣除用户金额—>商品库存减1
    
    ```
    
    ![2013122510571541794.png](http://drops.javaweb.org/uploads/images/fc630c4ff334d6d70954d50ea90d8eb85623b293.jpg)
    
    流程的每一部分都是web与数据库打交道,查询或者操作数据库。
    
    ### 程序示例(PHP+MySQL)
    
    ```
    $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](http://drops.javaweb.org/uploads/images/e8fb7c60f86a55848054013b6d4c1716d0b646bd.jpg)
    
    总结来说,当有大量的购买操作同时进行,如果**数据库的处理速度跟不上程序的请求速度**,就会出现判断不准确的问题,造成用户以单个商品的金额购买多个商品、某些用户付款了但得不到商品等,算是一个安全风险。
    
    0x02 解决方案:
    ----------
    
    * * *
    
    核心思想:将一次业务处理流程(如购买操作)作为一个最小操作单元,同一时间只能有一个操作。
    
    ```
    1.  整个操作加内存锁。如在memcache里,开始购买时设置购买状态为进行中,购买结束后清除购买状态,程序开始时即从memcache里判断是否有正在进行的购买操作,如有则退出。
    2.  限制每个用户的购买间隔,如10秒内仅允许购买一次,最好也是放在内存里。
    3.  当然,优化数据库及程序以加快处理速度也是有必要的。
    
    ```
    
    **解决方案程序示例(PHP+MySQL+Memcached)**
    
    ```
    /**
     * 通过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('购买成功');
    
    ```
    
    links
    file_download