webgame中常見安全問題、防禦方式與挽救措施

【轉自】http://www.cnxct.com/experience-with-webgame-of-security-and-defense/

十月一的假期間,在知乎上看到一個問題《網頁遊戲都有哪些安全問題?》,我是一個網頁遊戲開發者,對這個問題非常感興趣,印象比較深刻。當時是在遊玩,也沒時間細看這個問題。後來,在微博上,有一位朋友的轉發,又讓我看到這個問題,冥冥中,有種想回答的衝動。上週六時,研發部門內部週會時,聽到其他項目組的一個整型溢出問題,導致刷錢的bug,又讓我想起這個問題,更加堅定我要回答這個問題的決心,總結一下這項目中,所有經歷過的webgame安全問題的經驗,以加固當前項目安全壁壘,避免損失。亦可分享給其他做webgame研發的朋友,做交流探討。

知乎中的原問題是『網頁遊戲都有哪些安全問題?』,我覺得不妥,我給改成了『網頁遊戲都有哪些安全問題?如何做得更安全?』,同時,問題也從『大家來研究探討一下,網頁遊戲攻防技術。必定,這個話題很敏感。目前,網頁遊戲已經很多了,會不會被黑產盯上?網頁遊戲會不會被黑,數據庫會不會被拖庫』改成了『大家來研究探討一下,網頁遊戲攻防技術。必定,這個話題很敏感。目前,網頁遊戲已經很多了,會不會被黑產盯上?網頁遊戲會不會被入侵?入侵方式有哪些?如何做好網頁遊戲的入侵防禦?挽救措施有哪些?如何才能最小化減少廠商損失?入侵方式有哪些?如何做好網頁遊戲的入侵防禦?挽救措施有哪些?如何才能最小化減少廠商損失?』,更改的理由是『本文原提問者開篇提到「大家來研究探討一下,網頁遊戲攻防技術。」,那麼應該不光提到如何入侵,更應該提到如何防禦,應該細心描述漏洞形成原理,規避方式,以提高研發者技能水平;應該詳細講解安全事件發生後,如何最小化減少廠商損失,減少用戶損失,保護遊戲平衡。』,幸運的是,這個修改,被知乎通過了。對此,表示感謝。

在後來閱讀這篇提問以及回答時,已經有幾位網友回答了,多數是站在安全工作者角度上回答了這個問題。在這篇日誌裏,我將以webgame研發者角度,切合遊戲業務模塊邏輯,從業務需求,數據庫設計,程序編寫,操作方式上來講解漏洞形成原理,規避方案,也歡迎大家討論。

登錄認證

近幾年,網頁遊戲幾乎都是以聯運方式運營,意味着遊戲服務器本身不保存用戶密碼,用戶登錄在平臺,通過平臺跟遊戲服務器的接口對接登錄。接口做加密認證。故webgame的帳號密碼安全問題,這裏不提了。但登錄認證的hash字符串安全,也還是要注意的。比如登錄hash字符串的生效時間,hash字符串的加密參數來源,比如包括用戶名、登錄IP,瀏覽器user-agent等數據,以防止改hash被泄漏了,也是很難通過服務器的驗證。

遊戲充值

webgame的遊戲充值流程,跟普通網頁充值流程一致,沒有特殊的地方,其不同點就是跟其他衆多平臺做聯合運營時,勢必要每個公司做接口對接,且接口規範各式各樣,且遊戲廠商沒有話語權,必須按照他們的接口規範來,這實在棘手。騰訊的充值接口的驗證方式,安全性做的較爲突出,大約代碼:

01 // 返回參數列表
02 $signKey = array('openid','appid','ts','payitem','token','billno','version','zoneid','providetype','amt','payamt_coins','pubacct_payamt_coins');
03 $sign = array();
04 //從GET參數中,對比找出上面參數的值
05 foreach($signKey as $key ) {
06     if (isset($data[$key]))
07     {
08  $sign[$key] = $data[$key];  //只有 GET裏有的參數,才參與sig的計算
09     }
10 }
11 ######開始生成簽名############
12 //1: URL編碼 URI
13 $url = rawurlencode($url);
14 //2:按照key進行字典升序排列
15 ksort($sign);
16 //3: &拼接,並URL編碼
17 $arrQuery = array();
18 foreach ($sign as $key => $val )
19 {
20     $arrQuery[] = $key . '=' . str_replace('-','%2D',$val);
21 }  
22 $query_string = join('&', $arrQuery);
23 //4 以POST方式拼接 1、3 以及URL
24 $src = 'GET&'.$url.'&'.rawurlencode($query_string);
25 // ## 構造密鑰
26 $key = $this->config->get('qq_appkey').'&';
27 //### 生成簽名
28 $sig = base64_encode(hash_hmac("sha1", $src, strtr($key, '-_', '+/'), true));
29 if ( $sig != $data['sig'] ) {
30     $return['ret'] = 4;
31     $return['msg'] = '請求參數錯誤:(sig)';
32     $this->output->set(json_encode($return));
33     return ;
34 }

在此基礎上,還可以做的嚴謹點:

  • 增加隨機參數名、參數值。隨機參數名、參數值由聯運方隨機生成,按照參數名的字符串所屬ASCII碼順序排序,參數名、參數值均參與sign的計算,增加暴力破解密鑰(app key)難度。
  • 增加回調驗證訂單號,金額信息。遊戲充值服務器接收到充值請求時,反向到該平臺回調接口,確認此筆訂單有效性,以防止加密密鑰泄漏的問題。

遠程文件引入

在網頁遊戲的研發中,多數都是使用框架來做,即使用REQUEST來的參數,作爲請求文件名的一部分,來使用,那麼很容易形成遠程文件引入的漏洞。在我們之前的遊戲中,曾出現過一例這樣的漏洞問題。

1 // Load the local application controller
2 // Note: The Router class automatically validates the controller path.  If this include fails it
3 // means that the default controller in the Routes.php file is not resolving to something valid.
4 if ( ! file_exists(APPROOT.'controllers/'.load('Router')->getDirectory().load('Router')->getClass().EXT))
5 {
6  load('Errors')->show404('Unable to load your default controller.  Please make sure the controller specified in your Routes.php file is valid.');
7 }
8 include(APPROOT.'controllers/'.load('Router')->getDirectory().load('Router')->getClass().EXT);
9 load('Benchmark')->mark('load_basic_class_time_end');
webgame中的遠程文件引入

webgame中的遠程文件引入

從代碼以及案例圖中,可以看到對於REQUEST的參數沒有過濾處理,直接作爲文件名來include引入的,故導致這種問題,類似上頁圖中,若PHP version < 5.3.4 ,還會發生Null(%00) 截斷的問題,帶來更大的安全問題。在我們新的項目中,我們更改了實現方式,我們遊戲所有接口都會走gateway,gateway裏,對控制器名做類名規範的檢測處理,再在指定幾個目錄下做autoload加載文件,且還會對REQUEST的類名、方法用ReflectionClass反射類的處理,檢測到類、方法、參數是否合法。一來避免『遠程文件引入』漏洞問題,二來便於前後端聯調時,拋出更詳細的異常,方便調試。下面爲參考代碼:

01 require_once CONFIG_PATH . "/auto.php";
02 spl_autoload_register("__autoload");
03  
04 ……
05  
06 //默認消息格式
07 $view->clear();
08 $view->error(MLanguages::COM__INVALID_REQUST);
09 $msg = new Afx_Amf_plugins_AcknowledgeMessage($val->data[0]->$messageIdField);
10 $msg->setBody($view->get());
11 $message->data = $msg;
12
13  
14 $a = new Yaf_Request_Simple();
15 $a->setControllerName($method[0]);
16 $a->setActionName($method[1]);
17 $objC = new ReflectionClass($method[0]."Controller");
18 $arrParamenter = $objC->getMethod($method[1]."Action")->getParameters();
19 $arrRequest = isset($val->data[0]->body[0]) ? (array)$val->data[0]->body[0] : array();
20 $bCanCall = true;
21 foreach ($arrParamenter as $objParam)
22 {
23     $parm = $objParam->getName();
24     $bIsOption = $objParam->isOptional();    //是否爲可選參數
25     if (isset($arrRequest[$parm]))
26     {
27         $a->setParam($parm , $arrRequest[$parm]);
28     }
29     elseif ($objParam->isOptional())
30     {
31         //可選參數
32     }
33     else
34     {
35         $bCanCall = false;
36     }
37 }
38 if ($bCanCall)
39 {
40     $rp = $app->getDispatcher()->dispatch($a);
41     $msg = new Afx_Amf_plugins_AcknowledgeMessage($val->data[0]->$messageIdField);
42     $msg->setBody($view->get());
43     $message->data = $msg;
44 }

SQL 注入

SQL注入原理、方式,跟普通web應用一樣,沒什麼特別的,在使用REQUEST來的參數時,過濾處理即可。可能在消息格式,以及注入操作簡便上,會矇蔽研發人員的眼睛,被忽略掉了。比如我們項目的AMF消息格式,在前端界面沒出來之前,我們後端程序員一般使用Pinta來模擬操作,調試程序。前端界面出來之後,會使用Charles proxy來捕捉http請求。在這些過程中,請求接口、參數的構造,沒有普通web那麼簡單。研發人員也容易忽略對請求參數的過濾,故很容易形成這種問題。形成原理見:《WEB開發安全與運維安全淺見》,防禦方式做過濾處理,或SQL預編譯。

AMF消息格式的WEBGAME中的SQL注入

AMF消息格式的WEBGAME中的SQL注入


AMF消息格式的WEBGAME中的SQL注入

AMF消息格式的WEBGAME中的SQL注入

爲了提高遊戲服務器的吞吐能力,網頁遊戲的架構也是一直在演變的。在之前以Mysql作爲數據存儲的webgame架構中,其他節點都是可以水平擴展,或者說依賴簡單粗暴的增加服務器來解決,單單作爲唯一數據存儲中心,不能這麼做。爲此,很多webgame的數據存儲改用Nosql來代替,甚至java、C/C++的遊戲數據,直接在內存中操作,遊戲關服時,才寫入到DB中。故SQL注入的問題,也會越來越少。

通訊協議與消息格式

網頁遊戲雖然名字叫網頁遊戲,但通訊協議並非全是http,也有很多使用socket,以及http+socket並用的做法。我們是http協議+amf消息格式,以及socket並用來實現。在http與https的取捨上,我們考慮到ssl的啓用後,大量的ssl解密加密運算,勢必會增加服務器大量的CPU計算壓力。而傳輸的內容,多數是遊戲業務的操作,響應,是能接受被監聽嗅探的行爲的(認證信息除外)。站在安全角度,這不能理解。但站在產品角度,考慮一下 投入產出,然後選擇http通訊,也是可以理解的。socket在我們遊戲中,除了在聊天應用上使用外,在一些組隊、幫派戰之類需要多個玩家之間同步數據信息時,我們也會使用socket來推送數據。在使用socket作爲所有業務傳輸的協議時,協議格式一般都是開源協議,比如msgpackprotobuf之類,或者自定義的協議。使用自定義協議時,務必檢測整個消息包的每一個參數,類型範圍,避免個別超大數值、邊界數值出現,導致主程序內存越界,以至於服務宕機,無法正常服務的情況發生。

金幣複製-整型溢出

上週週六開週會時,聽到其他項目組的一個關於整型溢出導致產生刷金幣的問題。在這裏,我抽象該案例,分享一下。商城出售開啓揹包格子的所需道具『梧桐木』。在遊戲中,用戶包裹格子數量一般都會作爲一個收費點,一款遊戲的格子大約爲每行7格子,一共8行這樣。比如前面3行是默認開放的,第4行是收費的,而且第一個格子所需品梧桐木的價格1個銀子,第二個梧桐木是2個銀子,第三個是4個銀子。依次類推,意味着這些梧桐木的價格總和其實就是一個第一項爲1,公比爲2,項爲35的等比數列。 當用戶選擇購買梧桐木數量大於31時,比如32-36中這些數字時,這些等比數列的和就是大於2147483647。(只是舉例,實際上不會以這樣的價格出售物品)

在java中,4字節的存放int型變量的範圍是-21474836482147483647。在java、c的有符號int型中存儲時,數的最高位描述符號位,4字節共32位,除去最高位的符號位,剩下31位,每個位上能表示2個數字,4字節的有符號的整數表示範圍爲:負整數2^31個,範圍爲『-1至-2147483648』;正整數2^31個,範圍爲『2147483647至1』。 比如下圖(注意十進制數字跟二進制表示的變化順序):
4字節有符號整型溢出
當開啓格子數字爲大於31時,比如32,那麼所需費用就是2147483647個銀兩,再買點其他物品,湊成超過2147483647的數字,比如又買了3個銀子的其他道具,總共花費2147483650個銀子,在4字節的有符號int中表示出來的結果,變成符號位爲1,即負整數。數值位爲0000000 00000000 00000000 00000010,也就是10000000 00000000 00000000 00000010,對應十進制的-2147483646。程序邏輯上,再判斷現有銀兩是否足夠支付此筆花費時,是通過的。當使用當前餘額減去這筆花費時,將變成減去一個負數,那麼實際上就是加上一個正整數。變成了自己銀兩賬戶餘額的增長。而餘額字段類型是long,則正確的存儲了這些餘額,溢出漏洞被利用。在C中,使用無符號的數值類型,即可完成數值類型溢出刷錢的行爲,但在java中,好像沒有無符號的類型。這也可以先確定所有參與計算的數值必須爲正整數作爲必要條件(遊戲業務特性,遊戲內所有數字,肯定全爲正整數,甚至都不包括零),先做大小判斷,再做正正相加,不能得負;負負相加,不能得正。來判斷是否發生了溢出問題。在PHP中,不用擔心溢出問題。 更多信息參考:《溢出,符號與進位

不光是程序中,整型變量類型得需要注意,使用unsigned int在存儲數據的DB中也要做同樣的處理,對於存儲整型數字的字段,均設置爲unsigned int/tinyint/...。在遊戲中,數字幾乎全是自然數,幾乎不會出現負整數的情況。

金幣複製-併發請求

Rpg類型的網頁遊戲中,多數都有道具出售的功能,直接賣到商店,以及道具材料從商店買入功能。當玩家同時針對買入、賣出兩個操作,瞬間大量併發請求時,在服務器的處理邏輯一般有分別的兩個進程處理,共享數據分別數據庫中的對應賬戶餘額表,如下圖:

webgame買入、賣出併發請求處理

webgame買入、賣出併發請求處理

01 //賣出
02 // startTrans
03 $iBalance = $obj->getBalance('user1');  //餘額50
04  
05 //UPDATE `role_gold` SET gold = 150 WHERE role_id = 1
06 if(!$obj->setBalance('user1',$iBalance + 100))
07 {
08     //rollback
09 }
10 //扣除物品
11 if (!$obj->delItems($items))
12 {
13     //rollback
14 }
15 //commit
16  
17 //買入
18 // startTrans
19 $iBalance = $obj->getBalance('user1');    //餘額50
20 //UPDATE `role_gold` SET gold = 0 WHERE role_id = 1
21 if(!$obj->setBalance('user1',$iBalance - 50))
22 {
23     //rollback
24 }
25 //發放物品
26 if (!$obj->addItems($items))
27 {
28     //rollback
29 }
30 //commit

賣出請求的處理進程爲1,買入請求的處理進程爲2。在進程1還沒將結果寫入到DB時,進程2也從DB讀取到餘額爲50。這是,兩個進程拿到的餘額信息都是50。進程1按照邏輯代碼,計算出剩餘餘額是150;進程2計算出的剩餘餘額是0。最後,不管那個進程最後寫入餘額,都是錯誤的結果。(注:這裏的代碼邏輯操作,跟mysql事務無任何關係,事務只能保證單個進程的事務範圍內多條語句都正確執行,或回滾。比如能保證扣錢成功,且物品刪除掉的兩個語句都正確執行。能保證其中之一的語句執行失敗時,都正確回滾。)
其實,在事物開啓時候,SELECT語句是否可以取到最新的數據,或者是否需要等待鎖釋放,取決於MYSQL的事務隔離級別。在MYSQL的事務隔離級別中,有一下幾種隔離級別:
mysql transaction isolation level

  • READ-UNCOMMITTED(讀取未提交內容)級別
  • READ-COMMITTED(讀取提交內容
  • REPEATABLE-READ(可重讀)
  • SERIERLIZED(可串行化)

對於READ-UNCOMMITTED,可以讀取其他事務中未提交的數據,而且據說性能還高不到哪裏去,幾乎沒有在實際應用中使用;對於READ-COMMITTED,在同一事務中,會因爲其他事務隨時可能有新的commit,導致同一select可能返回不同結果。這個也不適合遊戲業務;再說第四個SERIERLIZED,只要事務開啓,所有其他查詢,均排隊等待該事務提交之後,對於上面提到的賣出買入情況,第二個事務的SELECT操作,不會立刻返回,會處於鎖等待狀態,一直到前一個事務結束。這個隔離級別,雖然能避免上面的問題,但性能較差,一般不會去使用。而REPEATABLE-READ隔離級別,也是mysql默認的隔離級別,從功能上,比較符合遊戲業務需要,也應該是廣大webgame架構中mysql的默認隔離級別。在使用REPEATABLE-READ隔離級別時,select的數據,都是事務未提交之前的數據,而每個事務都能正常成功執行,故錯誤的結果被執行出現。

對於這個問題,你可能很快就給出解決辦法,把UPDATE語句改爲UPDATE `role_gold` SET gold = gold + 100 WHERE role_id = 1或者UPDATE `role_gold` SET gold = 150 WHERE role_id = 1 AND gold = 100來解決,但這種多個事務同時操作修改多個表的多條記錄時,還容易引發死鎖問題,比如《webgame中Mysql Deadlock ERROR 1213 (40001)錯誤的排查歷程》。而且,當條件爲跨表內數據是否存在,或者另外條件不在MYSQL中,而在其他網絡接口的響應中時,如何做呢?

金幣複製--邏輯漏洞

引用DNF的漏洞新聞 《利用網遊漏洞狂刷遊戲幣賺錢 玩家自曝3天賺17萬》

玩家曝出刷幣漏洞 一個遊戲道具可刷400人民幣
該漏洞到底是什麼?原來遊戲中“雲冪袖珍罐”這個道具,可以開出2件一樣的遊戲裝備,還有極少機率開出遊戲幣,開出的裝備不值錢,但如果開出金幣了,則分爲5000萬、8000萬以及1億遊戲幣。而1億遊戲幣,按正常市場行情,可在交易網上賣400多元人民幣。據玩家稱,在遊戲中,角色的裝備是需要用包裹來存放的,不過目前角色的包裹最多隻有48格,也就是隻能存放最多48件裝備。漏洞就是利用包裹的有限空間,存放47件裝備(存放滿了又無法開罐子),只留下一格空位,而在開“雲冪袖珍罐”出裝備時,就會因包裹空間不足,而導致開罐失敗,而罐子還存在。玩家繼續開罐,直到出現金幣,但金幣不會佔據包裹的空間,因此開罐成功,然後罐子消失。發現這個漏洞後,部分玩家狂刷遊戲幣,然後馬上在第三方交易平臺出售遊戲幣,兌換成現金。

這種問題,都是研發人員邏輯不嚴謹導致,這種問題,也較難發現。規避方式可以依賴下面提到的『運營數據監控』。

道具複製--揹包整理

跟上面的賣出、買入一樣,同時穿裝備、整理包裹。在設計時,可能會將身上裝備設計在裝備表中;將不在身上的裝備,設計到揹包表中。當同時進行穿裝備跟整理包裹的請求併發時,也會發生跟上面賣出,買入的情況,線程1讀取DB,發現包裹裏有這裝備,然後準備刪除揹包表的這條記錄,當準備寫入到裝備表時,另外一個整理包裹請求的線程來了,讀取了整個揹包表,進行道具的合併、排序。這時,之前的線程將這個裝備寫入到裝備表,並刪除了揹包表裏的數據,並提交事務。這個穿裝備的所有操作都是合理、正常,且正確執行的。但另外一個整理揹包的線程讀取了之前的揹包表裏的數據,包括那件被穿上的裝備。在遊戲中,整理揹包需要對可堆疊道具做堆疊操作的,意味着需要合併多個道具,刪除部分道具。這意味着這裏的操作,當前cgi線程的內存中的數據,將都會以覆蓋的形式,寫入到DB中,那麼意味着,之前被穿到身上的那件裝備,也會重新被寫入到揹包中。那就變成兩張表裏出現了兩個相同唯一ID的相同屬性的道具。玩家就可以把揹包中的這個道具出售給其他玩家。

在java或者C之類程序中,數據放內存中的遊戲,也會存在這個問題,除非做讀鎖,但讀鎖會帶來鎖等待,鎖等待會導致線程被佔用,阻塞後面請求的處理,堆積大量請求。導致系統負載升高,服務器繁忙,以至於無法響應。好了,大約理解道具複製的形成原因了嗎?這個問題,我們從根本原因想想,問題到底出現在哪裏?如何規避呢?細心的同學不難發現,對於穿裝備的操作結果,會對下一個請求產生影響的,當前操作未得到服務端響應之前,服務端是不能處理下一個響應的。對此,我們做了響應處理鎖--『用戶併發請求鎖』。

用戶併發請求鎖的實現,php中session以文件形式存儲時,php會對session文件加鎖,不釋放(如果不特意執行session_write_close),知道當前響應完成。另外一個線程纔可以正常讀取,這簡介的形成了單個用戶的併發請求鎖,但是,後面的進程一直處於等待狀態,也會佔用一個php-fpm進程,阻塞其他用戶的正常請求對php線程的使用。爲此,我們使用NOSQL的K-V形式結構,以user_name爲key的形式,實現用戶併發請求鎖,比如 redis的setnx接口,原子性判斷操作有則返回false,,沒有就添加一個,返回true。那麼,對於下一個請求,setnx時,返回false,有這個key了,那麼立刻拋出異常,結束響應,FLASH根據異常內容,提醒用戶不要進行惡意操作。即不會發生併發請求,又不會阻塞請求處理。同時,在請求結束的析構函數裏,對這個鎖進行刪除操作,不影響下一個正常請求。若因爲程序異常,發生語法錯誤,導致析構函數沒法執行,沒有刪除用戶鎖時,可以在生成鎖的時候,設置過期時間,比如5秒,甚至2秒,利用nosql的過期機制,實現用戶解鎖,避免用戶長時間無法正常遊戲。

類CC攻擊-多用戶共享資源鎖的timebomb

我們現在研發的項目,是以NOSQL Redis作爲DB,來存儲數據的,redis並沒有成熟的事務處理機制,watch甚至算不上關係型數據庫中的事務處理。對此,更需要對錶進行加鎖解鎖。java之類語言的項目,很多都是直接操作內存的,更是需要資源鎖,來解決併發問題,解決多個請求操作同一份數據的問題。公司有另外一個項目,出現過一次因爲鎖的顆粒度較大,帶來的鎖等待timebomb的問題,也導致了線程繁阻塞忙,請求堆積,系統負載上升,導致宕機的問題。這個項目的鎖是針對所有用戶的鎖,每個用戶的請求發來時,當前線程會對所有用戶的數據加鎖,直到響應完成,才釋放掉。這麼做,是爲了解決因當前操作,會影響到其他用戶數據,比如多人PK,多個玩家之間的交互。
timebomb
當其他請求一併發來時,那麼資源會立刻被鎖住,直到上一個請求結束,才釋放鎖,那麼其他線程都處於等待狀態。用戶基數小時,是看不出來鎖帶來的影響的,內存操作都比較快。當用戶基數大時,或者說請求數增大時,後面的請求的等待時間會越來越長,超過webserver的等待時間,直接返回timeout,不能正常提供服務。

這種問題的發生,是因爲鎖的顆粒太大了,不應該將所有用戶都鎖住,最好細化到當前請求所影響到的單個用戶,只鎖住單個用戶的數據。這樣,才減少timebomb的發生。

其他

知乎裏的朋友提到,很多webgame 的前端做了判斷,而後端沒做判斷的問題,這種問題,實屬不該存在。在我們的項目中,後端做的驗證判斷,遠比前端多的多。有時候,爲了界面上的動畫表現,前端flash一般會在用戶操作之後,立刻渲染,然後,再根據後端響應,決定是否繼續做界面元素改動。比如脫裝備,玩家操作時,會先渲染裝備從角色面板,跳到揹包裏的動畫,然後,再根據後端響應結果,決定是否回滾動畫。這樣,避免顯得操作後,一定時間的反映遲鈍假象,以提高用戶體驗。當然,後端是一定會做判斷的,判斷角色揹包是否有空格之類。現在的webgame研發,一般都不會存在前端判斷,而後端不判斷的做法了。如果有,也應該是個別遺漏情況。

比如去年的time33算法的hash dos的問題,使用json消息格式的webgame一定要注意,php只是在接收請求時,做了最大數量的限制。但在json解碼之後的數據中,是沒有處理的。這裏千萬別忘記了。

運營數據異常監控

再完善的防禦措施,都仍會有安全漏洞。適當的監控措施,也一定要有,監控等級、金幣、遊戲幣、經驗、珍貴物品的變化等等,一旦發現,立刻報警,在漏洞未擴散之前,第一時間去修復漏洞,以減少損失,維護遊戲平衡。

日誌系統

日誌系統一定不能漏掉,所有操作,必須寫入日誌,當安全事件發生後,可以作爲各種數據回滾,交易糾紛處理的可靠數據。也是作爲數據監控的最準確的數據來源。

一個真實的故事。去年6月份,我們項目新上線一個系統,以及騰訊充值接口V2升級V3,涉及充值代碼改動,研發測試、策劃測試、QA測試完畢之後,上線到個別服務區,觀察情況。每次新版本上線,整個項目組都會持續觀察數據情況,尤其是充值總額, 10、50、100的漲,突然,總額下降了。充值總額下降了,這可是總額啊,只能增加,是不會下降的。肯定哪裏有BUG,DBA直接看binlog,查充值記錄相關的SQL語句,最後發現充值系統的sql語句爲UPDATE table set gold_num = $num,is_pay =1 ,沒有WHERE,沒有WHERE啊,多麼弱智的BUG,這尼瑪能容忍麼?肯定要拉出來,彈JJ彈到死。當我從SVN日誌中看到涉及這個文件的修改者時,我立刻石化了,悄悄的修了bug,默默的上傳......
UPDATE操作,忘記寫where

事情是發生了,原因很弱智。是我自己,忘記寫where條件,由於框架封裝了,問題並不容易被發現。而且自己測試充值都是正確的,包括後來的策劃測試、QA測試,都沒有發現問題。所屬功勞,就是“數據監控系統“,所以,我們遊戲開發商,第一時間,比用戶還早的發現了問題所在。數據監控,一定必不可少。

至於修復方案,那要感謝日誌系統。每筆充值,都有雙份日誌,一是各個遊戲大區自己的DB中。二是充值中心中。充值中心負責跟其他各大平臺對接。這次的事故,影響遊戲大區的數據,並未影響到充值中心數據,故可以有據可查。這樣,偉大的DBA可以更便捷、放心的修復數據了。對此,不管發生其他刷錢、刷裝備、盜號之後的交易糾紛,都可以依賴日誌來做處理。日誌系統,也一定必不可少。

從新功能發佈,到發現BUG,到修復BUG,總共歷時不到1小時(svn時間可以看出來,16:30對外的),可想而知,數據監控的效果多麼值得稱讚。新功能上線時,各個測試環節要去做。按大區服務器,做灰度發佈也可以更小的減少bug影響範圍,減少損失。

如果你用了我畫的小清新般的插圖,請記得爲圖片寫上署名來源,畫圖是最花費我時間的一件事。對了,看轉載的同學,建議過來看原文,一來原文的排版格式會更適合閱讀,二來原文會持續修正文中錯誤,增加內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章