Web 安全漏洞之 XSS 攻擊
編者說:作為JS系工程師接觸最多的漏洞我想就是 XSS 漏洞了,然鵝並不是所有的同學對其都有一個清晰的認識。今天我們請來了@盧士傑 同學為我們分享他眼中的 XSS 漏洞攻擊,希望能幫助到大家。
什麼是 XSS 攻擊
XSS(Cross-Site Scripting)又稱跨站指令碼,XSS的重點不在於跨站點,而是在於指令碼的執行。XSS是一種經常出現在 Web 應用程式中的電腦保安漏洞,是由於 Web 應用程式對使用者的輸入過濾不足而產生的。
常見的 XSS 攻擊有三種:反射型、DOM-based 型、儲存型。 其中反射型、DOM-based 型可以歸類為非持久型 XSS 攻擊,儲存型歸類為持久型 XSS 攻擊。
1.反射型
反射型 XSS 一般是攻擊者通過特定手法(如電子郵件),誘使使用者去訪問一個包含惡意程式碼的 URL,當受害者點選這些專門設計的連結的時候,惡意程式碼會直接在受害者主機上的瀏覽器執行。
對於訪問者而言是一次性的,具體表現在我們把我們的惡意指令碼通過 URL 的方式傳遞給了伺服器,而伺服器則只是不加處理的把指令碼“反射”回訪問者的瀏覽器而使訪問者的瀏覽器執行相應的指令碼。反射型 XSS 的觸發有後端的參與,要避免反射性 XSS,必須需要後端的協調,後端解析前端的資料時首先做相關的字串檢測和轉義處理。
此類 XSS 通常出現在網站的搜尋欄、使用者登入口等地方,常用來竊取客戶端 Cookies 或進行釣魚欺騙。
整個攻擊過程大約如下:
2.DOM-based 型
客戶端的指令碼程式可以動態地檢查和修改頁面內容,而不依賴於伺服器端的資料。例如客戶端如從 URL 中提取資料並在本地執行,如果使用者在客戶端輸入的資料包含了惡意的 JavaScript 指令碼,而這些指令碼沒有經過適當的過濾和消毒,那麼應用程式就可能受到 DOM-based XSS 攻擊。需要特別注意以下的使用者輸入源 document.URL
、location.hash
、location.search
、document.referrer
等。
整個攻擊過程大約如下:
3.儲存型
攻擊者事先將惡意程式碼上傳或儲存到漏洞伺服器中,只要受害者瀏覽包含此惡意程式碼的頁面就會執行惡意程式碼。這就意味著只要訪問了這個頁面的訪客,都有可能會執行這段惡意指令碼,因此儲存型XSS的危害會更大。
儲存型 XSS 一般出現在網站留言、評論、部落格日誌等互動處,惡意指令碼儲存到客戶端或者服務端的資料庫中。
整個攻擊過程大約如下:
XSS 攻擊的危害
XSS 可以導致:
- 攻擊劫持訪問;
- 盜用 cookie 實現無密碼登入;
- 配合 csrf 攻擊完成惡意請求;
- 使用 js 或 css 破壞頁面正常的結構與樣式等;
防禦方法
1. XSS 防禦之 HTML 編碼
應用範圍:將不可信資料放入到 HTML 標籤內(例如div、span等)的時候進行HTML編碼。
編碼規則:將 & < > " ' / 轉義為實體字元(或者十進位制、十六進位制)。
示例程式碼:
function encodeForHTML(str, kwargs){
return ('' + str)
.replace(/&/g, '&')
.replace(/</g, '<') // DEC=> < HEX=> < Entity=> <
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''') // ' 不推薦,因為它不在HTML規範中
.replace(/\//g, '/');
};
複製程式碼
HTML 有三種編碼表現方式:十進位制、十六進位制、命名實體。例如小於號(<)可以編碼為 "十進位制> <", "十六進位制=> <", "命名實體=> <" 三種方式。對於單引號(')由於實體字元編碼方式不在 HTML 規範中,所以此處使用了十六進位制編碼。
2. XSS 防禦之 HTML Attribute 編碼
應用範圍:將不可信資料放入 HTML 屬性時(不含src、href、style 和事件處理屬性),進行 HTML Attribute 編碼
編碼規則:除了字母數字字元以外,使用 &#xHH;(或者可用的命名實體)格式來轉義ASCII值小於256所有的字元
示例程式碼:
function encodeForHTMLAttibute(str, kwargs){
let encoded = '';
for(let i = 0; i < str.length; i++) {
let ch = hex = str[i];
if (!/[A-Za-z0-9]/.test(str[i]) && str.charCodeAt(i) < 256) {
hex = '&#x' + ch.charCodeAt(0).toString(16) + ';';
}
encoded += hex;
}
return encoded;
};
複製程式碼
3. XSS 防禦之 JavaScript 編碼
作用範圍:將不可信資料放入事件處理屬性、JavaScirpt值時進行 JavaScript 編碼
編碼規則:除字母數字字元外,請使用\xHH格式轉義ASCII碼小於256的所有字元
示例程式碼:
function encodeForJavascript(str, kwargs) {
let encoded = '';
for(let i = 0; i < str.length; i++) {
let cc = hex = str[i];
if (!/[A-Za-z0-9]/.test(str[i]) && str.charCodeAt(i) < 256) {
hex = '\\x' + cc.charCodeAt().toString(16);
}
encoded += hex;
}
return encoded;
};
複製程式碼
4. XSS 防禦之 URL 編碼
作用範圍:將不可信資料作為 URL 引數值時需要對引數進行 URL 編碼
編碼規則:將引數值進行 encodeURIComponent 編碼
示例程式碼:
function encodeForURL(str, kwargs){
return encodeURIComponent(str);
};
複製程式碼
5. XSS 防禦之 CSS 編碼
作用範圍:將不可信資料作為 CSS 時進行 CSS 編碼
編碼規則:除了字母數字字元以外,使用\XXXXXX格式來轉義ASCII值小於256的所有字元
示例程式碼:
function encodeForCSS (attr, str, kwargs){
let encoded = '';
for (let i = 0; i < str.length; i++) {
let ch = str.charAt(i);
if (!ch.match(/[a-zA-Z0-9]/) {
let hex = str.charCodeAt(i).toString(16);
let pad = '000000'.substr((hex.length));
encoded += '\\' + pad + hex;
} else {
encoded += ch;
}
}
return encoded;
};
複製程式碼
後記
在任何時候使用者的輸入都是不可信的。對於 HTTP 引數,理論上都要進行驗證,例如某個欄位是列舉型別,其就不應該出現列舉以為的值;對於不可信資料的輸出要進行相應的編碼;此外httpOnly
、CSP
、X-XSS-Protection
、Secure Cookie
等也可以起到有效的防護。
XSS 漏洞有時比較難發現,所幸當下React、Vue等框架都從框架層面引入了 XSS 防禦機制,一定程度上解放了我們的雙手。 但是作為開發人員依然要了解 XSS 基本知識、於細節處避免製造 XSS 漏洞。框架是輔助,我們仍需以人為本,規範開發習慣,提高 Web 前端安全意識。