我在登錄功能埋的坑:HTTP重定向攻擊差點(diǎn)讓公司背鍋(附解決方案)
上周我在公司捅了個(gè)簍子——自己寫的登錄模塊差點(diǎn)成了釣魚網(wǎng)站的幫兇。今天就跟大家嘮嘮這個(gè)驚險(xiǎn)過(guò)程,以及怎么避免HTTP重定向攻擊這個(gè)"隱形炸彈"。
一、那個(gè)讓測(cè)試妹子暴走的早晨
事情發(fā)生在某個(gè)陽(yáng)光明媚的周一,測(cè)試組的小美突然沖進(jìn)我們開發(fā)組:"你們的登錄接口被劫持了!用戶點(diǎn)完登錄直接跳轉(zhuǎn)到賭博網(wǎng)站!"
我當(dāng)時(shí)的反應(yīng):"絕對(duì)不可能!我明明做了URL白名單驗(yàn)證..."
1.1 問(wèn)題重現(xiàn):用戶登錄變賭博
我們復(fù)現(xiàn)了問(wèn)題場(chǎng)景:
- 用戶訪問(wèn) www.our-app.com/login?redirect=/profile
- 輸入正確賬號(hào)密碼
- 頁(yè)面跳轉(zhuǎn)到...澳門首家線上賭場(chǎng)(?。?/li>
我盯著瀏覽器的Network面板,發(fā)現(xiàn)請(qǐng)求里赫然有個(gè)302狀態(tài)碼:
HTTP/1.1 302 Found
Location: https://malicious-site.com?steal_cookie=123abc
關(guān)鍵問(wèn)題解析:起初我們以為用戶訪問(wèn)的是正常路徑/profile,但實(shí)際攻擊發(fā)生時(shí),redirect參數(shù)是經(jīng)過(guò)精心偽裝的:
圖片
攻擊者如何操作?
- 構(gòu)造釣魚鏈接:www.our-app.com/login?redirect=%2F%2Fmalicious-site.com(%2F是"/"的URL編碼)
- 服務(wù)器收到參數(shù)后:
// 未解碼直接拼接
String redirect = request.getParameter("redirect"); // 得到"http://malicious-site.com"
response.sendRedirect("https://our-app.com" + redirect);
實(shí)際跳轉(zhuǎn)地址變成:https://our-app.com//malicious-site.com瀏覽器自動(dòng)解析為:https://malicious-site.com
為什么測(cè)試時(shí)沒(méi)發(fā)現(xiàn)?
我們?cè)跍y(cè)試環(huán)境用的都是類似/profile的簡(jiǎn)單路徑,完全沒(méi)料到這些騷操作:
- //外部網(wǎng)站 的路徑拼接攻擊
- @惡意域名 的特殊解析
- %編碼 的繞過(guò)手法
二、解剖這只"重定向蟑螂"
2.1 重定向的工作原理
圖片
就像快遞員送錯(cuò)包裹:
- 用戶說(shuō):"送完這個(gè)去A地址"(帶redirect參數(shù))
- 服務(wù)器說(shuō):"好的,下個(gè)包裹送到B地址"(返回302+Location)
- 快遞員(瀏覽器)無(wú)腦照做
2.2 漏洞代碼長(zhǎng)啥樣?
這是我最初寫的危險(xiǎn)代碼(Java示例):
// 危險(xiǎn)示范!請(qǐng)勿模仿!
String redirectUrl = request.getParameter("redirect");
response.sendRedirect(redirectUrl);
三、我是怎么填坑的
3.1 第一層防護(hù):白名單驗(yàn)證
List<String> allowedPaths = Arrays.asList("/profile", "/dashboard");
if(!allowedPaths.contains(redirectParam)){
redirectParam = "/default"; // 跳轉(zhuǎn)到安全頁(yè)面
}
3.2 第二層防護(hù):簽名校驗(yàn)
給redirect參數(shù)加"防偽碼":
# 生成簽名
sign = hashlib.sha256(redirect_path + SECRET_KEY).hexdigest()
safe_url = f"{redirect_path}?sign={sign}"
# 驗(yàn)證時(shí)
client_sign = request.GET.get('sign')
server_sign = hashlib.sha256(redirect_path + SECRET_KEY).hexdigest()
if client_sign != server_sign:
abort(403)
3.3 第三層防護(hù):相對(duì)路徑轉(zhuǎn)換
把絕對(duì)URL變成相對(duì)路徑:
// 把 https://www.our-app.com/profile 轉(zhuǎn)為 /profile
function sanitizeRedirect(url) {
return new URL(url).pathname;
}
四、預(yù)防重定向攻擊的五個(gè)要點(diǎn)
- 絕不信任客戶端傳參:把redirect參數(shù)當(dāng)嫌疑人審
- 禁用開放重定向:就像不給陌生人留家門鑰匙
- 設(shè)置跳轉(zhuǎn)延遲:重要操作前加二次確認(rèn)
- 記錄可疑日志:給異常跳轉(zhuǎn)裝監(jiān)控
- 定期安全掃描:用自動(dòng)化工具查漏
五、血淚教訓(xùn)總結(jié)
這次事故讓我明白:安全不是功能,而是底線?,F(xiàn)在每次處理重定向時(shí),我都會(huì)默念三遍:
"用戶傳參猛于虎,未經(jīng)驗(yàn)證就是賭,白名單加簽名鎖,安全紅線不能觸。"
最后送大家一個(gè)自查清單:
? [ ] 所有redirect參數(shù)是否強(qiáng)制校驗(yàn)?
? [ ] 是否存在裸跳轉(zhuǎn)(直接拼接URL)?
? [ ] 是否配置了CSP安全策略?
? [ ] 是否禁用非必要的HTTP方法?
? [ ] 是否定期進(jìn)行滲透測(cè)試?