業(yè)數(shù)字化爆火背后:餐飲小程序與儲(chǔ)值系統(tǒng)的安全漏洞及低成本防護(hù))
1. 從一只蝦到一條鏈爆火背后被忽視的攻擊面小龍蝦這個(gè)品類(lèi)在過(guò)去兩年里經(jīng)歷了一輪罕見(jiàn)的爆發(fā)式增長(zhǎng)。從街邊大排檔到連鎖品牌從線下堂食到預(yù)制菜電商整條產(chǎn)業(yè)鏈在極短時(shí)間內(nèi)完成了數(shù)字化改造。點(diǎn)單小程序、會(huì)員系統(tǒng)、供應(yīng)鏈管理平臺(tái)、直播帶貨后臺(tái)、外賣(mài)聚合接單系統(tǒng)——這些工具在半年內(nèi)被倉(cāng)促塞進(jìn)了原本只靠一部電話和一本手寫(xiě)賬本就能運(yùn)轉(zhuǎn)的生意里。我之所以關(guān)注這個(gè)話題是因?yàn)樯磉吅脦讉€(gè)做餐飲SaaS的朋友都在聊同一件事客戶(hù)催著上線安全評(píng)審基本為零。一個(gè)典型的場(chǎng)景是某連鎖品牌為了趕在旺季前上線會(huì)員儲(chǔ)值功能從立項(xiàng)到上線只用了十一天。十一天里前端調(diào)通了支付接上了數(shù)據(jù)庫(kù)建好了但沒(méi)有人問(wèn)過(guò)一句這個(gè)系統(tǒng)的接口有沒(méi)有做頻率限制用戶(hù)手機(jī)號(hào)在數(shù)據(jù)庫(kù)里是明文還是加密后臺(tái)管理頁(yè)面的登錄態(tài)是怎么維持的這不是個(gè)別現(xiàn)象。當(dāng)一個(gè)行業(yè)突然涌入大量熱錢(qián)和流量時(shí)技術(shù)團(tuán)隊(duì)的第一優(yōu)先級(jí)永遠(yuǎn)是能不能跑起來(lái)而不是跑起來(lái)之后會(huì)不會(huì)被人從后面踹一腳。小龍蝦產(chǎn)業(yè)的數(shù)字化恰恰處在這個(gè)階段——業(yè)務(wù)跑得比安全快而攻擊者最喜歡的就是這種時(shí)間差。這篇文章想聊的不是某個(gè)具體漏洞的利用教程而是從從業(yè)者視角出發(fā)把這類(lèi)爆火行業(yè)數(shù)字化中反復(fù)出現(xiàn)的幾類(lèi)安全問(wèn)題拆開(kāi)來(lái)看。我會(huì)講清楚每一類(lèi)問(wèn)題的成因、攻擊者通常從哪個(gè)角度切入、以及在實(shí)際項(xiàng)目中可以怎么低成本地堵住這些口子。適合正在做餐飲/零售類(lèi)系統(tǒng)的開(kāi)發(fā)者、剛接手這類(lèi)項(xiàng)目的技術(shù)負(fù)責(zé)人以及對(duì)業(yè)務(wù)安全感興趣的讀者參考。2. 點(diǎn)單小程序里的數(shù)據(jù)暴露從手機(jī)號(hào)到儲(chǔ)值余額2.1 為什么這類(lèi)系統(tǒng)天生容易泄露數(shù)據(jù)餐飲類(lèi)小程序的開(kāi)發(fā)節(jié)奏和傳統(tǒng)企業(yè)軟件完全不同。傳統(tǒng)企業(yè)軟件有明確的需求文檔、測(cè)試周期和上線評(píng)審而餐飲小程序往往是老板今天說(shuō)要加個(gè)功能明天就要看到效果。這種節(jié)奏下開(kāi)發(fā)者的注意力全部集中在功能實(shí)現(xiàn)上數(shù)據(jù)安全往往被默認(rèn)成框架會(huì)幫我處理。但框架不會(huì)幫你處理業(yè)務(wù)層面的數(shù)據(jù)暴露。舉個(gè)最常見(jiàn)的例子用戶(hù)在小程序里查看自己的訂單詳情前端發(fā)起一個(gè)請(qǐng)求后端返回一個(gè)包含訂單信息的JSON。如果這個(gè)接口的設(shè)計(jì)是傳入訂單ID返回訂單詳情而沒(méi)有校驗(yàn)這個(gè)訂單ID是否屬于當(dāng)前登錄用戶(hù)那么任何人只要把訂單ID改一個(gè)數(shù)字就能看到別人的訂單——包括姓名、手機(jī)號(hào)、配送地址、消費(fèi)金額。這類(lèi)問(wèn)題在安全圈里叫越權(quán)訪問(wèn)分為水平越權(quán)和垂直越權(quán)兩種。水平越權(quán)是同級(jí)用戶(hù)之間的數(shù)據(jù)互訪比如A用戶(hù)能看到B用戶(hù)的訂單垂直越權(quán)是低權(quán)限用戶(hù)訪問(wèn)高權(quán)限功能比如普通用戶(hù)能進(jìn)管理后臺(tái)。餐飲類(lèi)系統(tǒng)里水平越權(quán)是最常見(jiàn)的因?yàn)橛唵尾樵?xún)、會(huì)員信息查詢(xún)、優(yōu)惠券領(lǐng)取這些功能天然就是按ID取數(shù)據(jù)的模式。2.2 一個(gè)真實(shí)的排查思路假設(shè)你接手了一個(gè)已經(jīng)上線的點(diǎn)單系統(tǒng)想快速判斷它有沒(méi)有越權(quán)問(wèn)題可以按下面的順序過(guò)一遍列出所有傳入ID返回?cái)?shù)據(jù)的接口。打開(kāi)瀏覽器的開(kāi)發(fā)者工具在小程序里把每個(gè)頁(yè)面都點(diǎn)一遍記錄下所有請(qǐng)求URL和參數(shù)。重點(diǎn)關(guān)注包含id、orderId、userId、memberId這類(lèi)參數(shù)的接口。用兩個(gè)賬號(hào)做交叉測(cè)試。注冊(cè)兩個(gè)普通賬號(hào)分別下單然后用賬號(hào)A的登錄態(tài)去請(qǐng)求賬號(hào)B的訂單ID。如果返回了數(shù)據(jù)說(shuō)明存在水平越權(quán)。檢查返回字段。即使接口做了歸屬校驗(yàn)也要看返回的字段里有沒(méi)有多余信息。比如訂單詳情接口返回了用戶(hù)的完整手機(jī)號(hào)但前端只展示了后四位——那這個(gè)完整手機(jī)號(hào)就是多余的暴露面。檢查批量接口。有些系統(tǒng)為了減少請(qǐng)求次數(shù)會(huì)提供批量查詢(xún)接口比如傳入一個(gè)ID數(shù)組返回多條數(shù)據(jù)。這類(lèi)接口如果沒(méi)做逐條歸屬校驗(yàn)危害比單條查詢(xún)更大。注意做這類(lèi)測(cè)試時(shí)務(wù)必使用自己注冊(cè)的測(cè)試賬號(hào)不要嘗試訪問(wèn)真實(shí)用戶(hù)的數(shù)據(jù)。未經(jīng)授權(quán)的測(cè)試可能觸犯法律。2.3 修復(fù)方案與成本評(píng)估越權(quán)問(wèn)題的修復(fù)其實(shí)不復(fù)雜核心原則就一條每一個(gè)數(shù)據(jù)查詢(xún)都必須帶上當(dāng)前登錄用戶(hù)的身份條件。具體到代碼層面就是把WHERE id ?改成WHERE id ? AND user_id ?。如果用的是ORM框架就是在查詢(xún)條件里強(qiáng)制附加當(dāng)前用戶(hù)的ID。但實(shí)際項(xiàng)目中這個(gè)修復(fù)往往會(huì)遇到阻力。原因在于有些接口在設(shè)計(jì)之初就沒(méi)有考慮用戶(hù)維度比如根據(jù)訂單號(hào)查詢(xún)訂單這個(gè)功能可能是給客服用的客服需要能查任何訂單。這種情況下正確的做法是把接口拆成兩個(gè)面向用戶(hù)的接口強(qiáng)制帶用戶(hù)ID面向客服的接口走獨(dú)立的權(quán)限校驗(yàn)。成本方面一個(gè)中等規(guī)模的餐飲小程序大概有30到50個(gè)接口其中涉及數(shù)據(jù)查詢(xún)的可能有20個(gè)左右。如果代碼結(jié)構(gòu)清晰一個(gè)熟練的開(kāi)發(fā)者大概需要兩到三天完成排查和修復(fù)。如果代碼是外包做的、結(jié)構(gòu)混亂時(shí)間可能要翻倍。但相比數(shù)據(jù)泄露后的公關(guān)成本和監(jiān)管處罰這個(gè)投入是值得的。3. 儲(chǔ)值余額與優(yōu)惠券黑產(chǎn)眼中的高價(jià)值目標(biāo)3.1 為什么儲(chǔ)值功能是重災(zāi)區(qū)小龍蝦品類(lèi)的客單價(jià)不低很多品牌會(huì)推儲(chǔ)值優(yōu)惠來(lái)鎖定復(fù)購(gòu)。比如充500送100用戶(hù)賬戶(hù)里就有了600元的余額。這個(gè)余額在系統(tǒng)里就是一個(gè)數(shù)字字段而這個(gè)數(shù)字字段的讀寫(xiě)邏輯往往就是黑產(chǎn)重點(diǎn)關(guān)照的對(duì)象。我見(jiàn)過(guò)最離譜的一個(gè)案例是某品牌的儲(chǔ)值系統(tǒng)在充值回調(diào)接口里沒(méi)有做簽名校驗(yàn)。也就是說(shuō)支付平臺(tái)通知用戶(hù)充值成功的那個(gè)請(qǐng)求任何人都可以偽造。攻擊者只需要構(gòu)造一個(gè)包含用戶(hù)ID和充值金額的請(qǐng)求發(fā)給服務(wù)器賬戶(hù)余額就直接加上去了。這個(gè)漏洞從上線到被發(fā)現(xiàn)中間隔了將近兩個(gè)月?lián)p失的具體金額沒(méi)有對(duì)外公布但據(jù)說(shuō)足夠讓技術(shù)負(fù)責(zé)人直接走人。即使做了簽名校驗(yàn)還有一類(lèi)問(wèn)題叫并發(fā)競(jìng)爭(zhēng)。比如用戶(hù)賬戶(hù)里有100元余額同時(shí)發(fā)起兩筆80元的消費(fèi)請(qǐng)求。如果系統(tǒng)在處理時(shí)是先讀余額、判斷夠不夠、再扣減那么兩個(gè)請(qǐng)求可能同時(shí)讀到100元都判斷為夠然后各自扣減80元最終余額變成-60元。這就是典型的競(jìng)態(tài)條件漏洞。3.2 支付回調(diào)的安全設(shè)計(jì)要點(diǎn)支付回調(diào)是儲(chǔ)值系統(tǒng)的命門(mén)設(shè)計(jì)時(shí)至少要滿足以下幾點(diǎn)簽名驗(yàn)證支付平臺(tái)的回調(diào)請(qǐng)求必須攜帶簽名服務(wù)端要用約定的密鑰驗(yàn)證簽名。簽名不通過(guò)的請(qǐng)求直接丟棄不要返回任何業(yè)務(wù)信息。金額校驗(yàn)回調(diào)里的金額必須和訂單創(chuàng)建時(shí)的金額一致。有些系統(tǒng)只校驗(yàn)訂單號(hào)不校驗(yàn)金額攻擊者可以把1元充值的回調(diào)改成1000元。冪等處理同一個(gè)回調(diào)可能被重復(fù)發(fā)送系統(tǒng)必須保證同一筆訂單只處理一次。通常的做法是在數(shù)據(jù)庫(kù)里記錄已處理的回調(diào)ID處理前先查重。異步確認(rèn)不要依賴(lài)前端跳轉(zhuǎn)結(jié)果來(lái)判定支付成功必須以服務(wù)端收到的回調(diào)為準(zhǔn)。前端跳轉(zhuǎn)可以被偽造服務(wù)端回調(diào)相對(duì)可靠。3.3 優(yōu)惠券系統(tǒng)的常見(jiàn)邏輯漏洞優(yōu)惠券的問(wèn)題通常不在技術(shù)層面而在業(yè)務(wù)邏輯層面。比如無(wú)限領(lǐng)取領(lǐng)券接口沒(méi)有做頻率限制也沒(méi)有校驗(yàn)用戶(hù)是否已領(lǐng)過(guò)。攻擊者寫(xiě)個(gè)腳本就能把一批券全部領(lǐng)走。疊加使用系統(tǒng)允許同一訂單使用多張優(yōu)惠券但沒(méi)有限制疊加規(guī)則。攻擊者通過(guò)組合不同券種可以把訂單金額壓到極低甚至負(fù)數(shù)。過(guò)期券復(fù)用券過(guò)期后狀態(tài)沒(méi)有及時(shí)更新或者更新了但核銷(xiāo)接口沒(méi)有校驗(yàn)狀態(tài)導(dǎo)致過(guò)期券仍可使用。退款不退券用戶(hù)用券下單后申請(qǐng)退款系統(tǒng)退了錢(qián)但沒(méi)有收回優(yōu)惠券用戶(hù)相當(dāng)于白賺一張券。這些問(wèn)題的共同點(diǎn)是它們不是代碼寫(xiě)錯(cuò)了而是業(yè)務(wù)規(guī)則沒(méi)有想清楚。修復(fù)方式通常是在關(guān)鍵操作上加校驗(yàn)、加限制、加日志。比如領(lǐng)券接口加用戶(hù)維度的頻率限制核銷(xiāo)接口加券狀態(tài)和有效期的雙重校驗(yàn)退款流程里加券的回收邏輯。4. 供應(yīng)鏈與后臺(tái)系統(tǒng)被低估的入口4.1 后臺(tái)管理頁(yè)面的弱口令問(wèn)題前臺(tái)小程序的安全做得再好如果后臺(tái)管理頁(yè)面用的是admin/123456那一切努力都白費(fèi)。餐飲行業(yè)的后臺(tái)系統(tǒng)有個(gè)特點(diǎn)使用者往往是不懂技術(shù)的店長(zhǎng)或運(yùn)營(yíng)人員他們傾向于設(shè)置簡(jiǎn)單好記的密碼而且多個(gè)系統(tǒng)共用同一個(gè)密碼。更麻煩的是很多后臺(tái)系統(tǒng)的登錄頁(yè)面直接暴露在公網(wǎng)上沒(méi)有做IP白名單也沒(méi)有做登錄失敗鎖定。攻擊者可以用自動(dòng)化工具對(duì)后臺(tái)登錄接口進(jìn)行撞庫(kù)成功率遠(yuǎn)比想象中高。我建議的做法是后臺(tái)系統(tǒng)盡量不直接暴露在公網(wǎng)通過(guò)內(nèi)網(wǎng)訪問(wèn)或加一層訪問(wèn)控制。強(qiáng)制密碼復(fù)雜度至少8位包含大小寫(xiě)字母和數(shù)字。登錄失敗5次后鎖定賬戶(hù)15分鐘或者引入圖形驗(yàn)證碼。對(duì)后臺(tái)的所有操作記錄審計(jì)日志包括登錄、查詢(xún)、修改、導(dǎo)出。4.2 供應(yīng)鏈數(shù)據(jù)的敏感性小龍蝦供應(yīng)鏈涉及采購(gòu)價(jià)格、供應(yīng)商信息、庫(kù)存數(shù)量、物流軌跡等數(shù)據(jù)。這些數(shù)據(jù)在競(jìng)爭(zhēng)激烈的餐飲行業(yè)里屬于核心商業(yè)機(jī)密。如果被競(jìng)爭(zhēng)對(duì)手拿到可以直接推算出你的成本結(jié)構(gòu)和采購(gòu)渠道。供應(yīng)鏈系統(tǒng)的安全重點(diǎn)和前臺(tái)不同它更關(guān)注訪問(wèn)控制和數(shù)據(jù)脫敏。比如采購(gòu)價(jià)格字段只對(duì)采購(gòu)角色可見(jiàn)運(yùn)營(yíng)角色只能看到庫(kù)存數(shù)量。供應(yīng)商聯(lián)系方式在非必要場(chǎng)景下脫敏展示。數(shù)據(jù)導(dǎo)出功能加審批流程防止內(nèi)部人員批量導(dǎo)出后帶走。這些措施聽(tīng)起來(lái)簡(jiǎn)單但在實(shí)際項(xiàng)目中往往被忽略因?yàn)橄到y(tǒng)上線時(shí)通常只考慮了功能能不能用沒(méi)有考慮誰(shuí)能用哪些功能。4.3 第三方接口的信任邊界餐飲系統(tǒng)通常會(huì)對(duì)接多個(gè)第三方服務(wù)支付、短信、地圖、外賣(mài)平臺(tái)、電子發(fā)票等。每對(duì)接一個(gè)第三方就多一個(gè)外部入口。如果第三方接口的調(diào)用沒(méi)有做鑒權(quán)或者回調(diào)地址暴露在外就可能被利用。一個(gè)常見(jiàn)的疏忽是短信接口沒(méi)有做頻率限制。攻擊者發(fā)現(xiàn)你的短信發(fā)送接口后可以寫(xiě)腳本瘋狂調(diào)用導(dǎo)致你的短信費(fèi)用暴漲同時(shí)用戶(hù)被騷擾。修復(fù)方式是在發(fā)送短信前加圖形驗(yàn)證碼并對(duì)同一手機(jī)號(hào)、同一IP做頻率限制。另一個(gè)疏忽是外賣(mài)平臺(tái)的訂單回調(diào)沒(méi)有做來(lái)源校驗(yàn)。攻擊者可以偽造訂單回調(diào)在你的系統(tǒng)里生成虛假訂單干擾正常運(yùn)營(yíng)。修復(fù)方式是校驗(yàn)回調(diào)請(qǐng)求的簽名或來(lái)源IP。5. 從應(yīng)急到常態(tài)小團(tuán)隊(duì)的安全建設(shè)路徑5.1 先做資產(chǎn)梳理再談防護(hù)很多小團(tuán)隊(duì)一提到安全建設(shè)第一反應(yīng)是買(mǎi)個(gè)WAF或者上個(gè)堡壘機(jī)。但在不知道自己要保護(hù)什么的情況下買(mǎi)再多設(shè)備也是浪費(fèi)。第一步應(yīng)該是資產(chǎn)梳理把所有的域名、服務(wù)器、數(shù)據(jù)庫(kù)、接口、第三方服務(wù)都列出來(lái)標(biāo)注每個(gè)資產(chǎn)的重要程度和負(fù)責(zé)人。資產(chǎn)梳理的產(chǎn)出是一張表大概長(zhǎng)這樣資產(chǎn)類(lèi)型具體資產(chǎn)重要程度負(fù)責(zé)人當(dāng)前防護(hù)措施域名小程序API域名高后端A無(wú)服務(wù)器生產(chǎn)環(huán)境應(yīng)用服務(wù)器高運(yùn)維B基礎(chǔ)防火墻數(shù)據(jù)庫(kù)用戶(hù)數(shù)據(jù)庫(kù)高后端A無(wú)審計(jì)接口支付回調(diào)接口高后端A簽名校驗(yàn)第三方短信服務(wù)中后端C無(wú)頻率限制這張表不需要很精確但必須要有。有了它你才能判斷哪些地方需要優(yōu)先投入。5.2 用最低成本堵住最大的洞對(duì)于資源有限的小團(tuán)隊(duì)安全建設(shè)應(yīng)該遵循二八原則用20%的投入解決80%的風(fēng)險(xiǎn)。根據(jù)我的經(jīng)驗(yàn)以下幾項(xiàng)是性?xún)r(jià)比最高的接口鑒權(quán)全覆蓋確保每一個(gè)需要登錄才能訪問(wèn)的接口都做了身份校驗(yàn)每一個(gè)數(shù)據(jù)查詢(xún)都帶了用戶(hù)歸屬條件。這一項(xiàng)能擋住大部分越權(quán)問(wèn)題。關(guān)鍵操作加日志登錄、支付、退款、修改密碼、導(dǎo)出數(shù)據(jù)這些操作必須記錄日志包括操作人、時(shí)間、IP、操作內(nèi)容。出問(wèn)題時(shí)日志是唯一的追溯依據(jù)。數(shù)據(jù)庫(kù)定期備份備份要離線保存并且定期驗(yàn)證可恢復(fù)性。很多團(tuán)隊(duì)做了備份但從來(lái)沒(méi)恢復(fù)過(guò)真出事時(shí)才發(fā)現(xiàn)備份是壞的。依賴(lài)組件及時(shí)更新框架、中間件、第三方庫(kù)的已知漏洞是攻擊者的首選目標(biāo)。建立一個(gè)簡(jiǎn)單的依賴(lài)清單定期檢查更新。5.3 把安全寫(xiě)進(jìn)開(kāi)發(fā)流程安全問(wèn)題的根源往往不在技術(shù)而在流程。如果每次上線都是先上再說(shuō)那安全問(wèn)題永遠(yuǎn)只能靠事后補(bǔ)救。小團(tuán)隊(duì)可以嘗試把以下幾件事寫(xiě)進(jìn)開(kāi)發(fā)流程上線前檢查清單包括接口鑒權(quán)、敏感數(shù)據(jù)加密、日志記錄、錯(cuò)誤信息脫敏等。不需要很復(fù)雜一頁(yè)紙就夠。代碼評(píng)審關(guān)注安全在代碼評(píng)審時(shí)除了看邏輯對(duì)不對(duì)也看一眼有沒(méi)有明顯的安全問(wèn)題比如SQL拼接、硬編碼密鑰、日志打印敏感信息。定期做一次簡(jiǎn)單掃描用開(kāi)源的掃描工具對(duì)線上系統(tǒng)做一次基礎(chǔ)掃描看看有沒(méi)有暴露的敏感文件、弱口令、已知漏洞。頻率不用很高一個(gè)季度一次就行。這些措施不會(huì)讓你的系統(tǒng)變得絕對(duì)安全但能讓攻擊者的成本顯著提高。在安全領(lǐng)域讓攻擊者覺(jué)得不劃算往往比追求無(wú)懈可擊更現(xiàn)實(shí)。6. 一個(gè)值得反復(fù)提醒的細(xì)節(jié)日志里的敏感信息最后聊一個(gè)特別容易被忽略、但危害很大的問(wèn)題日志泄露。開(kāi)發(fā)者在排查問(wèn)題時(shí)習(xí)慣把請(qǐng)求參數(shù)和響應(yīng)內(nèi)容都打到日志里。這本身沒(méi)問(wèn)題問(wèn)題在于很多系統(tǒng)會(huì)把用戶(hù)的手機(jī)號(hào)、身份證號(hào)、銀行卡號(hào)、密碼明文打進(jìn)日志。而這些日志往往存儲(chǔ)在多個(gè)地方應(yīng)用服務(wù)器本地、日志收集系統(tǒng)、監(jiān)控平臺(tái)、甚至開(kāi)發(fā)者的本地電腦。一旦這些日志被泄露危害不亞于數(shù)據(jù)庫(kù)被拖。而且日志泄露往往更隱蔽因?yàn)楹芏嗳烁静恢雷约旱娜罩纠镉惺裁?。我的建議是在日志框架里配置脫敏規(guī)則對(duì)手機(jī)號(hào)、身份證號(hào)、銀行卡號(hào)等字段自動(dòng)打碼。禁止在日志里打印密碼、密鑰、Token等憑證信息。日志的訪問(wèn)權(quán)限要控制不是所有開(kāi)發(fā)人員都需要看生產(chǎn)日志。日志的保留時(shí)間要有策略不需要永久保存的日志定期清理。這些操作不需要太多技術(shù)投入但能堵住一個(gè)非常隱蔽的泄露渠道。我在實(shí)際項(xiàng)目中見(jiàn)過(guò)太多數(shù)據(jù)庫(kù)加密做得很好但日志里全是明文手機(jī)號(hào)的情況希望你不要成為下一個(gè)。