<dd id="sga4y"><optgroup id="sga4y"></optgroup></dd>
<nav id="sga4y"><code id="sga4y"></code></nav>
  • <optgroup id="sga4y"></optgroup>

    原文地址:http://drops.wooyun.org/papers/831

    0x00 背景


    一段簡單的購買程序,看起來沒有任何問題。

    剩余余額、商品庫存、購買權限等判斷面面俱到,從頭到腳包裝的嚴嚴實實。

    但是為何人一多就頻頻漏點吶?何解?

    0x01 問題分析


    還是以商城購買為例,商城網站是web程序和數據庫兩部分,業務處理流程:

    #!shell
    用戶金額是否大于商品價格—>商品庫存是否充足—>購買操作:生成訂單—>扣除用戶金額—>商品庫存減1
    

    2013122510571541794.png

    流程的每一部分都是web與數據庫打交道,查詢或者操作數據庫。

    程序示例(PHP+MySQL)

    #!php
    $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)

    #!php
    /**
     * 通過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('購買成功');
    

    97av