當前位置: 華文問答 > 遊戲

網頁遊戲都有哪些安全問題?如何做得更安全?

2013-10-15遊戲

謝邀。。。。 壓根沒人邀請我,好像這個詞成為知乎的通用開篇詞語。


十月一的假期間,看到這個問題,當時沒怎麽在意。幾天後,在微博上,又看到這個問題,冥冥中,有種想回答的沖動。上周六時,研發部門內部周會時,聽到其他專案組的一個整型溢位問題,導致刷錢的bug。這更加堅定我要回答這個問題的決心,拯救地球的任務,就交給我了。以下是在下經歷過的webgame安全問題的經驗,定有不妥指出,還請各位斧正。如果覺得幫助了你,亦可分享給其他做webgame研發的朋友,做交流探討。

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

前面安全專家

@余弦

站在安全工作者的角度,回答了這個問題。下面我將以webgame研發者角度,切合遊戲業務模組邏輯,從業務需求,數據庫設計,程式編寫,操作方式上來講解漏洞形成原理,規避方案,也歡迎大家討論。


登入認證

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

遊戲充值

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

$signKey = array ( 'openid' , 'appid' , 'ts' , 'payitem' , 'token' , 'billno' , 'version' , 'zoneid' , 'providetype' , 'amt' , 'payamt_coins' , 'pubacct_payamt_coins' ); $sign = array (); //從GET參數中,對比找出上面參數的值 foreach ( $signKey as $key ) { if ( isset ( $data [ $key ])) { $sign [ $key ] = $data [ $key ]; //只有 GET裏有的參數,才參與sig的計算 } } ######開始生成簽名############ //1: URL編碼 URI $url = rawurlencode ( $url ); //2:按照key進行字典升序排列 ksort ( $sign ); //3: &拼接,並URL編碼 $arrQuery = array (); foreach ( $sign as $key => $val ) { $arrQuery [] = $key . '=' . str_replace ( '-' , '-' , $val ); } $query_string = join ( '&' , $arrQuery ); //4 以POST方式拼接 1、3 以及URL $src = 'GET&' . $url . '&' . rawurlencode ( $query_string ); // ## 構造金鑰 $key = $this -> config -> get ( 'qq_appkey' ) . '&' ; //### 生成簽名 $sig = base64_encode ( hash_hmac ( "sha1" , $src , strtr ( $key , '-_' , '+/' ), true )); if ( $sig != $data [ 'sig' ] ) { $return [ 'ret' ] = 4 ; $return [ 'msg' ] = '請求參數錯誤:(sig)' ; $this -> output -> set ( json_encode ( $return )); return ; }

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

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

遠端檔引入

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

// Load the local application controller // Note: The Router class automatically validates the controller path. If this include fails it // means that the default controller in the Routes.php file is not resolving to something valid. if ( ! file_exists ( APPROOT . 'controllers/' . load ( 'Router' ) -> getDirectory () . load ( 'Router' ) -> get class () . EXT )) { load ( 'Errors' ) -> show404 ( 'Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.' ); } include ( APPROOT . 'controllers/' . load ( 'Router' ) -> getDirectory () . load ( 'Router' ) -> get class () . EXT ); load ( 'Benchmark' ) -> mark ( 'load_basic_ class_time_end' );

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

require_once CONFIG_PATH . "/auto.php" ; spl_autoload_register ( "__autoload" ); ...... //預設訊息格式 $view -> clear (); $view -> error ( MLanguages :: COM__INVALID_REQUST ); $msg = new Afx_Amf_plugins_AcknowledgeMessage ( $val -> data [ 0 ] -> $messageIdField ); $msg -> setBody ( $view -> get ()); $message -> data = $msg ; ... $a = new Yaf_Request_Simple (); $a -> setControllerName ( $method [ 0 ]); $a -> setActionName ( $method [ 1 ]); $objC = new Reflection class ( $method [ 0 ] . "Controller" ); $arrParamenter = $objC -> getMethod ( $method [ 1 ] . "Action" ) -> getParameters (); $arrRequest = isset ( $val -> data [ 0 ] -> body [ 0 ]) ? ( array ) $val -> data [ 0 ] -> body [ 0 ] : array (); $bCanCall = true ; foreach ( $arrParamenter as $objParam ) { $parm = $objParam -> getName (); $bIsOption = $objParam -> isOptional (); //是否為可選參數 if ( isset ( $arrRequest [ $parm ])) { $a -> setParam ( $parm , $arrRequest [ $parm ]); } elseif ( $objParam -> isOptional ()) { //可選參數 } else { $bCanCall = false ; } } if ( $bCanCall ) { $rp = $app -> getDispatcher () -> dispatch ( $a ); $msg = new Afx_Amf_plugins_AcknowledgeMessage ( $val -> data [ 0 ] -> $messageIdField ); $msg -> setBody ( $view -> get ()); $message -> data = $msg ; }

SQL 註入

SQL註入原理、方式,跟普通web套用一樣,沒什麽特別的,在使用REQUEST來的參數時,過濾處理即可。可能在訊息格式,以及註入操作簡便上,會蒙蔽研發人員的眼睛,被忽略掉了。比如我們專案的AMF訊息格式,在前端界面沒出來之前,我們後端程式設計師一般使用Pinta來模擬操作,偵錯程式。前端界面出來之後,會使用Charles proxy來捕捉http請求。在這些過程中,請求介面、參數的構造,沒有普通web那麽簡單。研發人員也容易忽略對請求參數的過濾,故很容易形成這種問題。防禦方式做過濾處理,或SQL預編譯。

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作為所有業務傳輸的協定時,協定格式一般都是開源協定,比如msgpack、protobuf之類,或者自訂的協定。使用自訂協定時,務必檢測整個訊息包的每一個參數,類別範圍,避免個別超大數值、邊界數值出現,導致主程式記憶體越界,以至於服務宕機,無法正常服務的情況發生。

金幣復制-整型溢位

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

在java中,4字節的存放int型變量的範圍是-2147483648至2147483647。在java、c的有符號int型中儲存時,數的最高位描述符號位,4字節共32位元,除去最高位的符號位,剩下31位元,每個位上能表示2個數碼,4字節的有符號的整數表示範圍為:負整數2^31個,範圍為『-1至-2147483648』;正整數2^31個,範圍為『2147483647至1』。 比如下圖(註意十進制數碼跟二進制表示的變化順序):

當開啟格子數碼為大於31時,比如32,那麽所需費用就是2147483647個銀兩,再買點其他物品,湊成超過2147483647的數碼,比如又買了3個銀子的其他道具,總共花費2147483650個銀子,在4字節的有符號int中表示出來的結果,變成符號位為1,即負整數。數值位為0000000 00000000 00000000 00000010,也就是10000000 00000000 00000000 00000010,對應十進制的-2147483646。程式邏輯上,再判斷現有銀兩是否足夠支付此筆花費時,是透過的。當使用當前余額減去這筆花費時,將變成減去一個負數,那麽實際上就是加上一個正整數。變成了自己銀兩賬戶余額的增長。而余額欄位類別是long,則正確的儲存了這些余額,溢位漏洞被利用。在C中,使用無符號的數值類別,即可完成數值類別溢位刷錢的行為,但在java中,好像沒有無符號的類別。這也可以先確定所有參與計算的數值必須為正整數作為必要條件(遊戲業務特性,遊戲內所有數碼,肯定全為正整數,甚至都不包括零),先做大小判斷,再做正正相加,不能得負;負負相加,不能得正。來判斷是否發生了溢位問題。

金幣復制-並行請求

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

//賣出 // startTrans $iBalance = $obj -> getBalance ( 'user1' ); //余額50 //UPDATE `role_gold` SET gold = 150 WHERE role_id = 1 if ( ! $obj -> setBalance ( 'user1' , $iBalance + 100 )) { //rollback } //扣除物品 if ( ! $obj -> delItems ( $items )) { //rollback } //commit //買入 // startTrans $iBalance = $obj -> getBalance ( 'user1' ); //余額50 //UPDATE `role_gold` SET gold = 0 WHERE role_id = 1 if ( ! $obj -> setBalance ( 'user1' , $iBalance - 50 )) { //rollback } //發放物品 if ( ! $obj -> addItems ( $items )) { //rollback } //commit


賣出請求的處理行程為1,買入請求的處理行程為2。在行程1還沒將結果寫入到DB時,行程2也從DB讀取到余額為50。這是,兩個行程拿到的余額資訊都是50。行程1按照邏輯程式碼,計算出剩余余額是150;行程2計算出的剩余余額是0。最後,不管那個行程最後寫入余額,都是錯誤的結果。(註:這裏的程式碼邏輯操作,跟mysql事務無任何關系,事務只能保證單個行程的事務範圍內多條語句都正確執行,或回滾。比如能保證扣錢成功,且物品刪除掉的兩個語句都正確執行。能保證其中之一的語句執行失敗時,都正確回滾。)

其實,在事物開啟時候,SELECT語句是否可以取到最新的數據,或者是否需要等待鎖釋放,取決於MYSQL的事務隔離級別。在MYSQL的事務隔離級別中,有一下幾種隔離級別:

  1. READ-UNCOMMITTED(讀取未送出內容)級別
  2. READ-COMMITTED(讀取送出內容
  3. REPEATABLE-READ(可重讀)
  4. SERIERLIZED(可序列化)

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

對於這個問題,你可能很快就給出解決辦法,把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,多個玩家之間的互動。

當其他請求一並行來時,那麽資源會立刻被鎖住,直到上一個請求結束,才釋放鎖,那麽其他執行緒都處於等待狀態。使用者基數小時,是看不出來鎖帶來的影響的,記憶體操作都比較快。當使用者基數大時,或者說請求數增大時,後面的請求的等待時間會越來越長,超過webserver的等待時間,直接返回timeout,不能正常提供服務。

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

另外,

@余弦

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

其他

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

營運數據異常監控

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

--------------------------------10-16 21:08補充---------------------

日誌

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

--------------------------------10-18 16:05補充---------------------

補一個真實的故事。去年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,默默的上傳......

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

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

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

---------------------------------10月22日 更新------------------------------------------------

協定與外掛

本問題的目標是『網頁遊戲』,『網頁』意味著的用瀏覽器。使用瀏覽器與伺服器通訊的遊戲,才能成為瀏覽器。而瀏覽器支持普通的http協定的基本網頁數據通訊,支持以socket協定的flash外掛程式或者java Applet實作,以及unity3D開發的遊戲。就目前業界流行做法上,flash外掛程式做客戶端的占了一多半。unity3D的客戶端源碼安全性我不清楚。flash的源碼,眾所周知,很容易逆向出來。比如回答者

@Seraph

提到網誌

Seraph's Blog

中所寫方式逆向還原出flash源碼。當源碼很容易被拿到後,那麽通訊協定的訊息格式,也是一覽無余,那麽各種外掛實作起來,易如反掌。

在我們專案中,對於外掛的態度,一般都是放在最後考慮。當專案前期,沒有多少知名度,也沒盈利時,我們開發商是不會把精力放到防外掛上的。而且,天下攘攘,皆為利往,同樣的外掛編寫者也會認為這無利,會無視它。當然,也不是無視外掛的存在,我們一般這麽對待它:

  1. 策劃初期,減少重復性勞動的玩法,以免玩家感到疲勞,給與外掛市場。若重復性不高,那麽外掛可用性也就差。
  2. 遊戲本身提供一些內掛,替玩家實作重復性勞動。
  3. 前後端都是統一閘道器,後期實作協定加密也比較簡單,快捷。抵擋住初級外掛編寫者,當然,中國人才濟濟,大牛多多。
  4. 定時更換通訊金鑰,增加外掛編寫者的勞動量。
  5. 遊戲內部機制限制,比如體力值。
  6. 人肉判斷,驗證碼之類(網頁遊戲目前還沒火到這個地步)。

其實,網頁遊戲中的最大、最多的漏洞,幾乎都是遊戲業務金幣復制、裝備復制等技術性漏洞,以及遊戲業務判斷不嚴謹、條件不合法之類的邏輯漏洞,而常見的web漏洞在網頁遊戲裏,顯得並不突出,種類數量也少,也較為容易發現,修復。

=============================2014年7月22日更新================

我不知道這個回答會不會還有人看到,因為這不是一個新的回復,不會出現在各位的訊息提醒中,可能不會漲粉,但我覺得還是有必要在更新一下,希望以後透過搜尋,看到這篇文章的同學,對你們有幫助。SO,我還是更新一下。

又一起webgame中的刷道具的問題,問題發生在背包內道具移動的業務上:

  1. 當從A位置移動到B位置時,若兩者是同一類別,且可疊加(兩者都是繫結或不繫結),則疊加數量到其中一個上,並銷毀另外一個。
  2. 若不是同一類別,或不可疊加,則兩者呼喚位置。
  3. 若目標位置為空,則把物品從A移動到B。

如果這個功能由你看寫,你如何寫?

程式設計師的程式碼在

gist:b7d794d3cb663dd86aad

/** * * 背包內道具移動 * @param string $former 源位置格子號 * @param int $target 目標位置格子號 */ public function MoveItemAction ( $former , $target ) { $former = trim ( $former ); $target = intval ( $target ); if ( ! $former || $target < 1 ) { return $this -> _view -> error ( MLanguages :: COM__PARAMETERS_NOT_LEGAL ); //參數異常; } //獲取道具資訊 $former_item = $this -> __mItems -> getNumberItem ( $former ); if ( ! $former_item ) { return $this -> _view -> error ( MLanguages :: PACK__NOT_EXIST ); //使用道具不存在; } $former_md5id = $former_item [ 'md5_id' ]; $former_num = $former_item [ 'number' ]; $former_base = $former_item [ 'item_id' ]; $former_sup = $former_item [ 'superpose' ]; //開啟事務 $this -> __mRole -> beginTrans (); $target_item = $this -> __mItems -> getNumberItem ( $target ); ......


其出現問題部份就是如上這些,大家看程式碼時,會認為這前期的判斷都是正確的,符合業務邏輯的,問題卻發生了「來源格子」跟「目標格子」的判斷,沒有判斷這兩個參數是不是同一個位置,若是同一個位置,那麽上面的所有業務邏輯判斷都是合法的,下面的程式碼中,將根據這「兩個」道具的內容進行堆疊,然後就發生了道具復制的問題.....

修復方法想必大家都會,就是判斷來源位置目標位置是否相同

if ( ! $former || $target < 1 || ( $former == $target )) //判斷來源位置目標位置是否相同


如果是你,你會犯這樣的錯誤嗎?

以上為鄙人的遊戲研發經驗,歡迎討論。時間倉促,再加上感冒前期癥狀,輕微頭暈,難免思維混亂,敬請諒解。 此回答禁止轉載,程式碼高亮效果不理想,其他格式沒有。鄙人BLOG中文章

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

可以轉載,但務必遵循網誌所示協定。

@葛巾

大神能給個贊麽?

葛大神點贊了,居然看到了,居然點贊了。內牛滿面。