繞過與防御)
在實際業(yè)務里摸爬滾打過一段時間后我對CSRF這個老牌漏洞有了些新的理解。今天不聊那些嘴上說爛的“Token校驗、SameSite”空話而是用一篇完整的教程從底層機制到實際場景把CSRF這個看似簡單的漏洞講透徹。無論你是剛入行的新人還是寫過幾年業(yè)務代碼的開發(fā)我相信這篇文章都能給你一些不一樣的啟發(fā)。1. 內容整體設計與思路拆解1.1 為什么說CSRF“入門容易精通難”CSRFCross-Site Request Forgery跨站請求偽造在各類漏洞排行里一直沒掉出過前排但很多零基礎的安全愛好者在學習它時會經(jīng)歷這樣一個過程剛看教程時覺得“這還不簡單”無非就是誘導用戶點擊一個鏈接就能偽造請求。真正上手測試后才發(fā)現(xiàn)實際情況復雜得多——CSRF漏洞的利用成功與否往往取決于你對目標站點的業(yè)務邏輯理解有多深而不是僅僅會構造一個表單就完事。我記得團隊里一個新人A同學第一次接觸CSRF時對照網(wǎng)上模版寫了一個惡意的提交表單興沖沖地跑去測試某個內部系統(tǒng)。結果顯而易見請求被攔截了。他當時疑惑地問我“是不是目標系統(tǒng)沒這個漏洞”我讓他去抓包看下實際請求里有沒有Token參數(shù)再看看Referer頭部是怎么處理的。結果發(fā)現(xiàn)目標系統(tǒng)不僅校驗了Token還把SameSite屬性設置成了Lax。這個案例很典型讓我意識到CSRF的“簡單”只停留在教科書層面真實環(huán)境下CSRF最難的其實是“繞過”與“防御”之間的博弈。1.2 這期教程的價值與適合人群這篇文章的定位不是一篇“讀起來很爽”的漏洞列表而是一篇具備實操參考價值的挖掘指南。我會從零開始拆解CSRF的觸發(fā)鏈路逐層分析“同源策略”“表單提交”“JSON跨域”這些看似基礎但綜合起來能決定漏洞是否存在的因素。如果你正在學習Web安全或者你是后端開發(fā)、全棧工程師想從“防御者視角”反向理解為什么CSRF會讓開發(fā)團隊頭疼這篇文章很適合你。我會用自己的實戰(zhàn)經(jīng)歷、模擬測試環(huán)境和測試中踩過的坑給你展示一個符合真實業(yè)務場景的CSRF測試全流程。提示所有測試操作均應在具備合法授權的前提下進行。不建議對未經(jīng)許可的目標發(fā)起任何安全測試否則后果自負。2. 核心機制解析CSRF為什么能夠存在2.1 先從“會話”聊起Cookie與身份認證想要理解CSRF必須先理解Web應用是如何識別一個用戶的。HTTP協(xié)議本身是無狀態(tài)的服務器無法直接知道“當前發(fā)起請求的人是誰”。為了讓用戶不需要每次請求都重新輸入用戶名密碼Web應用引入了“會話Session”機制通常通過Cookie在客戶端保存一個“會話標識”Session ID。你登錄了一個網(wǎng)站服務器生成一個Session ID存在你瀏覽器的Cookie里并把它和你的用戶身份綁定在一起。之后你的每次請求帶上這個Cookie服務器確認無誤后就認為“這是本人在操作”。這個機制本身沒有問題但它催生了一個盲區(qū)瀏覽器自動攜帶Cookie的特性。2.2 攻擊的核心邏輯瀏覽器的“無腦”行為CSRF的攻擊邏輯是建立在瀏覽器“自動攜帶身份憑據(jù)”這一行為之上的。攻擊者構造一個惡意頁面頁面里“藏”著一個指向目標網(wǎng)站的請求。當受害者訪問這個惡意頁面時瀏覽器會忠實地替受害者發(fā)出那個請求并且只要受害者在目標網(wǎng)站處于登錄狀態(tài)瀏覽器就會自動附帶上他的Cookie。舉個最常見的例子假設你在某個論壇保持了登錄狀態(tài)黑客在某個人氣很旺的圖片分享帖里嵌入了一個隱藏表單表單的提交地址是那個論壇的“修改郵箱”接口。當你打開這個帖子時隱藏表單自動提交你的郵箱就被修改成了黑客的郵箱。整個過程你完全無感知請求已經(jīng)發(fā)出并且攜帶了你的合法身份憑證。這就是CSRF最核心的模型。2.3 關鍵誤區(qū)CSRF與XSS的本質區(qū)別很多零基礎同學會把CSRF和XSS跨站腳本弄混這里我用一句話說清楚XSS利用的是“用戶對網(wǎng)站的信任”核心是“在目標網(wǎng)站內執(zhí)行惡意腳本”。CSRF利用的是“網(wǎng)站對用戶瀏覽器的信任”核心是“在受害者的瀏覽器里發(fā)起帶有身份的偽造請求”。在CSRF攻擊中攻擊者完全不需要拿到目標網(wǎng)站的Cookie、Session ID或其他任何身份憑證攻擊者只需要知道請求的目標URL和參數(shù)格式。2.4 CSRF利用成功的條件缺一不可在實際測試中一個CSRF漏洞想要利用成功通常需要滿足以下三個條件之一或全部目標請求必須使用Cookie或瀏覽器自動攜帶的身份進行鑒權。如果目標接口改用Token放在請求頭比如Authorization: Bearer那么純CSRF攻擊基本行不通。目標請求沒有防偽校驗。比如沒有CSRF Token、沒有校驗Referer/Origin或者校驗方式可以被繞過。攻擊者能夠構造一個“命中”的請求。這意味著攻擊者要精確知道目標接口的URL、請求方法POST/GET、必要的參數(shù)名和參數(shù)值。理解了這個鏈路你才能明白為什么CSRF漏洞的高發(fā)區(qū)往往是**“非核心業(yè)務但涉及狀態(tài)變更”**的接口比如修改個人資料、綁定手機號、修改密碼一定條件下、點贊、關注、發(fā)表評論等。這些接口往往安全防護松懈但又真實地改變了用戶數(shù)據(jù)。3. 實操要點從零構造一個CSRF驗證環(huán)境以“教”帶“學”是最快的成長路徑。想要真正掌握CSRF漏洞的利用和防御自己動手搭一個靶場環(huán)境是必經(jīng)之路。我分享一下我平時測試用的最小化環(huán)境方案簡單、直接、不依賴外部網(wǎng)絡。3.1 環(huán)境準備三個組件缺一不可你需要準備這三個角色一個目標站點我建議用本地PHPStudy或者直接寫一個最簡單的PHP/Flask/Express服務提供登錄鑒權接口和“修改個人信息”接口。一個攻擊者站點同樣可以放在本地或者使用本地文件file://協(xié)議模擬跨域注意文件協(xié)議在某些瀏覽器里Cookie行為不同建議還是起一個本地HTTP服務。一個受害者瀏覽器推薦使用無痕窗口方便模擬登錄態(tài)。3.2 核心靶場代碼目標接口設計我以Python Flask為例寫一個最典型的“修改昵稱”接口用于演示。這個接口僅依賴Cookie進行身份識別沒有CSRF防護為了模擬真實性我特意讓它使用POST請求內容編碼格式為表單application/x-www-form-urlencodedfrom flask import Flask, request, make_response, session app Flask(__name__) app.secret_key test-secret-key # 僅演示用 users { admin: {password: 123456, nickname: Admin}, } # 登錄接口設置Session app.route(/login, methods[POST]) def login(): username request.form.get(username) password request.form.get(password) user users.get(username) if user and user[password] password: session[username] username return make_response(login ok) return make_response(login failed, 403) # 修改昵稱接口 —— 僅檢查登錄狀態(tài)無CSRF Token app.route(/update_nickname, methods[POST]) def update_nickname(): if username not in session: return make_response(not logged in, 401) new_nickname request.form.get(nickname) # 實際業(yè)務在這里更新數(shù)據(jù)庫 users[session[username]][nickname] new_nickname return make_response(fnickname updated to {new_nickname}) if __name__ __main__: app.run(host0.0.0.0, port8000)這段代碼里update_nickname接口沒有Token校驗邏輯極其簡單——只判斷當前瀏覽器是否攜帶了有效Session Cookie。3.3 攻擊者頁面隱藏表單與自動提交接下來是攻擊者的陣地。我需要構造一個“惡意頁面”這個頁面放在另一個本地端口比如8001上。關鍵點是這個頁面里“藏”的表單提交地址是目標站點127.0.0.1:8000的接口并且由于表單提交天然支持跨域瀏覽器不會攔截這種“頁面發(fā)起跨域請求”的行為。html body h1一個正常的私人博客頁面/h1 p如果你看到了這篇文章說明你已經(jīng)中招了。/p form idcsrf-form methodPOST actionhttp://127.0.0.1:8000/update_nickname enctypeapplication/x-www-form-urlencoded input typehidden namenickname valueHacked-By-CSRF / /form script // 頁面加載完成后立即提交 window.onload function() { document.getElementById(csrf-form).submit(); } /script /body /html頁面看起來人畜無害但實際上是一個“請求發(fā)射器”。任何登錄了目標站點的用戶只要訪問了這個惡意頁面瀏覽器就會向目標接口發(fā)出一個POST請求并把受害者的Session Cookie一并帶上。3.4 完整復現(xiàn)與現(xiàn)象解釋操作步驟很簡單我按順序演示一遍打開無痕窗口A訪問http://127.0.0.1:8000/login提交用戶名和密碼admin/123456確認登錄成功。登錄后訪問任意頁面確認Cookie已被設置此時瀏覽器對目標站點是“已認證”狀態(tài)。另開一個無痕窗口B確保和目標站點沒有直接關聯(lián)訪問攻擊者控制的惡意頁面http://127.0.0.1:8001/evil.html。觀察窗口A或者直接查詢數(shù)據(jù)庫中的nickname字段你會發(fā)現(xiàn)即使沒用目標站點的Cookie、沒有輸入密碼用戶的昵稱也已經(jīng)被修改成了Hacked-By-CSRF?,F(xiàn)象背后發(fā)生了什么窗口B的瀏覽器向目標服務器發(fā)送了一個攜帶了SessionID的合法POST請求目標服務器只認Cookie不關心這個請求到底是不是用戶本人主動發(fā)出的于是直接更新了數(shù)據(jù)。受害者完全沒有感知。這里補充一個容易被忽略的細節(jié)為什么表單提交能跨域因為 HTML 表單form的action屬性可以指向任意域名的地址瀏覽器出于歷史原因老式的Web交互模式允許這種POST跨域行為。而XMLHttpRequest / fetch 則受同源策略限制無法直接跨域發(fā)送“可攜帶Cookie”的請求需要額外配置CORS。這也是CSRF攻擊最傳統(tǒng)的載體是“表單”的根本原因。4. 常見問題與排查技巧為什么我的測試不成功實際測試中不可能每次CSRF利用都一擊即中。我把近幾年測試中遇到的高頻失敗原因做了匯總你對照著排查基本能解決95%的問題。4.1 防偽Token校驗導致的失敗現(xiàn)象提交惡意表單后對方返回“請求無效”或“參數(shù)校驗失敗”。排查思路這是最常見的攔截手段。目標接口在渲染時生成了一個一次性Token存入隱藏字段或自定義Header下一次提交時對比是否一致。這里有個關鍵測試思路不要一上來就說“這里防御了沒法繞過”。你要在合法業(yè)務里找“Token是否被復用、是否可預測、是否在特定接口缺失”的場景。比如注冊接口的新用戶Token是否固定某些接口如退訂郵件、消息已讀上報是否因為歷史原因漏掉了TokenGET類型請求也能觸發(fā)狀態(tài)變更且無Token時能否利用圖片標簽img srchttp://target.com/delete?id123直接觸發(fā)4.2 SameSite Cookie策略導致的失敗現(xiàn)象寫好的利用頁面請求始終不攜帶Cookie抓包看Cookie字段是空的。排查思路這是現(xiàn)代瀏覽器抵御CSRF的標準手段之一。SameSite屬性有Strict、Lax、None三個值簡單說Strict完全禁止第三方Cookie隨跨站請求發(fā)送。Lax只有“頂層導航”的GET請求會攜帶Cookie表單POST和異步請求都不會攜帶。None允許所有請求攜帶Cookie但需要在HTTPS環(huán)境配置。如果目標站點的Cookie設置的是SameSiteLax或Strict傳統(tǒng)的表單自動提交方式大概率失效。這時候你得轉換測試思路嘗試尋找“子域漏洞Cookie域配置寬松”的組合或者看看是否有“JSONP”“CORS配置錯誤”可以利用的服務端邏輯。4.3 Referer/Origin校驗帶來的挑戰(zhàn)現(xiàn)象目標服務器校驗了Referer或Origin頭發(fā)現(xiàn)請求來源不是自身域名直接拒絕。排查思路如果Referer校驗是通過“包含匹配”例如檢查請求頭中是否包含了某個域名片段那么就存在繞過空間。舉個例子目標校驗Referer中是否包含target.com那攻擊者可以把惡意頁面放在target.com.attacker.com域名下不同子域名Referer就會變成http://target.com.attacker.com/evil-page包含目標域名但實際是攻擊者控制的。不過這種方式需要攻擊者擁有一個可控域名的子域解析在真實SRC挖掘中屬于高危接口可利用邊緣場景的結合。4.4 JSON接口與Content-Type校驗現(xiàn)象目標接口要求Content-Type: application/json而HTML表單只能發(fā)送text/plain、application/x-www-form-urlencoded、multipart/form-data這三種編碼無法直接提交JSON數(shù)據(jù)。排查思路碰到這種情況傳統(tǒng)的form提交失效??梢栽囋囘@兩個方向JSONP接口如果目標站點存在返回JSON數(shù)據(jù)的Callback接口老系統(tǒng)常見可以構造script src...跨域加載并執(zhí)行特定函數(shù)間接向接口發(fā)送請求。Flash / 表單跨域漏洞2010年前后的老系統(tǒng)內部處理函數(shù)有的會把text/plain主體按JSON解析因為request.getParameter無法讀取但request.getInputStream是通的這種情況純CSRF攻擊也有可能成立。4.5 調整請求方式GET型CSRF別忽略現(xiàn)象目標接口只支持GET請求但按照“傳統(tǒng)CSRF教程”好像都需要POST表單。排查思路如果GET請求直接攜帶了敏感操作參數(shù)例如DELETE /index.php?id2或者POST 接口支持 GET 方式訪問很多開發(fā)框架為了方便調試默認允許這種場景下用img、link、script標簽就能觸發(fā)跨站請求根本不需要表單。為什么不建議只測POST因為“能用GET絕不用POST”的老一代開發(fā)習慣以及RESTful接口的普遍化導致“GET方法修改狀態(tài)”的案例至今沒有斷絕。這是一個非常典型的“低投入高回報”測試點。5. 防御方案與有效性對比說完了攻擊側和測試側我站在開發(fā)者的視角聊幾套CSRF防御方案的實際效果。記住沒有銀彈安全的本質是“成本與收益的博弈”。5.1 同步令牌模式Synchronizer Token Pattern思路服務器生成隨機Token寫入表單隱藏字段或響應Header中并在Session中保存一份。每次請求提交時比對兩個值是否一致。有效性與缺點這是最經(jīng)典的CSRF防御機制有效防止“無腦表單提交”。但它的防護強度取決于Token是否綁定用戶身份全局Token在“登錄前功能”上容易被繞過Token是否在每次請求后刷新一次性Token比固定Token安全得多Token的隨機性生成算法是否安全實際踩坑時你會發(fā)現(xiàn)很多系統(tǒng)中的Token只做了“存在性校驗”沒做“一致性校驗”或者雖然做了校驗但Token同時存在于Cookie中導致可以配合XSS缺陷繞過。5.2 雙重提交CookieDouble Submit Cookie思路服務器要求前端提交一個Token這個Token在客戶端以Cookie形式存在。服務器校驗“提交的Token”和“Cookie里的Token”是否一致由于跨站請求無法設置目標域名的Cookie所以認為安全的。有效性與缺點這個方案在“純CSRF”場景下很有效但如果目標環(huán)境存在子域任一處XSS或者Cookie由于某些配置可以被子域影響例如Cookie Domain設置太寬攻擊者可以在攻擊者域名下寫入惡意Cookie來偽造提交Token導致繞過。5.3 SameSite Cookie 屬性前面已經(jīng)提到過這是瀏覽器層面提供的原生防護。強烈建議開發(fā)團隊將所有“不要求跨站攜帶”的Cookie一律設置為SameSiteLax必要時用Strict。在Chrome和Firefox高版本中這基本能把傳統(tǒng)CSRF的90%問題堵在門外。這也是2020年以后CSRF漏洞數(shù)量陡降的主因之一。5.4 Origin/Referer校驗思路如果請求頭中的Origin不是本站域名則拒絕。這種方案部署成本低但依然存在“空Origin”通過比如沙箱頁面、內嵌頁面、某些舊版瀏覽器的情況。建議配合Token方案做雙保險。5.5 防御方案對比速查表防御方案CSRF防護強度部署成本繞過難度備注Token同步令牌高中高需注意Token生成算法和失效機制雙重提交Cookie中低中對子域XSS較敏感SameSite屬性高極低高主流瀏覽器不能完全替代服務端防御兼容性問題Referer/Origin校驗中低中需關注空值和子域解析問題驗證碼/二次確認極高高極高常用于高敏感操作如改密、轉賬我的建議是核心業(yè)務采取“TokenSameSite”組合重要操作修改密碼、綁定手機、轉賬額外增加二次驗證。在成本和體驗之間尋求一個可控的平衡點。6. 從漏洞挖掘角度看CSRF的行業(yè)變化6.1 “降級”為高危鏈里的一環(huán)這兩年CSRF在獨立漏洞挖掘中的地位有所下降原因正如前面提到的瀏覽器對Cookie的跨站限制越來越嚴格開發(fā)者框架默認啟用防偽校驗獨立觸發(fā)一個“純CSRF”漏洞的難度確實大了。但注意CSRF并沒有消失而是更多作為“高階漏洞鏈條”中的一環(huán)出現(xiàn)。比如在以下場景中CSRF依然是關鍵點睛之筆配合XSS降級利用一個理論上只能自我觸發(fā)的存儲型XSS通過CSRF將數(shù)據(jù)寫入目標用戶擴大影響范圍。配合并發(fā)邏輯漏洞利用CSRF讓受害者在不知情的情況下啟動某個操作如綁定攻擊者賬號、關注特定用戶、上傳特定資源配合競態(tài)條件實現(xiàn)“雙重操作”。攻擊“邊緣業(yè)務”很多大公司在核心業(yè)務邊邊角角的“活動頁面”“埋點上報”“第三方登錄回調”里依然存在CSRF。這些接口數(shù)據(jù)價值不高但一旦涉及“修改回調地址”或者“綁定手機號”就可能是高分值漏洞。6.2 零基礎學習的建議不要為了“挖洞”而“挖洞”很多同學學習安全時容易陷入一種“刷漏洞”的心態(tài)。作為一個過來人我的建議是先把一條完整鏈路的底層原理摸透。為什么表單能跨域為什么瀏覽器要默認攜帶Cookie為什么SameSite設置為None時容易出問題如果一個漏洞的原理你已經(jīng)能夠用自己的語言向別人解釋清楚那你離真正掌握它就不遠了。同時一定要熟悉抓包工具的使用。無論是Burp Suite還是瀏覽器開發(fā)者工具你需要從“結果”反推“過程”觀察請求的來龍去脈。我見過很多初學者明明漏洞已經(jīng)觸發(fā)了卻因為看不懂請求面板里的參數(shù)來源錯失了確認漏洞的機會。6.3 合法測試的邊界最后必須非常嚴肅地強調一遍本文所有內容僅為技術交流與教育目的。每一個涉及漏洞的工具、思路、代碼片段都應當在合規(guī)授權的范圍內使用。企業(yè)授權測試和公益SRC有明確測試范圍——在范圍外測試即使沒造成破壞也是違背法律和道德的行為。做安全技術重要邊界感更重要。這一點每個從事安全領域的人都應該牢記?,F(xiàn)在可以打開你身邊的靶場用本地環(huán)境按照上面的演示手動復現(xiàn)一遍CSRF的完整攻擊和防御流程。這比刷一百個漏洞報告都管用。