情人節引起的血案

首先, 若是你能看到這句話,那我就應該恭喜你,你已經被此文的標題所吸引。不過,千萬不要想太多,此文不是什麼《今日說法》,但也與法有那麼一丁點的關係;此文也不是什麼《咱們約會吧》,約會自古與單身狗就無緣,況且此文的受衆僅僅是大齡屌絲單身程序員。程序員

等等等~~,先彆着急關,既然來都來了,就別太在乎本身是否是此文的受衆了,相信我,看完此文你必定有一種想罵人的感受,不過想罵的人應該不是我,至因而誰,我也不知道。只要不是我就行。算法

閒話少說,閒蛋少扯,如今跟着個人節奏進入正題吧,everybody,伸出你的雙手,讓我看到你~~數據庫

先來講說此文產生的背景吧。本猿已經好久沒有更新文章了,已經忘卻了寫東西的感受了,不過此次的經歷讓我以爲仍是有必要記錄下來與你們分享的。一方面,做爲***程序員(此處省略不少字),我們的工做就是開發安全、穩定、高效的應用,服務於我們的用戶。固然,做爲用戶,不能否認的是,每一個人都但願不須要動什麼腦筋,就能玩轉任何的應用。因此,問題就來了,若是程序員以爲用戶都不太想動腦子,而本身開發的時候老是想固然的話,那麼總有那麼一次,你會爲本身的想法買單。數組

2月14,本應該是個炮火連天的日子,而我也本應該浴血奮戰在槍林彈雨的第一線,可誰讓咱是個愛家,愛國,更愛工做的工做狂呢(boss,若是你看到了,記得偷偷給我加薪)。2月10號,好像腦子忽然短路了,興沖沖找到boss。安全

 

 

我:boss,情人節我們給我們的公衆號粉絲髮點福利吧。服務器

Boss:好。微信

我:須要花點錢。cookie

Boss:好。微信開發

我:額,你不問下須要花多少嗎?工具

Boss:好。

就怕空氣忽然安靜……

我:那我直接找財務了。

Boss:好。

好吧,有個這樣壕的boss真不知道是幸福呢仍是幸福呢。

 

好吧,忽然發現到這裏仍是沒有進入正文

 

----------------------------我是華麗的分割線-------------------------------------------

 

用一句話總結:情人節前夕,我閒的dan疼,自報奮勇發紅包給粉絲,而後就作了個助力發狗糧(狗糧只是噱頭,實際上是RMB啦)的活動,這裏稍微作下解釋,大概的意思是,我們給每一個粉絲髮個空碗(空紅包),而後粉絲拿着這個空碗去找朋友要狗糧(RMB),固然啦,這個狗糧確定不是粉絲的朋友出,粉絲的朋友只須要幫他點個按鈕,系統就會自動增長隨機金額的狗糧了,條件是他的朋友必須先關注咱們的公衆號,且滿一元才能兌換成RMB。因爲從提案到上線只有三天,再去掉週末(不要問我週末爲何不加班,沒錢但任性,哼~~),也就只有一天了。13號開始作,加班到凌晨,終於開發完畢,不過也就是簡單的測試了下。次日就上線了。

秀逗麻袋,好像忘記了什麼。好吧,這裏應該與上文呼應下(小學語文老師講過,好的文章要作到上下文呼應),在開頭的時候我講了,大概意思就是,程序員在開發的時候不能把用戶都當「傻子」,我們要把用戶都當成無孔不入的黑客,作好防範,這樣才能保證活動的真實性與公平性。

2月14日上午十一點發布,截止到中午1點也就漲了區區200多粉絲,發出去不到100塊的紅包,哎,有點小失望,心想如今這種活動你們都不感冒了呀。

到了下午兩點,差點嚇得生活不能自理,當時粉絲量以每秒5-8的速度增長,趕忙查人均成本,發現與預期差很少,也就稍微放了點心。而後再查下總金額,仍是嚇了一身冷汗,有人的紅包金額居然高達100多,可我限制了最大紅包說只能是50呀。而後又查了下代碼,仍是沒找到緣由,百思不得其解。萬不得已,只能把那幾我的所有屏蔽,額外加了個強制條件,即在更新紅包金額前,先判斷金額是否大於50,若是大於50,則不加了。提現的時候一樣的處理。大於50就只給提現50。就這樣,過了半個小時,也沒發現什麼大的紅包。直到下午5點的時候,有不少粉絲反饋提現失敗。遂進入商戶後臺查看,發現餘額已不足,趕忙找財務充錢,此時粉絲量已經增長了1w+了,而後又查了了數據,發現了好幾個50塊的紅包,而後又是各類屏蔽。可是當時已經快分不清哪些是真是的粉絲了。沒有辦法,最後在一個「業內人士」好心提醒下,不得已關閉了提現通道。那些惡意刷紅包的,看提現不了了,差很少心滿意足的走了。關閉提現通道後,粉絲依舊在增加,到晚上9點的時候淨增加了2.8w的粉絲。

好吧,至此,這場瘋狂的攻守之戰,以個人小勝而結束(但咱們也算是損失慘重)。2.15人工審覈了全部的紅包,將正常的粉絲的紅包一一的發了後,也就應該開始反省下此次活動帶給個人經驗教訓,儘管最終增加的粉絲量以及所消耗的成本基本是能夠接受的,但人均成本卻高了挺多,並且給忠實粉絲帶來了一些不便。發現的問題以下:

一、openid以明文保存在cookie中。

二、微信開發者模式沒有開啓加密模式。

三、沒有設置請求來源限制。

四、沒有限制必須真實的微信客戶端才能打開。

五、沒有使用https

六、客戶端提交信息沒有加密

七、時間問題。

大概也就上面這些了,下面再一一分析下,攻擊者是如何經過個人這些漏洞來攻擊個人系統的。

OpenId以明文保存在cookie

可能不少人看到這個會嘲笑說我活該,幹嗎要把OpenId保存在cookie中,並且還明文。先別急,且聽我慢慢道來。

作過公衆號開發的同窗應該都知道,訂閱號是沒有網頁受權的權限的,也沒有微信支付,更別提發微信紅包的接口權限了。而不巧的是,咱們要吸引關注的是個訂閱號,又要實現受權、發紅包的功能。個人作法是,使用服務號的接口獲取粉絲對於服務號的OpenId,而後再經過服務號的接口發紅包。可還有一個問題就是,怎樣使用這個服務號關聯的粉絲信息判斷是否關注了個人訂閱號呢?

 

 

嗨,那個一臉問號的你,對,就是你,想到了沒?沒想到怎麼解決吧。那我就告訴大家吧,記得待會兒給我發紅包。

UnionId,就是這個鬼。可能有些人作微信開發比較少,不是很理解。這裏我跟你們簡單說說。首先呢,上文說的OpenId其實就是微信分配給用戶的一個惟一標識,但這個惟一標識並非惟一的。是否是很拗口?哈哈,那就對了,其實這裏說的惟一隻是相對於某一個公衆號惟一,仍是沒聽懂嗎?好吧,舉個例子說,我有兩個公衆號A和B,另外我有一個微信號,假如我如今分別發消息給公衆號A和B,雖然都是同一個微信號發的,可是收到的信息裏的惟一標示確實不一樣的。由於惟一標示不同,因此根據OpenId來判斷多個公衆號裏的粉絲是不是同一個是無法實現的。再通俗點,咱們能夠把一個個公衆號想象成家與社會的關係。張三在家裏的名字多是‘小蘋果’、‘小櫻桃’之類的,由於家裏人都是喊小名,張三在公司裏上班的時候,同事可能就直接喊他‘張三’了,那怎麼區分小蘋果與張三的關係呢?或許你們都據說過身份證號這個東西(沒據說過的自行百度哦)。終於講到重點了,UnionId就能夠理解爲是微信號的身份證號。但只有把公衆號綁定到開放平臺纔會有這個屬性,而且多個公衆號必須綁定在同一個開放平臺,這我的的UnionId纔是惟一的。這就比如在張三在中國咱們能夠根據他的身份證號來判斷「張三」與「小蘋果」的關係,但他出了國後,老外可就不懂這個了。

好累呀,說是簡單的說下,結果寫了這麼一大段。

我們繼續日後看,如今個人作法基本明瞭了,就是經過服務號獲取用戶的openid和unionId,我事先會將全部已關注訂閱號的粉絲信息導入到數據庫,後面只要有新的關注也添加到數據庫,有取消關注的則將關注狀態改成0。因此,判斷一個用戶的是否關注訂閱號,我只須要直接根據unionId從數據庫獲取關注狀態就好了。

再說說,我爲何把openid和unionId以明文的方式保存在cookie中,且以明文的形式。上面說了,我是經過服務號獲取openid後,而後與訂閱號共享這個用戶信息,由於服務號自己有一套單獨的程序,因此想讓兩套程序共享cookie,我能想到的就是將兩套程序部署在同一個域名下,iis完美解決了這個問題。至於爲何以明文的方式,我能說的是,我想固然了,一方面時間緊,另外一方面我以爲加解密會影響效率,且我也想到別人拿到這openid也沒什麼用,因此就…。至於這個問題的優化方式,如今我給出個人解決方式:

首先,若是你須要將一些信息保存在cookie中,又擔憂安全的問題,那麼只須要在cookie中額外添加一個簽名。當黑客模擬請求,並篡改了cookie的內容時,因爲他們不知道我們的加密方式,因此提交給咱們服務器的cookie數據的簽名是有問題,咱們只須要在服務器端驗證簽名便可。下面是個人簽名算法,僅供參考,請根據本身的實際須要進行修改:

 

public static string DictionaryToSign(Dictionary<string, string> dic)

        {

            if (dic.Count<=1)

            {

                new Exception("集合中項的數量必須大於1,如須要簽名的參數爲1,可增長冗餘隨機數");

            }

            //第一步,將dic的鍵值經過=進行拼接,轉換成數組

            var arr = dic.Select(d => d.Key + "=" + d.Value).ToArray();

            //第二步,數組排序

            Array.Sort(arr);

            //第三步,獲取數組的長度,並獲取中值

            var length = arr.Length;

            var middleIndex = length%2 == 0 ? length/2 : (length/2) + 1;

            var middleValue = arr[middleIndex];

            //第四步,將中值進行base64編碼

            var base64key = GetCoding(middleValue);

            //第五步,以上一步生成的key分割,拼接數組爲字符串,獲得tempstr

            var tempstr = string.Join(base64key, arr);

            //第六步,將上一步獲得的tempstr先base64編碼,再md5,最後轉換成小寫,獲得最終的簽名

            return MD5(GetCoding(tempstr)).ToLower();

        }

 

 

使用的時候,只須要將cookie集合添加到集合中,生成簽名後,再額外將簽名添加到cookie中,最後,每次用戶的請求,都作下簽名驗證。

 

微信開發者模式沒有開啓加密模式

 

在開發公衆號時,作接入功能的時候,早期是沒有加密模式,惟一的安全點就是:token。由於微信接入時的算法你們都是知道的,有token以後,若是黑客不知道你的token,那麼就算知道了你的url,在驗證消息真實性的時候對方仍是不能獲得正確的簽名,因此token必須複雜點。像筆者這麼懶的人,也就吃一塹長一智吧。以下圖所示:

 

 

顯然,我沒有選擇明文模式,且token也是足夠簡單,黑客破解起來也是垂手可得的。另外,有一點不明白的是,黑客是怎麼知道我綁定的url的呢?這個抓包應該抓不了吧,全部的消息應該是經過微信服務器進行轉發的呀。費解,有知道的同窗過來交流下。

 

沒有設置請求來源限制。

 

這個失誤也是大意了,我在跟別人講課的時候特別強調過要加上這個,至關於給用於接收微信推送消息的服務又加了吧鎖。結合安全模式一塊兒,基本上不太可能會被攻破。下面詳細說下這個設置的詳細思路吧。

不知道你們有沒有注意過,在微信開發文檔有有個接口是獲取微信服務器IP地址。以下圖所示:

 

 

官方只是一句帶過,什麼機遇安全等考慮呀。哎,這文檔寫的太敷衍了。

就是這麼個鬼。在公衆號開啓了服務器配置後,消息的交互流程大概是這樣的:

微信客戶端→微信服務器→開發者服務器→微信服務器→微信客戶端。

看不懂的繼續往下面看:

首先,用戶在微信端給公衆號發消息,微信客戶端會將此消息推送給微信服務器,微信服務器處理後(加密)再對開發者服務器發送http請求,最後處理完成後,再按照來的路原路返回。因此,在微信用戶與公衆號交互時,直接跟開發者服務器交互的微信服務器,那麼咱們只須要在接收到請求時,判斷這個請求的來源ip,而後再經過獲取微信服務器ip接口,獲取微信服務器的ip,與這個來源ip進行匹配,匹配成功則表示是微信服務器請求的,則繼續處理,不然不處理請求。(目前,我還沒發現有什麼技術能夠仿造指定的ip進行請求)。

 

沒有限制必須真實的微信客戶端才能打開

 

這個請示微信受權連接是有限制的,但也只是最常規的限制,在網頁版微信中,仍是能夠走完受權的流程。有人說能夠用UA限制,但UA也是能夠仿冒的呀,感受也是沒什麼意義。

咱們可使用微信JSSDK來處理這個問題,JSSDK在配置成功後,有個ready接口,此接口是config信息驗證後會執行的ready方法,但這個ready目前僅能在微信客戶端和開發者工具中使用。因此能夠在頁面加載後,寫個定時器,好比延遲2秒,判斷下ready接口是否執行了,若是沒執行,則表示用戶不是在微信端打開的,則跳轉到一個錯誤提示頁面。假如,你以爲這還不夠安全,你還能夠經過UA來屏蔽用戶不許在pc客戶端的微信以及開發者工具中打開。具體怎麼判斷,從下圖相信你能找到答案。

 

 

 

 

沒有使用https

 

這個我就很少說了,https相對於http仍是比較安全的。相關的知識你們自行百度吧。至於怎麼配置https,今天的篇幅有點長了,下一篇我會專門發個專門介紹https配置的文章,敬請期待。

 

客戶端提交信息沒有加密

 

這個其實仍是比較重要的,以前作爬蟲的時候,發現百度和12306都是有相關的js加密的。原理就是在請求的參數中額外加個簽名的參數,這個簽名的參數是經過其餘參數根據必定的算法生成的,這個算法無非就是md5,base64,sha1等多個算法的組合。而後當收到請求時,服務器端使用與js相同的算法生成一個簽名,與用戶發送過來的簽名進行比較,相同則表示請求合法。須要注意的時,咱們在使用js寫算法生成簽名時,最好在發佈前對代碼進行壓縮,若是能夠的話,最後能稍微改下方法的命名,誠然,良好的命名習慣方便代碼的維護,但也方便黑客攻擊我們的系統,因此,我建議,在發佈的版本中,假如js有個md5運算,可能你的方法就相似於var md5=function(s){},能夠試試把改爲var sha1=function(s){}。方法體執行的代碼固然仍是md5,這樣作的目的只是爲了增長黑客的破解難度。

 

  時間問題

 

 

固然了, 以上說的這些均是基於你有充足的條件。假如boss在後面催着上線,哪怕你想到了,也沒那麼多精力來作這些事情。(個人boss沒催我,時間上確實來不及實現那麼多)。

 

 

好了,終於總結完了,人類的進步不就是從一次次的失敗,一次次的不完美中總結出來的嘛,因此,活到老,學到老,善於總結,方能成事。

 

 

╮(╯▽╰)╭,先彆着急關頁面呀,看到下面的二維碼了吧,關注不關注你看心情,反正我也不許備求你。

以爲本文能夠吐槽的話,有本事就發到朋友圈,讓全世界的朋友都來吐槽我吧。


 轉載請註明出處哦。

相關文章
相關標籤/搜索