與漏洞挖掘)
1. 項目概述為什么登錄頁面是滲透測試的“黃金入口”干了這么多年滲透測試我越來越覺得登錄頁面就像一棟大樓的正門。表面上看起來光鮮亮麗有保安驗證碼、有門禁密碼但往往也是安全設計最容易被忽視、攻擊面最集中的地方。很多甲方在安全建設上投入不少搞WAF、上態(tài)勢感知但登錄頁面的安全邏輯尤其是前端JavaScriptJS源碼里藏著的那些“貓膩”卻常常被開發(fā)和安全團隊忽略。這次我們就來一次深度實戰(zhàn)把登錄頁面從用戶點擊到后端驗證的整個鏈條掰開揉碎了看重點就是那些藏在JS源碼里的攻擊面。你可能覺得登錄不就一個表單提交嗎用戶名、密碼、點登錄后端一查對就過錯就拒。但現實遠比這復雜。現代Web應用特別是前后端分離的架構比如用Vue.js、React寫的單頁應用大量的業(yè)務邏輯、數據校驗、甚至部分認證邏輯都被放在了前端。這固然提升了用戶體驗但也把攻擊面大大前移了。攻擊者根本不需要一開始就去碰堅固的后端API在前端JS代碼里就可能找到繞過認證、竊取憑證、進行邏輯漏洞攻擊的鑰匙。這次實戰(zhàn)我們就模擬一個攻擊者的視角目標就是一個典型的、可能是Vue.js構建的登錄頁面看看如何通過分析其JS源碼挖掘出那些常規(guī)掃描器發(fā)現不了的深層次漏洞。2. 攻擊面全景圖登錄流程的每一個環(huán)節(jié)都可能失守在動手分析JS源碼之前我們得先建立全局視野。一個登錄頁面的攻擊面遠不止一個“SQL注入”或“弱口令”那么簡單。它是一個由多個環(huán)節(jié)串聯起來的鏈條任何一個環(huán)節(jié)的脆弱都可能導致整個認證體系崩塌。我們可以把這個鏈條拆解為以下幾個關鍵攻擊面2.1 客戶端輸入與渲染邏輯這是最前端的一環(huán)也是JS源碼分析的主戰(zhàn)場。攻擊者在這里關注的是頁面如何生成表單如何校驗數據如何組裝和發(fā)送源碼泄露與信息收集首先我們得拿到JS文件。現代構建工具如Webpack可能會把源碼打包、混淆但并非無懈可擊。通過瀏覽器開發(fā)者工具的“Sources”面板我們能直接看到當前頁面加載的所有JS文件。混淆的代碼雖然難讀但通過一些反混淆工具如de4js或耐心分析常能發(fā)現硬編碼的API端點、接口參數格式、甚至調試信息。例如一個config.js文件里可能寫著API_BASE_URL: https://api.target.com/internal/v1這就暴露了內部API地址。客戶端輸入驗證繞過這是經典漏洞。登錄頁面常用JS在提交前檢查用戶名是否為空、密碼長度、郵箱格式等。比如這樣的代碼function validateLogin() { let username document.getElementById(username).value; let password document.getElementById(password).value; if (username.length 6) { alert(用戶名至少6位); return false; } // ... 其他檢查 return true; // 只有所有檢查通過才觸發(fā)表單提交或axios請求 }攻擊思路這種校驗純粹是“防君子不防小人”。攻擊者可以直接用Burp Suite攔截HTTP請求修改username為任意長度比如admin或者直接禁用瀏覽器JS就可以輕松繞過。更隱蔽的是一些復雜的校驗邏輯可能存在缺陷比如正則表達式過濾不嚴可能導致注入。客戶端會話與狀態(tài)管理登錄狀態(tài)token如JWT是如何存儲的是localStorage、sessionStorage還是CookieJS代碼中如何讀取和發(fā)送它檢查是否有將敏感token記錄到日志、或通過console.log泄露的代碼。例如在調試代碼中看到console.log(Auth Token:, token)這就是一個低級但真實存在的信息泄露點。2.2 網絡通信與API接口當表單數據離開瀏覽器飛向服務器的過程中又是一個充滿風險的階段。JS源碼決定了數據以何種形式、發(fā)往何處。API端點枚舉與探測分析JS中axios、fetch或$.ajax的調用找到登錄、注冊、找回密碼等接口的真實URL。有時開發(fā)會為了方便設計出有規(guī)律的API如/api/login,/api/register,/api/reset-password。攻擊者可以據此枚舉其他可能未在前端暴露的管理接口比如/api/admin/list-users。請求參數構造與篡改JS源碼揭示了后端期望的數據結構。是JSON格式還是FormData字段名是什么除了username和password還有沒有captcha、token、deviceId等隱藏或可選參數例如源碼顯示登錄請求體是{“user”: “xxx”, “pwd”: “yyy”, “rememberMe”: true}。那么攻擊者可以嘗試將rememberMe改為一個非布爾值或者添加一個額外的參數如role: admin測試后端是否存在參數解析或業(yè)務邏輯漏洞。認證憑證的傳輸安全JS代碼是否強制使用HTTPS有沒有在明文HTTP下發(fā)送密碼的潛在分支比如用于開發(fā)環(huán)境檢查所有網絡請求的URL構造邏輯。2.3 認證與授權邏輯缺陷這是最致命的攻擊面之一往往源于前后端對業(yè)務邏輯理解的不一致而漏洞就藏在JS實現的邏輯中。密碼重置邏輯繞過找回密碼功能是重災區(qū)。通過JS分析“發(fā)送驗證碼”和“驗證驗證碼”的流程。是否存在“驗證碼僅前端校驗”或者驗證碼與手機號/郵箱的綁定關系是否在客戶端驗證我曾見過一個案例JS代碼中提交重置密碼請求時僅將用戶輸入的驗證碼和當前會話里存儲的驗證碼做比較而完全沒在請求體中帶上要重置的賬號。這意味著只要我獲取到一個有效的驗證碼比如通過自己的手機號獲取我就可以在請求中指定任意username參數從而重置他人密碼。登錄狀態(tài)維持與越權登錄成功后JS如何跳轉是根據后端返回的role字段動態(tài)渲染菜單嗎檢查是否有這樣的代碼if (loginResponse.data.role admin) { window.location.href /admin/dashboard; } else { window.location.href /user/home; }如果攻擊者能篡改后端響應或直接修改本地JS代碼邏輯將role字段改為admin就可能實現前端越權跳轉。雖然后端接口可能還有校驗但這已經打開了第一道門。競態(tài)條件與并發(fā)攻擊JS發(fā)起的請求是否處理了并發(fā)場景比如在“修改郵箱”功能中通常流程是1請求發(fā)送驗證碼到舊郵箱2驗證舊郵箱的驗證碼3綁定新郵箱。如果JS代碼沒有在步驟2成功后禁用“提交新郵箱”的按鈕或者沒有設置有效的狀態(tài)鎖攻擊者可能通過快速并發(fā)請求在驗證舊郵箱的同時將郵箱替換為攻擊者控制的郵箱。2.4 第三方依賴與供應鏈風險現代前端離不開npm包。JS源碼中引入的第三方庫如某個特定的vue-login-plugin、crypto-js的特定版本可能本身存在已知漏洞。通過分析package.json有時會被打包進源碼或通過window對象查看全局變量可以識別出引用的庫及其版本進而查找公開的CVE漏洞。3. 實戰(zhàn)演練親手解剖一個Vue.js登錄頁面的JS源碼光說不練假把式。我們假設目標是一個采用Vue.js 2.x Element UI構建的典型管理后臺登錄頁面。我們將使用Chrome開發(fā)者工具作為主要手術刀。3.1 環(huán)境準備與信息收集打開目標登錄頁假設地址是https://target.com/login。啟動開發(fā)者工具按F12重點關註“Network”網絡和“Sources”源代碼標簽頁。清除并記錄在Network標簽頁勾選“Preserve log”保留日志然后刷新頁面。這樣能看到頁面加載的所有靜態(tài)資源JS、CSS和初始的API請求。定位核心JS文件在Network的“JS”過濾器下尋找文件名中帶login、app、chunk-vendors這是Webpack打包的第三方庫、chunk-xxx業(yè)務代碼的文件。通常app.xxxx.js包含了主邏輯。同時查看Sources標簽頁下的“Page” - “top” - “target.com” - “static/js”目錄這里存放著所有JS文件。注意很多生產環(huán)境會啟用代碼壓縮minify和混淆obfuscation。混淆后的變量名可能是單個字母但函數結構和字符串常量通常保留。我們主要尋找的是硬編碼的URL、接口參數名、調試信息字符串如debug,error和特定的業(yè)務邏輯關鍵詞如login,password,token,validate。3.2 靜態(tài)源碼分析與關鍵信息提取我們找到了一個名為login.4a3b2c1d.js的文件哈希值用于緩存破壞。在Sources面板中打開它雖然代碼被壓縮成一行但我們可以使用格式化工具點擊左下角的{}圖標使其可讀。格式化后我們開始搜索關鍵詞搜索API端點在格式化后的代碼中按CtrlF搜索/api,login,axios,fetch,url,request。可能發(fā)現const LOGIN_API /api/v1/auth/login;可能發(fā)現this.$axios.post(/api/user/signin, this.form)收獲我們確定了登錄請求的精確端點POST /api/v1/auth/login。搜索請求參數結構繼續(xù)在附近代碼中查看data或params對象。// 可能發(fā)現的代碼片段 login() { this.$refs.form.validate(valid { if (valid) { let postData { username: this.form.username, password: this.$md5(this.form.password), // 注意前端MD5加密 captcha: this.form.captcha, deviceId: localStorage.getItem(deviceId) || web }; this.$axios.post(LOGIN_API, postData).then(...) } }) }關鍵發(fā)現1密碼在前端使用了MD5加密。這是一個重大安全信號。雖然避免了明文傳輸但意味著后端數據庫很可能存儲的是MD5哈希值而非加鹽的強哈希如bcrypt。攻擊者無需破解原始密碼只需獲取或碰撞MD5哈希即可登錄彩虹表攻擊。我們可以測試“密碼重置”功能看新密碼是否也以同樣方式處理這可能存在哈希傳遞攻擊Pass-the-Hash的風險。關鍵發(fā)現2請求體中包含deviceId且從localStorage讀取。如果localStorage中沒有則默認為web。這給了我們一個參數操控點。我們可以嘗試修改或枚舉deviceId看后端是否依賴它進行設備綁定或風險識別。搜索驗證邏輯搜索validate,check,rule,if語句。可能發(fā)現客戶端驗證規(guī)則如用戶名必須是郵箱格式。這再次確認了我們可以繞過它。可能發(fā)現對登錄響應response的處理邏輯.then(response { if (response.data.code 200) { localStorage.setItem(access_token, response.data.data.token); localStorage.setItem(user_info, JSON.stringify(response.data.data.user)); // 根據角色跳轉 if (response.data.data.user.role super_admin) { this.$router.push(/super-admin); } else { this.$router.push(/dashboard); } } else { this.$message.error(response.data.message); } })關鍵發(fā)現3跳轉邏輯依賴于后端返回的user.role字段。這本身沒問題但結合后續(xù)的接口訪問如果其他API的權限校驗不嚴前端角色顯示可能誤導管理員。3.3 動態(tài)調試與行為驗證靜態(tài)分析給了我們地圖動態(tài)調試則是實地勘探。我們使用“Debugger”功能。設置斷點在Sources面板找到我們關心的函數比如login()方法的那一行this.$axios.post...點擊行號設置斷點藍色標記。觸發(fā)斷點在登錄頁面輸入測試賬號如test:test123點擊登錄。瀏覽器執(zhí)行會暫停在斷點處。觀察狀態(tài)在右側的“Scope”面板可以看到當前作用域的所有變量值。我們可以查看postData對象的具體內容確認密碼的MD5哈希值。我們可以實時修改變量。比如右鍵點擊postData.deviceId選擇“Edit value”將其改為mobile_app_vip然后放行程序。這相當于在請求發(fā)出前篡改了數據用于測試后端對deviceId參數的處理是否嚴格。攔截與修改請求更強大的工具是配合Burp Suite。將瀏覽器代理指向Burp。在登錄時Burp會攔截到POST /api/v1/auth/login請求。我們可以修改請求體中的任何字段。例如將username改為已知的管理員賬號如admin進行用戶名枚舉測試觀察錯誤信息差異。在JSON末尾添加額外的逗號和參數如extra_param:test測試后端JSON解析器的健壯性。嘗試將captcha參數置空或刪除測試驗證碼是否在后端真正被校驗。3.4 針對“前端MD5加密”的深入攻擊測試這是我們靜態(tài)分析發(fā)現的最有價值的一個點。我們設計以下測試用例測試密碼重置流程走一遍密碼重置流程用Burp攔截“設置新密碼”的請求。觀察新密碼是否也是以MD5哈希的形式發(fā)送如newPassword: e10adc3949ba59abbe56e057f20f883e。如果也是MD5那么攻擊者如果通過其他方式如SQL注入獲取了某個用戶的密碼哈希MD5他無需知道明文直接在重置密碼請求中提交這個舊的MD5哈希作為newPassword即可將該用戶的密碼重置為自己已知哈希對應的密碼如果他知道明文的話或者更直接地他可以用這個哈希直接發(fā)起登錄請求如果后端只是比較哈希值。這就是一種前端哈希傳遞漏洞的雛形。測試登錄接口的哈希直接登錄首先用一個已知賬號密碼如user1:password1正常登錄用Burp攔截請求記下密碼字段的MD5值假設是e10adc...。然后嘗試用另一個賬號如user2但在密碼字段直接填入e10adc...即user1的密碼哈希。發(fā)送請求。如果登錄成功那將是災難性的。這說明后端只是簡單比較數據庫存儲的MD5哈希和前端傳過來的MD5哈希是否一致完全喪失了“鹽”salt的保護意義使得哈希等同于密碼。實操心得在測試前端加密時一定要理解其背后的意圖。前端加密通常是為了避免密碼明文在傳輸中被嗅探提供有限的傳輸層安全但它絕不能替代后端的安全存儲必須使用帶鹽的、計算成本高的哈希算法如Argon2, bcrypt, PBKDF2。一旦發(fā)現前端使用MD5、SHA1等弱哈希幾乎可以肯定后端存儲也存在問題這是一個需要深挖的高危信號。4. 常見漏洞模式與JS源碼中的蛛絲馬跡根據多年經驗我總結了一些在登錄頁面JS源碼中常見的漏洞模式你可以把它們當作檢查清單漏洞模式在JS源碼中可能的表現滲透測試驗證方法客戶端校驗繞過存在validate()、checkForm()等函數僅在前端驗證輸入格式、長度、必填項。禁用瀏覽器JS或直接用Burp等工具發(fā)送請求跳過前端校驗。硬編碼敏感信息代碼中出現明文API密鑰、內部URL、默認密碼、加密密鑰等字符串常量。全局搜索apiKey,secret,password,http://internal,key:等關鍵詞。不安全的憑證傳輸使用http://開頭的API URL或在development模式下使用明文日志打印token。檢查所有axios/fetch的baseURL搜索console.log、alert輸出敏感數據。邏輯缺陷/業(yè)務繞過密碼重置流程中驗證“驗證碼”的請求不攜帶待重置的賬號修改信息時身份標識如user_id由前端提供。分析關鍵業(yè)務流程的函數調用順序和參數嘗試篡改、刪除或重排請求。依賴已知漏洞的第三方庫package.json中或全局變量顯示使用了存在CVE的jquery、lodash、vue等庫的舊版本。識別庫名和版本在NVD、Snyk等漏洞庫中查詢。不安全的跳轉/重定向登錄后跳轉的URLredirectUrl從前端URL參數獲取未經凈化。尋找window.location.href、$router.push、redirect等參數來源測試是否可被控制。客戶端會話固定會話標識如session token在登錄前就已生成并設置登錄后未更新。在未登錄狀態(tài)獲取Cookie或token登錄后觀察該值是否變化。5. 防御視角給開發(fā)者的安全編碼建議分析了這么多攻擊面我們反過來從防御者角度看看開發(fā)登錄頁面時在JS層面應該注意什么牢記“前端無安全”所有前端代碼HTML、CSS、JS對用戶都是透明的、可篡改的。任何關鍵的安全校驗身份認證、權限檢查、輸入有效性最終裁決都必須在后端進行。前端校驗只是為了提升用戶體驗和減少無效請求。避免前端密碼加密除非是與后端協(xié)商的、用于特定安全模型的方案如SRP協(xié)議否則不要在前端對密碼進行哈希加密。應始終使用HTTPS來保證傳輸安全讓密碼以明文形式到達后端由后端進行加鹽哈希存儲。如果必須前端加密例如應對中間人攻擊的額外措施也必須結合隨機鹽、并使用安全的算法且后端需有對應的解密或二次哈希邏輯但這會極大增加復雜性。凈化所有輸入包括來自前端的參數即使參數是前端生成的如deviceId后端也必須進行校驗和凈化。不要信任任何來自客戶端的數據。安全的錯誤處理登錄失敗時返回統(tǒng)一的、模糊的錯誤信息如“用戶名或密碼錯誤”避免提示“用戶名不存在”或“密碼錯誤”這會幫助攻擊者進行用戶名枚舉。使用安全的依賴定期使用npm audit或yarn audit檢查并更新第三方依賴移除不必要的包。代碼混淆與壓縮雖然不能防止攻擊但能提高分析門檻。使用Webpack、Terser等工具進行代碼壓縮、混淆、打包移除源碼中的注釋和調試信息。關鍵操作服務端狀態(tài)鎖對于密碼重置、郵箱修改等敏感操作必須在服務端維護狀態(tài)機防止并發(fā)請求導致邏輯繞過。登錄頁面的攻防是一場永不停歇的貓鼠游戲。攻擊者不斷尋找前端邏輯與后端實現之間的縫隙而防御者需要建立起縱深防御的思維從前端代碼規(guī)范到后端業(yè)務邏輯校驗每一個環(huán)節(jié)都不能掉以輕心。這次針對JS源碼的分析實戰(zhàn)希望能給你提供一個清晰的切入點和一套可操作的方法論。下次面對一個登錄框時別忘了它可能不只是個登錄框而是一個等待被探索的、充滿可能性的攻擊面集合。