情人节引发的血案
首先,?如果你能看到這句話,那我就應(yīng)該恭喜你,你已經(jīng)被此文的標(biāo)題所吸引。不過,千萬不要想太多,此文不是什么《今日說法》,但也與法有那么一丁點的關(guān)系;此文也不是什么《我們約會吧》,約會自古與單身狗就無緣,何況此文的受眾僅僅是大齡屌絲單身程序員。
等等等~~,先別著急關(guān),既然來都來了,就別太在意自己是不是此文的受眾了,相信我,看完此文你一定有一種想罵人的感覺,不過想罵的人應(yīng)該不是我,至于是誰,我也不知道。只要不是我就行。
閑話少說,閑蛋少扯,現(xiàn)在跟著我的節(jié)奏進(jìn)入正題吧,everybody,伸出你的雙手,讓我看到你~~,
先來說說此文產(chǎn)生的背景吧。本猿已經(jīng)很久沒有更新文章了,已經(jīng)忘卻了寫東西的感覺了,不過這次的經(jīng)歷讓我覺得還是有必要記錄下來與大家分享的。一方面,作為***程序員(此處省略很多字),咱們的工作就是開發(fā)安全、穩(wěn)定、高效的應(yīng)用,服務(wù)于咱們的用戶。當(dāng)然,作為用戶,不可否認(rèn)的是,每個人都希望不需要動什么腦筋,就能玩轉(zhuǎn)任何的應(yīng)用。所以,問題就來了,如果程序員覺得用戶都不太想動腦子,而自己開發(fā)的時候總是想當(dāng)然的話,那么總有那么一次,你會為自己的想法買單。
2月14,本應(yīng)該是個炮火連天的日子,而我也本應(yīng)該浴血奮戰(zhàn)在槍林彈雨的第一線,可誰讓咱是個愛家,愛國,更愛工作的工作狂呢(boss,如果你看到了,記得偷偷給我加薪)。2月10號,好像腦子突然短路了,興沖沖找到boss。
?
?
我:boss,情人節(jié)咱們給咱們的公眾號粉絲發(fā)點福利吧。
Boss:好。
我:需要花點錢。
Boss:好。
我:額,你不問下需要花多少嗎?
Boss:好。
就怕空氣突然安靜……
我:那我直接找財務(wù)了。
Boss:好。
好吧,有個這樣壕的boss真不知道是幸福呢還是幸福呢。
?
好吧,突然發(fā)現(xiàn)到這里還是沒有進(jìn)入正文
?
----------------------------我是華麗的分割線-------------------------------------------
?
用一句話總結(jié):情人節(jié)前夕,我閑的dan疼,自報奮勇發(fā)紅包給粉絲,然后就做了個助力發(fā)狗糧(狗糧只是噱頭,其實是RMB啦)的活動,這里稍微做下解釋,大概的意思是,咱們給每個粉絲發(fā)個空碗(空紅包),然后粉絲拿著這個空碗去找朋友要狗糧(RMB),當(dāng)然啦,這個狗糧肯定不是粉絲的朋友出,粉絲的朋友只需要幫他點個按鈕,系統(tǒng)就會自動增加隨機(jī)金額的狗糧了,條件是他的朋友必須先關(guān)注我們的公眾號,且滿一元才能兌換成RMB。由于從提案到上線只有三天,再去掉周末(不要問我周末為什么不加班,沒錢但任性,哼~~),也就只有一天了。13號開始做,加班到凌晨,終于開發(fā)完畢,不過也就是簡單的測試了下。第二天就上線了。
秀逗麻袋,好像忘記了什么。好吧,這里應(yīng)該與上文呼應(yīng)下(小學(xué)語文老師講過,好的文章要做到上下文呼應(yīng)),在開頭的時候我講了,大概意思就是,程序員在開發(fā)的時候不能把用戶都當(dāng)“傻子”,咱們要把用戶都當(dāng)成無孔不入的黑客,做好防范,這樣才能保證活動的真實性與公平性。
2月14日上午十一點發(fā)布,截止到中午1點也就漲了區(qū)區(qū)200多粉絲,發(fā)出去不到100塊的紅包,哎,有點小失望,心想現(xiàn)在這種活動大家都不感冒了呀。
到了下午兩點,差點嚇得生活不能自理,當(dāng)時粉絲量以每秒5-8的速度增加,趕緊查人均成本,發(fā)現(xiàn)與預(yù)期差不多,也就稍微放了點心。然后再查下總金額,還是嚇了一身冷汗,有人的紅包金額竟然高達(dá)100多,可我限制了最大紅包說只能是50呀。然后又查了下代碼,還是沒找到原因,百思不得其解。萬不得已,只能把那幾個人全部屏蔽,額外加了個強制條件,即在更新紅包金額前,先判斷金額是否大于50,如果大于50,則不加了。提現(xiàn)的時候同樣的處理。大于50就只給提現(xiàn)50。就這樣,過了半個小時,也沒發(fā)現(xiàn)什么大的紅包。直到下午5點的時候,有很多粉絲反饋提現(xiàn)失敗。遂進(jìn)入商戶后臺查看,發(fā)現(xiàn)余額已不足,趕緊找財務(wù)充錢,此時粉絲量已經(jīng)增加了1w+了,然后又查了了數(shù)據(jù),發(fā)現(xiàn)了好幾個50塊的紅包,然后又是各種屏蔽。但是當(dāng)時已經(jīng)快分不清哪些是真是的粉絲了。沒有辦法,最后在一個“業(yè)內(nèi)人士”好心提醒下,不得已關(guān)閉了提現(xiàn)通道。那些惡意刷紅包的,看提現(xiàn)不了了,差不多心滿意足的走了。關(guān)閉提現(xiàn)通道后,粉絲依舊在增長,到晚上9點的時候凈增長了2.8w的粉絲。
好吧,至此,這場瘋狂的攻守之戰(zhàn),以我的小勝而結(jié)束(但我們也算是損失慘重)。2.15人工審核了所有的紅包,將正常的粉絲的紅包一一的發(fā)了后,也就應(yīng)該開始檢討下這次活動帶給我的經(jīng)驗教訓(xùn),盡管最終增長的粉絲量以及所消耗的成本基本是可以接受的,但人均成本卻高了挺多,而且給忠實粉絲帶來了一些不便。發(fā)現(xiàn)的問題如下:
1、openid以明文保存在cookie中。
2、微信開發(fā)者模式?jīng)]有開啟加密模式。
3、沒有設(shè)置請求來源限制。
4、沒有限制必須真實的微信客戶端才能打開。
5、沒有使用https
6、客戶端提交信息沒有加密
7、時間問題。
大概也就上面這些了,下面再一一分析下,攻擊者是如何通過我的這些漏洞來攻擊我的系統(tǒng)的。
OpenId以明文保存在cookie
可能很多人看到這個會嘲笑說我活該,干嘛要把OpenId保存在cookie中,而且還明文。先別急,且聽我慢慢道來。
做過公眾號開發(fā)的同學(xué)應(yīng)該都知道,訂閱號是沒有網(wǎng)頁授權(quán)的權(quán)限的,也沒有微信支付,更別提發(fā)微信紅包的接口權(quán)限了。而不巧的是,我們要吸引關(guān)注的是個訂閱號,又要實現(xiàn)授權(quán)、發(fā)紅包的功能。我的做法是,使用服務(wù)號的接口獲取粉絲對于服務(wù)號的OpenId,然后再通過服務(wù)號的接口發(fā)紅包。可還有一個問題就是,怎樣使用這個服務(wù)號關(guān)聯(lián)的粉絲信息判斷是否關(guān)注了我的訂閱號呢?
?
?
嗨,那個一臉問號的你,對,就是你,想到了沒?沒想到怎么解決吧。那我就告訴你們吧,記得待會兒給我發(fā)紅包。
UnionId,就是這個鬼。可能有些人做微信開發(fā)比較少,不是很理解。這里我跟大家簡單說說。首先呢,上文說的OpenId其實就是微信分配給用戶的一個唯一標(biāo)識,但這個唯一標(biāo)識并不是唯一的。是不是很拗口?哈哈,那就對了,其實這里說的唯一只是相對于某一個公眾號唯一,還是沒聽懂嗎?好吧,舉個例子說,我有兩個公眾號A和B,另外我有一個微信號,假如我現(xiàn)在分別發(fā)消息給公眾號A和B,雖然都是同一個微信號發(fā)的,但是收到的信息里的唯一標(biāo)示確實不同的。因為唯一標(biāo)示不一樣,所以根據(jù)OpenId來判斷多個公眾號里的粉絲是否是同一個是沒法實現(xiàn)的。再通俗點,我們可以把一個個公眾號想象成家與社會的關(guān)系。張三在家里的名字可能是‘小蘋果’、‘小櫻桃’之類的,因為家里人都是喊小名,張三在公司里上班的時候,同事可能就直接喊他‘張三’了,那怎么區(qū)分小蘋果與張三的關(guān)系呢?或許大家都聽說過身份證號這個東西(沒聽說過的自行百度哦)。終于講到重點了,UnionId就可以理解為是微信號的身份證號。但只有把公眾號綁定到開放平臺才會有這個屬性,并且多個公眾號必須綁定在同一個開放平臺,這個人的UnionId才是唯一的。這就好比在張三在中國我們可以根據(jù)他的身份證號來判斷“張三”與“小蘋果”的關(guān)系,但他出了國后,老外可就不懂這個了。
好累呀,說是簡單的說下,結(jié)果寫了這么一大段。
咱們繼續(xù)往后看,現(xiàn)在我的做法基本明了了,就是通過服務(wù)號獲取用戶的openid和unionId,我事先會將所有已關(guān)注訂閱號的粉絲信息導(dǎo)入到數(shù)據(jù)庫,后面只要有新的關(guān)注也添加到數(shù)據(jù)庫,有取消關(guān)注的則將關(guān)注狀態(tài)改為0。所以,判斷一個用戶的是否關(guān)注訂閱號,我只需要直接根據(jù)unionId從數(shù)據(jù)庫獲取關(guān)注狀態(tài)就行了。
再說說,我為什么把openid和unionId以明文的方式保存在cookie中,且以明文的形式。上面說了,我是通過服務(wù)號獲取openid后,然后與訂閱號共享這個用戶信息,因為服務(wù)號本身有一套單獨的程序,所以想讓兩套程序共享cookie,我能想到的就是將兩套程序部署在同一個域名下,iis完美解決了這個問題。至于為什么以明文的方式,我能說的是,我想當(dāng)然了,一方面時間緊,另一方面我覺得加解密會影響效率,且我也想到別人拿到這openid也沒什么用,所以就…。至于這個問題的優(yōu)化方式,現(xiàn)在我給出我的解決方式:
首先,如果你需要將一些信息保存在cookie中,又擔(dān)心安全的問題,那么只需要在cookie中額外添加一個簽名。當(dāng)黑客模擬請求,并篡改了cookie的內(nèi)容時,由于他們不知道咱們的加密方式,所以提交給我們服務(wù)器的cookie數(shù)據(jù)的簽名是有問題,我們只需要在服務(wù)器端驗證簽名即可。下面是我的簽名算法,僅供參考,請根據(jù)自己的實際需要進(jìn)行修改:
?
public static string DictionaryToSign(Dictionary<string, string> dic){if (dic.Count<=1){new Exception("集合中項的數(shù)量必須大于1,如需要簽名的參數(shù)為1,可增加冗余隨機(jī)數(shù)");}//第一步,將dic的鍵值通過=進(jìn)行拼接,轉(zhuǎn)換成數(shù)組var arr = dic.Select(d => d.Key + "=" + d.Value).ToArray();//第二步,數(shù)組排序 Array.Sort(arr);//第三步,獲取數(shù)組的長度,并獲取中值var length = arr.Length;var middleIndex = length%2 == 0 ? length/2 : (length/2) + 1;var middleValue = arr[middleIndex];//第四步,將中值進(jìn)行base64編碼var base64key = GetCoding(middleValue);//第五步,以上一步生成的key分割,拼接數(shù)組為字符串,得到tempstrvar tempstr = string.Join(base64key, arr);//第六步,將上一步得到的tempstr先base64編碼,再md5,最后轉(zhuǎn)換成小寫,得到最終的簽名return MD5(GetCoding(tempstr)).ToLower();}?
?
使用的時候,只需要將cookie集合添加到集合中,生成簽名后,再額外將簽名添加到cookie中,最后,每次用戶的請求,都做下簽名驗證。
?
微信開發(fā)者模式?jīng)]有開啟加密模式
?
在開發(fā)公眾號時,做接入功能的時候,早期是沒有加密模式,唯一的安全點就是:token。因為微信接入時的算法大家都是知道的,有token之后,如果黑客不知道你的token,那么就算知道了你的url,在驗證消息真實性的時候?qū)Ψ竭€是不能得到正確的簽名,所以token必須復(fù)雜點。像筆者這么懶的人,也就吃一塹長一智吧。如下圖所示:
?
?
顯然,我沒有選擇明文模式,且token也是足夠簡單,黑客破解起來也是輕而易舉的。另外,有一點不明白的是,黑客是怎么知道我綁定的url的呢?這個抓包應(yīng)該抓不了吧,所有的消息應(yīng)該是通過微信服務(wù)器進(jìn)行轉(zhuǎn)發(fā)的呀。費解,有知道的同學(xué)過來交流下。
?
沒有設(shè)置請求來源限制。
?
這個失誤也是大意了,我在跟別人講課的時候特別強調(diào)過要加上這個,相當(dāng)于給用于接收微信推送消息的服務(wù)又加了吧鎖。結(jié)合安全模式一起,基本上不太可能會被攻破。下面詳細(xì)說下這個設(shè)置的詳細(xì)思路吧。
不知道大家有沒有注意過,在微信開發(fā)文檔有有個接口是獲取微信服務(wù)器IP地址。如下圖所示:
?
?
官方只是一句帶過,什么機(jī)遇安全等考慮呀。哎,這文檔寫的太敷衍了。
就是這么個鬼。在公眾號開啟了服務(wù)器配置后,消息的交互流程大概是這樣的:
微信客戶端→微信服務(wù)器→開發(fā)者服務(wù)器→微信服務(wù)器→微信客戶端。
看不懂的繼續(xù)往下面看:
首先,用戶在微信端給公眾號發(fā)消息,微信客戶端會將此消息推送給微信服務(wù)器,微信服務(wù)器處理后(加密)再對開發(fā)者服務(wù)器發(fā)送http請求,最后處理完成后,再按照來的路原路返回。所以,在微信用戶與公眾號交互時,直接跟開發(fā)者服務(wù)器交互的微信服務(wù)器,那么我們只需要在接收到請求時,判斷這個請求的來源ip,然后再通過獲取微信服務(wù)器ip接口,獲取微信服務(wù)器的ip,與這個來源ip進(jìn)行匹配,匹配成功則表示是微信服務(wù)器請求的,則繼續(xù)處理,否則不處理請求。(目前,我還沒發(fā)現(xiàn)有什么技術(shù)可以仿造指定的ip進(jìn)行請求)。
?
沒有限制必須真實的微信客戶端才能打開
?
這個請示微信授權(quán)鏈接是有限制的,但也只是最常規(guī)的限制,在網(wǎng)頁版微信中,還是可以走完授權(quán)的流程。有人說可以用UA限制,但UA也是可以仿冒的呀,感覺也是沒什么意義。
我們可以使用微信JSSDK來處理這個問題,JSSDK在配置成功后,有個ready接口,此接口是config信息驗證后會執(zhí)行的ready方法,但這個ready目前僅能在微信客戶端和開發(fā)者工具中使用。所以可以在頁面加載后,寫個定時器,比如延遲2秒,判斷下ready接口是否執(zhí)行了,如果沒執(zhí)行,則表示用戶不是在微信端打開的,則跳轉(zhuǎn)到一個錯誤提示頁面。假如,你覺得這還不夠安全,你還可以通過UA來屏蔽用戶不準(zhǔn)在pc客戶端的微信以及開發(fā)者工具中打開。具體怎么判斷,從下圖相信你能找到答案。
?
?
?
?
沒有使用https
?
這個我就不多說了,https相對于http還是比較安全的。相關(guān)的知識大家自行百度吧。至于怎么配置https,今天的篇幅有點長了,下一篇我會專門發(fā)個專門介紹https配置的文章,敬請期待。
?
客戶端提交信息沒有加密
?
這個其實還是比較重要的,之前做爬蟲的時候,發(fā)現(xiàn)百度和12306都是有相關(guān)的js加密的。原理就是在請求的參數(shù)中額外加個簽名的參數(shù),這個簽名的參數(shù)是通過其他參數(shù)根據(jù)一定的算法生成的,這個算法無非就是md5,base64,sha1等多個算法的組合。然后當(dāng)收到請求時,服務(wù)器端使用與js相同的算法生成一個簽名,與用戶發(fā)送過來的簽名進(jìn)行比較,相同則表示請求合法。需要注意的時,我們在使用js寫算法生成簽名時,最好在發(fā)布前對代碼進(jìn)行壓縮,如果可以的話,最后能稍微改下方法的命名,誠然,良好的命名習(xí)慣方便代碼的維護(hù),但也方便黑客攻擊咱們的系統(tǒng),所以,我建議,在發(fā)布的版本中,假如js有個md5運算,可能你的方法就類似于var md5=function(s){},可以試試把改成var sha1=function(s){}。方法體執(zhí)行的代碼當(dāng)然還是md5,這樣做的目的只是為了增加黑客的破解難度。
?
? 時間問題
?
?
當(dāng)然了, 以上說的這些均是基于你有充足的條件。假如boss在后面催著上線,哪怕你想到了,也沒那么多精力來做這些事情。(我的boss沒催我,時間上確實來不及實現(xiàn)那么多)。
?
?
好了,終于總結(jié)完了,人類的進(jìn)步不就是從一次次的失敗,一次次的不完美中總結(jié)出來的嘛,所以,活到老,學(xué)到老,善于總結(jié),方能成事。
?
?
╮(╯▽╰)╭,先別著急關(guān)頁面呀,看到下面的二維碼了吧,關(guān)注不關(guān)注你看心情,反正我也不準(zhǔn)備求你。
覺得本文可以吐槽的話,有本事就發(fā)到朋友圈,讓全世界的朋友都來吐槽我吧。
?轉(zhuǎn)載請注明出處哦。
轉(zhuǎn)載于:https://www.cnblogs.com/zskbll/p/6405801.html
總結(jié)
- 上一篇: Spring3系列7- 自动扫描组件或B
- 下一篇: 桌面整理工具 Stardock Fenc