戰(zhàn)指南:從注入防御到安全左移的開(kāi)發(fā)者必修課)
1. 為什么每個(gè)開(kāi)發(fā)者的書(shū)架上都該有一份OWASP Top 10如果你寫(xiě)過(guò)哪怕一個(gè)帶登錄功能的頁(yè)面或者調(diào)過(guò)任何一個(gè)對(duì)外暴露的接口那你其實(shí)已經(jīng)在網(wǎng)絡(luò)安全的風(fēng)險(xiǎn)敞口里了。很多剛?cè)胄械呐笥延X(jué)得“安全”是安全工程師的事自己只要把業(yè)務(wù)邏輯跑通就行。但現(xiàn)實(shí)情況是絕大多數(shù)被利用的漏洞都藏在最普通的業(yè)務(wù)代碼里——一個(gè)沒(méi)過(guò)濾的查詢(xún)參數(shù)、一個(gè)圖省事的字符串拼接、一個(gè)忘了加權(quán)限校驗(yàn)的接口。OWASP Top 10 就是把這些“最容易被踩中”的坑按風(fēng)險(xiǎn)和出現(xiàn)頻率整理出來(lái)的一份清單。它不是一本需要從頭讀到尾的教科書(shū)更像是一份“體檢表”你拿著它逐條對(duì)照自己的項(xiàng)目就能發(fā)現(xiàn)大部分顯而易見(jiàn)的問(wèn)題。我剛開(kāi)始接觸這塊的時(shí)候也覺(jué)得這十個(gè)名詞看著都認(rèn)識(shí)但真到代碼里就不知道從哪下手。后來(lái)踩過(guò)幾次坑才明白OWASP Top 10 的價(jià)值不在于讓你背下十個(gè)條目而在于幫你建立一種“攻擊者視角”——寫(xiě)代碼的時(shí)候會(huì)下意識(shí)地想這個(gè)參數(shù)如果被人改了會(huì)怎樣這個(gè)接口如果被人直接調(diào)用會(huì)怎樣這份清單適合所有需要寫(xiě)代碼、搭服務(wù)、做運(yùn)維的人不管你是前端、后端還是全棧只要你的代碼跑在網(wǎng)絡(luò)上它就和你有關(guān)。接下來(lái)我會(huì)按“漏洞長(zhǎng)什么樣、怎么被利用、代碼里怎么防、實(shí)際踩過(guò)什么坑”這個(gè)順序把每一類(lèi)都拆開(kāi)講清楚。2. 注入類(lèi)漏洞當(dāng)你的查詢(xún)語(yǔ)句變成了攻擊者的提線(xiàn)木偶2.1 SQL注入的本質(zhì)是“代碼和數(shù)據(jù)沒(méi)分家”注入類(lèi)漏洞常年霸占各類(lèi)安全風(fēng)險(xiǎn)榜單的前列原因很簡(jiǎn)單它太容易出現(xiàn)了而且一旦出現(xiàn)后果往往很?chē)?yán)重。所謂注入說(shuō)白了就是攻擊者把你原本打算當(dāng)“數(shù)據(jù)”處理的內(nèi)容變成了“代碼”的一部分去執(zhí)行。最典型的就是SQL注入。假設(shè)你寫(xiě)了一個(gè)登錄接口SQL語(yǔ)句是這樣拼的sql SELECT * FROM users WHERE username username AND password password 正常用戶(hù)輸入zhangsan和123456語(yǔ)句沒(méi)問(wèn)題。但如果有人在用戶(hù)名框里輸入 OR 11整條語(yǔ)句就變成了SELECT * FROM users WHERE username OR 11 AND password 11永遠(yuǎn)為真于是這條查詢(xún)會(huì)返回用戶(hù)表里的所有記錄攻擊者不需要知道任何密碼就能登錄。這就是注入的核心你用來(lái)拼接的字符串被對(duì)方當(dāng)成了SQL語(yǔ)法的一部分。2.2 參數(shù)化查詢(xún)?yōu)槭裁茨軓母旧辖鉀Q問(wèn)題防注入最有效的手段就是參數(shù)化查詢(xún)也叫預(yù)編譯語(yǔ)句。它的原理是先把SQL語(yǔ)句的結(jié)構(gòu)發(fā)給數(shù)據(jù)庫(kù)編譯好占位符的位置留空然后再把用戶(hù)輸入的值填進(jìn)去。這樣一來(lái)無(wú)論用戶(hù)輸入什么數(shù)據(jù)庫(kù)都只把它當(dāng)成一個(gè)“值”而不是“語(yǔ)法”。用Python的數(shù)據(jù)庫(kù)驅(qū)動(dòng)舉例cursor.execute(SELECT * FROM users WHERE username %s AND password %s, (username, password))注意這里用的是%s占位符而不是字符串拼接。很多新手會(huì)問(wèn)那我用ORM框架是不是就自動(dòng)安全了大部分情況下是的但前提是你沒(méi)有在ORM里寫(xiě)原生SQL。我見(jiàn)過(guò)有人在ORM里用raw()方法拼字符串那等于繞過(guò)了ORM的保護(hù)照樣會(huì)中招。所以關(guān)鍵不是用不用框架而是有沒(méi)有把“數(shù)據(jù)”和“指令”分開(kāi)。2.3 命令注入和LDAP注入的隱蔽性更強(qiáng)除了SQL注入命令注入也很常見(jiàn)。比如你的程序需要調(diào)用系統(tǒng)命令去處理一個(gè)文件代碼寫(xiě)成os.system(convert filename output.jpg)。如果攻擊者把文件名改成test.jpg; rm -rf /那系統(tǒng)在執(zhí)行完轉(zhuǎn)換之后還會(huì)額外執(zhí)行刪除命令。這種漏洞的可怕之處在于它直接操作服務(wù)器權(quán)限往往很高。防御方法和SQL注入類(lèi)似盡量不要拼接命令如果必須調(diào)用外部程序用參數(shù)列表的方式傳參而不是拼成一個(gè)字符串。LDAP注入、XPath注入原理相通都是因?yàn)榘延脩?hù)輸入直接嵌入了查詢(xún)語(yǔ)句。記住一個(gè)原則任何你拼接進(jìn)“指令”里的用戶(hù)輸入都是潛在的注入點(diǎn)。3. 失效的身份認(rèn)證你的登錄邏輯真的經(jīng)得起推敲嗎3.1 弱密碼策略和暴力破解的邊界身份認(rèn)證出問(wèn)題往往不是技術(shù)多復(fù)雜而是設(shè)計(jì)上偷了懶。最常見(jiàn)的就是允許用戶(hù)設(shè)置“123456”這種弱密碼或者登錄接口沒(méi)有任何頻率限制。攻擊者可以用一個(gè)字典每秒嘗試幾百次密碼如果你的系統(tǒng)不限制嘗試次數(shù)那破解只是時(shí)間問(wèn)題。我見(jiàn)過(guò)一個(gè)內(nèi)部系統(tǒng)登錄接口連失敗次數(shù)都不記錄跑一個(gè)常用密碼字典十分鐘就試出了三個(gè)賬號(hào)。防御措施很直接強(qiáng)制密碼復(fù)雜度、限制登錄嘗試次數(shù)、失敗多次后鎖定賬號(hào)或加驗(yàn)證碼。但這里有個(gè)平衡點(diǎn)——鎖定策略太激進(jìn)會(huì)影響正常用戶(hù)太寬松又起不到防護(hù)作用。我的經(jīng)驗(yàn)是連續(xù)失敗5次后加一個(gè)圖形驗(yàn)證碼失敗10次后鎖定15分鐘這樣既能擋住自動(dòng)化腳本又不會(huì)讓用戶(hù)太難受。3.2 Session和Token管理里的那些坑登錄成功之后怎么維持登錄狀態(tài)也有講究。有些系統(tǒng)把用戶(hù)ID直接存在Cookie里比如user_id1001而且不做任何簽名。攻擊者只要把1001改成1002就能以別人的身份操作。正確的做法是使用服務(wù)端生成的Session ID或者用簽名過(guò)的Token比如JWT。但JWT也不是萬(wàn)能的我踩過(guò)一個(gè)坑JWT的簽名密鑰用了默認(rèn)值或者太簡(jiǎn)單攻擊者可以自己偽造Token。還有一個(gè)常見(jiàn)問(wèn)題是Token沒(méi)有設(shè)置過(guò)期時(shí)間一旦泄露就永久有效。所以Token里一定要有exp字段并且密鑰要足夠復(fù)雜最好定期輪換。3.3 多因素認(rèn)證不是萬(wàn)能藥但能擋住大部分自動(dòng)化攻擊多因素認(rèn)證MFA現(xiàn)在越來(lái)越普及它的邏輯是即使密碼泄露了攻擊者還需要第二個(gè)因素比如手機(jī)驗(yàn)證碼、硬件密鑰才能登錄。這能有效擋住大部分自動(dòng)化撞庫(kù)攻擊。但MFA也有自己的坑短信驗(yàn)證碼可能被攔截而且有些系統(tǒng)在驗(yàn)證MFA之前就把用戶(hù)信息返回給前端了這等于給了攻擊者一個(gè)“密碼正確”的信號(hào)。正確的流程應(yīng)該是密碼驗(yàn)證通過(guò)后先不返回任何敏感信息只返回一個(gè)臨時(shí)的、一次性的憑證等MFA驗(yàn)證通過(guò)后再正式建立會(huì)話(huà)。這個(gè)細(xì)節(jié)很多開(kāi)發(fā)者會(huì)忽略但它在實(shí)際攻防中很關(guān)鍵。4. 敏感數(shù)據(jù)泄露與安全配置錯(cuò)誤那些你以為沒(méi)人會(huì)看的角落4.1 數(shù)據(jù)在傳輸和存儲(chǔ)時(shí)到底該怎么保護(hù)敏感數(shù)據(jù)泄露的范圍很廣從用戶(hù)的手機(jī)號(hào)、身份證號(hào)到系統(tǒng)的API密鑰、數(shù)據(jù)庫(kù)密碼都屬于敏感數(shù)據(jù)。很多開(kāi)發(fā)者覺(jué)得“我的系統(tǒng)又不出名沒(méi)人會(huì)盯著”但自動(dòng)化掃描工具是不分目標(biāo)的它們會(huì)掃描整個(gè)IP段發(fā)現(xiàn)暴露的敏感信息就記錄下來(lái)。傳輸層面全站HTTPS現(xiàn)在是基本要求但要注意配置正確的TLS版本和加密套件老舊的SSLv3、TLS 1.0早就該禁用了。存儲(chǔ)層面密碼絕對(duì)不能明文存也不能用MD5或SHA1這種已經(jīng)被證明不安全的哈希算法。推薦用bcrypt、scrypt或者Argon2這類(lèi)專(zhuān)門(mén)為密碼設(shè)計(jì)的算法它們計(jì)算慢能有效抵抗暴力破解。對(duì)于其他敏感數(shù)據(jù)比如身份證號(hào)如果業(yè)務(wù)上不需要完整展示可以考慮加密存儲(chǔ)或者脫敏展示。4.2 默認(rèn)配置和錯(cuò)誤信息泄露的連鎖反應(yīng)安全配置錯(cuò)誤是OWASP Top 10里覆蓋面最廣的一類(lèi)因?yàn)樗婕暗臇|西太多了默認(rèn)賬號(hào)密碼沒(méi)改、目錄列表沒(méi)關(guān)、錯(cuò)誤堆棧直接返回給前端、云存儲(chǔ)桶權(quán)限設(shè)置成公開(kāi)可讀……我見(jiàn)過(guò)一個(gè)案例某系統(tǒng)的數(shù)據(jù)庫(kù)管理后臺(tái)用了默認(rèn)的admin/admin而且端口直接暴露在公網(wǎng)上結(jié)果被自動(dòng)化工具掃到整個(gè)數(shù)據(jù)庫(kù)被拖走。還有一個(gè)常見(jiàn)問(wèn)題是錯(cuò)誤信息泄露當(dāng)程序出錯(cuò)時(shí)直接把完整的堆棧信息返回給前端里面可能包含數(shù)據(jù)庫(kù)連接字符串、文件路徑、框架版本號(hào)。攻擊者拿到這些信息就能針對(duì)性地找漏洞。正確的做法是生產(chǎn)環(huán)境關(guān)閉詳細(xì)錯(cuò)誤輸出只給用戶(hù)一個(gè)通用的錯(cuò)誤提示詳細(xì)的錯(cuò)誤記錄到服務(wù)端日志里。4.3 依賴(lài)組件和API接口的“隱形”風(fēng)險(xiǎn)現(xiàn)代項(xiàng)目很少?gòu)牧銓?xiě)起大部分都會(huì)引入第三方庫(kù)和框架。但這些依賴(lài)組件本身也可能有漏洞。比如某個(gè)常用的JSON解析庫(kù)出了反序列化漏洞所有用了這個(gè)庫(kù)的項(xiàng)目都受影響。所以定期檢查依賴(lài)組件的版本、關(guān)注安全公告是每個(gè)項(xiàng)目維護(hù)者的日常功課。API接口方面常見(jiàn)的問(wèn)題是接口沒(méi)有做權(quán)限校驗(yàn)或者返回了過(guò)多的數(shù)據(jù)。比如一個(gè)查詢(xún)用戶(hù)信息的接口本來(lái)只需要返回用戶(hù)名和頭像結(jié)果把手機(jī)號(hào)、郵箱、地址全返回了。攻擊者拿到這些數(shù)據(jù)可以進(jìn)行精準(zhǔn)的釣魚(yú)攻擊。防御原則是接口只返回業(yè)務(wù)必需的最小數(shù)據(jù)集并且每個(gè)接口都要校驗(yàn)當(dāng)前用戶(hù)是否有權(quán)限訪(fǎng)問(wèn)。5. 從代碼到上線(xiàn)把安全左移的實(shí)操思路5.1 在開(kāi)發(fā)階段就引入安全檢查很多團(tuán)隊(duì)的安全工作是在上線(xiàn)前才做的這時(shí)候發(fā)現(xiàn)問(wèn)題修復(fù)成本已經(jīng)很高了。更好的做法是把安全左移在開(kāi)發(fā)階段就引入檢查。比如在代碼倉(cāng)庫(kù)里配置靜態(tài)代碼掃描工具每次提交代碼時(shí)自動(dòng)跑一遍發(fā)現(xiàn)明顯的注入、硬編碼密碼等問(wèn)題就報(bào)警。還可以在代碼審查清單里加入安全項(xiàng)這個(gè)接口有沒(méi)有做權(quán)限校驗(yàn)這個(gè)參數(shù)有沒(méi)有做輸入驗(yàn)證這個(gè)SQL是不是參數(shù)化的這些檢查不需要安全專(zhuān)家來(lái)做普通開(kāi)發(fā)者花幾分鐘就能完成。我自己的習(xí)慣是寫(xiě)完一個(gè)接口后先自己用工具掃一遍再提交給同事審查這樣能提前發(fā)現(xiàn)大部分低級(jí)問(wèn)題。5.2 上線(xiàn)前的安全測(cè)試怎么做才有效上線(xiàn)前的安全測(cè)試不一定要買(mǎi)昂貴的商業(yè)掃描器。開(kāi)源的漏洞掃描工具配合手工測(cè)試就能覆蓋大部分常見(jiàn)問(wèn)題。自動(dòng)化掃描器可以快速發(fā)現(xiàn)配置錯(cuò)誤、已知漏洞、弱密碼等問(wèn)題但它對(duì)業(yè)務(wù)邏輯漏洞的發(fā)現(xiàn)能力有限。所以手工測(cè)試很重要重點(diǎn)測(cè)這幾個(gè)地方登錄、注冊(cè)、密碼重置、支付、文件上傳、權(quán)限切換。測(cè)試的時(shí)候要站在攻擊者的角度想如果我是一個(gè)惡意用戶(hù)我會(huì)怎么繞過(guò)限制比如文件上傳功能我能不能上傳一個(gè)腳本文件權(quán)限切換功能我能不能通過(guò)修改請(qǐng)求參數(shù)訪(fǎng)問(wèn)別人的數(shù)據(jù)這些測(cè)試不需要很深的專(zhuān)業(yè)知識(shí)但需要細(xì)心和耐心。5.3 上線(xiàn)后的監(jiān)控和應(yīng)急響應(yīng)系統(tǒng)上線(xiàn)不代表安全工作就結(jié)束了。你需要有監(jiān)控機(jī)制能發(fā)現(xiàn)異常的訪(fǎng)問(wèn)模式。比如某個(gè)IP在短時(shí)間內(nèi)大量請(qǐng)求登錄接口或者某個(gè)賬號(hào)在異地登錄這些都應(yīng)該觸發(fā)告警。日志要記錄關(guān)鍵操作但注意不要把敏感信息寫(xiě)進(jìn)日志比如密碼、Token。萬(wàn)一真的發(fā)生了安全事件要有應(yīng)急響應(yīng)流程先隔離受影響的服務(wù)再分析日志定位問(wèn)題然后修復(fù)漏洞并通知受影響的用戶(hù)。這個(gè)過(guò)程最好提前演練一遍免得到時(shí)候手忙腳亂。我經(jīng)歷過(guò)一次小規(guī)模的數(shù)據(jù)泄露就是因?yàn)闆](méi)有提前準(zhǔn)備應(yīng)急流程發(fā)現(xiàn)漏洞后花了很長(zhǎng)時(shí)間才搞清楚影響范圍教訓(xùn)很深刻。6. 那些年我踩過(guò)的安全坑和總結(jié)出的經(jīng)驗(yàn)6.1 不要相信任何客戶(hù)端傳來(lái)的數(shù)據(jù)這是我踩過(guò)的最大的坑。早期做項(xiàng)目的時(shí)候我覺(jué)得前端已經(jīng)做了輸入驗(yàn)證后端就不用再做了。結(jié)果有人直接繞過(guò)前端用工具構(gòu)造請(qǐng)求把惡意數(shù)據(jù)發(fā)到了后端。從那以后我養(yǎng)成了一個(gè)習(xí)慣所有來(lái)自客戶(hù)端的數(shù)據(jù)不管是表單、URL參數(shù)、HTTP頭還是Cookie一律在后端重新驗(yàn)證一遍。驗(yàn)證的內(nèi)容包括數(shù)據(jù)類(lèi)型對(duì)不對(duì)、長(zhǎng)度有沒(méi)有超限、格式是否符合預(yù)期、有沒(méi)有包含特殊字符。這個(gè)習(xí)慣看起來(lái)麻煩但能擋住大部分注入和越權(quán)問(wèn)題。6.2 權(quán)限校驗(yàn)要放在每一個(gè)接口里另一個(gè)常見(jiàn)的坑是權(quán)限校驗(yàn)遺漏。比如一個(gè)系統(tǒng)有普通用戶(hù)和管理員兩種角色大部分接口都做了角色校驗(yàn)但有一個(gè)查詢(xún)接口忘了加結(jié)果普通用戶(hù)可以通過(guò)這個(gè)接口看到所有用戶(hù)的數(shù)據(jù)。這種問(wèn)題在代碼審查時(shí)很難發(fā)現(xiàn)因?yàn)榻涌诒旧磉壿嫑](méi)問(wèn)題只是少了一行校驗(yàn)代碼。我的做法是把權(quán)限校驗(yàn)做成一個(gè)統(tǒng)一的攔截器或中間件所有接口默認(rèn)都需要登錄需要特定角色的接口再單獨(dú)聲明。這樣就不會(huì)因?yàn)槭韬龆┑裟硞€(gè)接口。6.3 安全是一個(gè)持續(xù)的過(guò)程而不是一次性的任務(wù)最后想說(shuō)的是安全不是做完一次滲透測(cè)試就萬(wàn)事大吉了。新的漏洞不斷出現(xiàn)業(yè)務(wù)代碼也在不斷變化今天安全的設(shè)計(jì)明天可能就因?yàn)樾鹿δ芏兊貌话踩?。所以要把安全檢查融入到日常開(kāi)發(fā)流程里每次代碼提交都跑一遍掃描每次上線(xiàn)前都做一次安全測(cè)試定期更新依賴(lài)組件關(guān)注安全社區(qū)的最新動(dòng)態(tài)。這些工作看起來(lái)瑣碎但比起被攻擊后造成的損失這些投入是值得的。我在實(shí)際項(xiàng)目中的體會(huì)是安全做得好的團(tuán)隊(duì)往往也是代碼質(zhì)量高、工程規(guī)范好的團(tuán)隊(duì)因?yàn)閮烧弑澈蟮乃季S方式是一樣的嚴(yán)謹(jǐn)、細(xì)致、不抱僥幸心理。