<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址: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('購買成功');
            

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线