在 Web 安全中,服務端一直扮演着十分重要的角色。然而前端的問題也不容小覷,它也會導致信息泄露等諸如此類的問題。在這篇文章中,我們將向讀者介紹如何防範Web前端中的各種漏洞。【萬字長文,請先收藏再閱讀】
首先,我們需要了解安全防禦產品已經爲我們做了哪些工作。其次,我們將探討前端存在哪些漏洞,並提供相應的防範思路。
一、安全防禦產品
安全防禦產品一般有:
傳統互聯網公司的安全防禦體系,類似於一個空氣淨化模型,每個層都有不同的產品,包括網絡層防護、應用層防護、主機層防護、運行時防護、安全開發防護和安全運營防護。外部攻擊流量經過過濾後,到達應用系統的流量相對較爲安全。
對於我們前端而言,最有用的是安全開發防護層中的白盒和黑盒。白盒掃描是對我們的源代碼進行掃描,在上線時會自動檢測問題。黑盒掃描則不考慮程序的結構和代碼細節,對應用程序進行漏洞掃描,每天都有定時任務掃描公網。
在安全方面,我們與集團安全部門進行雙向合作。在集團安全防禦體系已經做了一些工作的基礎上,我們如何針對前端可能存在的漏洞進行防範,是我們研發部門需要思考的問題。
二、漏洞防範
1、安全傳輸
首先,務必使用HTTPS協議,這可以有效防止局域網內的明文抓包。
2、域分離
其次,進行域分離。將一些業務關聯性較小的內容轉移到不相關的域中。如果分離域下出現了XSS漏洞,不會影響業務主域,而且分離域下的XSS漏洞也無法獲得獎勵。如果所有內容都在主域下,一旦主域出現漏洞,主域下的所有子域都會受到破壞。
前端領域常見的漏洞有3種:XSS漏洞、CSRF漏洞、界面操作劫持漏洞
3、XSS防禦方案
對於概念,包括名詞定義、攻擊方式、解決方案等估計大家都看過不少,但留下印象總是很模糊,要動手操作一番才能加深印象並能真正理解。
1)XSS 模擬攻擊
先動手實現一個 XSS 的攻擊場景,然後再講解 XSS 的防範手段。看這個代碼:
點擊“write”按鈕後,會在頁面插入一個a鏈接,a鏈接的跳轉地址是文本框的內容。在這裏,通過innerHTML把一段用戶輸入的數據當做HTML寫入到頁面中,這就造成了XSS漏洞。嘗試如下輸入:
首先用一個單引號閉合掉href的第一個單引號,然後閉合掉<a>標籤,然後插入一個img標籤。頁面代碼變成了:
腳本被執行:
所以你明白了吧,XSS跨站腳本攻擊就是注入一段腳本,並且該腳本能夠被執行。其中最常見的XSS Payload就是讀取瀏覽器的Cookie對象。通過盜取Cookie,攻擊者可以直接調用接口,發起“Cookie劫持”攻擊。
爲了防止“Cookie劫持”,集團的統一登錄使用了Cookie的“HttpOnly”和“Secure”標識。
HttpOnly標識表示該Cookie僅在HTTP層面傳輸。一旦設置了HttpOnly標誌,客戶端腳本就無法讀寫該Cookie,從而有效地防禦XSS攻擊獲取Cookie的風險。
Secure標識確保Cookie只在加密的HTTPS連接中傳輸,從而降低了“Cookie劫持”的風險。
因此,建議避免使用localStorage存儲敏感信息,哪怕這些信息進行過加密。因爲localStorage存儲,沒有針對XSS攻擊,做任何防禦機制。一旦出現XSS漏洞,那麼存儲在localStorage裏的數據就極易被獲取到。
2)XSS的危害
讓我們簡單談談XSS的危害。注入的腳本可能會進行JavaScript函數劫持,覆蓋我們代碼中原有的函數,修改js原型鏈上的函數,或在原函數基礎上添加額外行爲。例如,將網絡請求的響應發送到攻擊者的服務器。
JavaScript函數劫持是什麼?它是在目標函數觸發之前重寫某個函數的過程。例如:
let _write = document.write.bind(document);
document.write = function(x) {
if(typeof(x) == "undefined") { return; }
_write(x);
}
注入的腳本還可能對用戶進行內存攻擊。如果知道用戶使用的瀏覽器、操作系統,攻擊者就有可以實施一次精準的瀏覽器內存攻擊。通過XSS,可以讀取瀏覽器的UserAgent對象。
alert(navigator.userAgent);
該對象提供了客戶端的信息,如操作系統版本:Mac OS X 10_15_7,瀏覽器版本:Chrome/114.0.0.0
注入的腳本可能強制彈出廣告頁面、刷流量,也可能進行大量的DDoS攻擊,導致網絡擁塞。此外,它還可能控制受害者的機器向其他網站發起攻擊。
如果信貸產品的網頁存在XSS漏洞,黑客可以注入一段腳本,調用查詢借還記錄的接口,並將響應結果發送到自己的服務器。然後,利用用戶的借還記錄等信息,對用戶進行電信詐騙。
第三方統計腳本有機會竊取用戶的敏感信息,如瀏覽歷史、真實IP、地理位置、設備信息,並將其傳送給攻擊者。此外,我們使用的第三方依賴和npm包也有可能竊取用戶信息。在這方面,集團對代碼進行了白盒掃描,已一定程度上規避問題。但最好我們在選擇第三方包時,選擇可信賴的包。
3)XSS 防範方法
XSS 防範方法通常有以下幾種:
①慎防第三方內容:對於流行的統計腳本和第三方依賴,要謹慎使用。
②輸入校驗和輸出編碼:進行輸入校驗,包括長度限制、值類型是否正確以及是否包含特殊字符(如<>)。同時,在輸出時進行相應的編碼,根據輸出的位置選擇適當的編碼方式,如HTML編碼和URL編碼。
③使用HttpOnly屬性:防範XSS攻擊後的"cookie劫持"。
使用Secure屬性,確保Cookie只在加密的HTTPS連接中傳輸,降低"cookie劫持"的風險。
④避免使用localStorage存儲敏感信息。
⑤使用Content-Security-Policy(內容安全策略):可以通過指定只允許加載來自特定域名的腳本,防範XSS攻擊,例如,可以使用以下配置只允許加載來自mjt.jd.com域名的腳本
# 只允許加載來自特定域名的腳本
add_header Content-Security-Policy "script-src mjt.jd.com";
4、CSRF防禦方案
CSRF漏洞是借用用戶的權限做一些事情,注意,是“借用”,而不是“盜取”。XSS漏洞是“盜取”用戶權限,CSRF漏洞是“借用”用戶權限。
1)CSRF 模擬攻擊
我們先動手實現一個 CSRF 攻擊場景,然後再介紹 CSRF 的防範手段。我們要模擬以下場景:用戶先登錄了銀行網站,然後黑客網站誘導用戶訪問和點擊,從而利用用戶的登錄權限,讓用戶給黑客自己轉賬。我們使用 express 啓動一個服務,模擬 CSRF 攻擊。銀行網站的服務啓動在 3001 端口,並提供以下3個接口:
app.use('/', indexRouter);
app.use('/auth', authRouter);
app.use('/transfer', transferRouter);
authRouter:
router.get('/', function(req, res, next) {
res.cookie('userId', 'ce032b305a9bc1ce0b0dd2a', { expires: new Date(Date.now() + 900000) })
res.end('ok')
});
/auth 接口在 cookie 中設置了一個名爲 userId 的 cookie,即給用戶授予登錄權限。
transferRouter:
router.get('/', function(req, res, next) {
const { query } = req;
const { userId } = req.cookies;
if(userId){
res.send({
status: 'transfer success',
transfer: query.number
})
}else{
res.send({
status: 'error',
transfer: ''
})
}
});
/transfer 接口判斷了 cookie,如果存在 cookie,則轉賬成功,否則轉賬失敗。
使用 ejs 提供銀行轉賬頁面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
<%= title %>
</title>
</head>
<body>
<h2>
轉賬
</h2>
<script>
const h2 = document.querySelector('h2');
h2.addEventListener('click', () => {
fetch('/transfer?number=15000&to=Bob').then(res => {
console.log(res.json());
})
})
</script>
</body>
</html>
黑客網站的服務啓動在 3002 端口,並提供一個與銀行網站外觀相同的頁面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
</head>
<body>
<div class="wrapper" id="container">
<h2>
轉賬
</h2>
<form action="http://bank.com/transfer" method=GET>
<input type="hidden" name="number" value="150000" />
<input type="hidden" name="to" value="Jack" />
</form>
<script>
const h2 = document.querySelector('h2');
h2.addEventListener('click', () => {
submitForm();
})
function submitForm() {
document.forms[0].submit();
}
</script>
</div>
</body>
</html>
由於兩個網站都是在 localhost 域名下,cookie 是根據域名而不是端口進行區分的。因此,我們使用 Whistle 進行域名映射:
# bank.com 映射到 127.0.0.1:3001
bank.com 127.0.0.1:3001
# hack.com 映射到 127.0.0.1:3002
hack.com 127.0.0.1:3002
現在我們開始操作。首先打開瀏覽器,訪問銀行網站 的 /auth獲得授權:
然後通過點擊”轉賬”按鈕發送請求,http://bank.com/transfer?number=15000&to=Bob,進行轉賬操作:
用戶受到郵件或者廣告誘惑進入了 黑客網站,黑客網站首頁有一個“轉賬”按鈕,調銀行的transfer接口 http://bank.com/transfer?number=150000&to=Jack 這個請求放在 <form /> 的 action 中
可以看到請求攜帶了 cookie,併成功轉賬,這樣一次 CSRF 攻擊就完成了。
從上面可以看出,CSRF 攻擊的主要特點是:
①發生在第三方域名(hack.com)
②攻擊者利用 cookie 而不獲取具體的 cookie 值
因此,防範 CSRF 攻擊的關鍵是防止其他人冒充你去執行只有你能執行的敏感操作。
2)CSRF 危害
簡單說,CSRF會導致:個人隱私泄露、機密資料泄露、甚至危及用戶和企業的財產安全。一句話概括CSRF的危害:盜用受害者身份,受害者能做什麼,攻擊者就能以受害者的身份做什麼。
3)CSRF 防範方法
①阻止不同域的訪問:同源檢測
在HTTP請求中檢查Referer或origin字段,確保請求來源是站內地址和站內域名。如果發現Referer或origin地址異常,就可以懷疑遭到了CSRF攻擊。
②使用驗證碼
儘管會降低用戶體驗,但驗證碼是防止CSRF攻擊的最有效手段。
③提交時要求附加本域才能獲取的信息
例如使用一次性token(生成token的因子包括時間戳和用戶ID)來驗證請求的合法性。
④不要允許所有域訪問,使用allow-access-from domain時避免以下方式,即根據前端請求的域來允許訪問:
String origin = request.getHeader("origin");
if (StringUtils.isNotEmpty(origin)) {
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
} else {
response.setHeader("Access-Control-Allow-Origin", "*");
}
這種方式是不可取的,應該限制訪問的域。
5、界面操作劫持防禦方案
它是一種基於視覺欺騙的攻擊方式,其核心在於利用標籤的透明屬性。攻擊者會在網頁的可見輸入控件上覆蓋一個不可見的框,從而讓用戶誤以爲自己在操作可見控件,實際上卻是在操作不可見框。
界面操作劫持還包括:點擊劫持、拖放劫持、觸屏劫持
1)常見的攻擊場景
點擊劫持的常見攻擊場景是僞造登錄框。當用戶在僞造的登錄框中輸入用戶名和密碼後,他們以爲自己點擊的是登錄頁面上的登錄按鈕,但實際上他們點擊的是黑客頁面上的“登錄”按鈕,導致密碼被髮送到黑客的服務器上。
拖放劫持利用了拖放操作不受“同源策略”限制的特點,用戶可以把一個域的內容拖放到另一個不同的域。攻擊者結合CSRF漏洞,將iframe中目標網頁的token拖放到攻擊者的頁面中,從而實施攻擊。
觸屏劫持是在手機上進行的一種視覺欺騙攻擊。由於手機屏幕空間有限,手機瀏覽器會隱藏地址欄以節省空間。攻擊者常常利用這一點,當觸發一個權限獲取的提示框時,他們會將提示框的主體背景設爲透明,並覆蓋上僞造的消息提示圖像,只留下權限提示框的確認按鈕。這樣,用戶會誤以爲自己在點擊某個消息的確認,實際上卻是在點擊權限確認。
2)界面操作劫持的防範方法
①Content-Security-Policy (內容安全策略)
# 防禦界面操作劫持
# add_header Content-Security-Policy "frame-ancestors 'self';"; # 只允許網頁在相同被嵌套到框架
# add_header Content-Security-Policy "frame-ancestors 'none';"; # 禁止網頁在任何域名下被嵌套到框架
# add_header Content-Security-Policy "frame-ancestors mkt.shop.jd.com"; # 只允許網頁在某些域名被嵌套到框架
②腳本防禦:破壞frame,防止利用透明層進行操作劫持攻擊
if (top === self) {
document.documentElement.style.display = 'block';
} else {
top.location = self.location;
}
③使用iframe嵌入目標網站進行測試,若成功嵌入,則說明可能存在漏洞。
三、培養安全意識
爲了確保代碼的安全性,我們需要培養以下幾點安全意識:
通過培養這些安全意識,我們可以更好地保護我們的代碼和系統,減少安全風險和潛在的攻擊。
作者:京東科技 張朝陽
來源:京東雲開發者社區