本週面試題一覽:javascript
更多優質文章可戳: github.com/YvetteLau/B…css
XSS(Cross-Site Scripting,跨站腳本攻擊)是一種代碼注入攻擊。攻擊者在目標網站上注入惡意代碼,當被攻擊者登錄網站時就會執行這些惡意代碼,這些腳本能夠讀取 cookie,session tokens,或者其它敏感的網站信息,對用戶進行釣魚欺詐,甚至發起蠕蟲攻擊等。html
XSS 的本質是:惡意代碼未通過濾,與網站正常的代碼混在一塊兒;瀏覽器沒法分辨哪些腳本是可信的,致使惡意腳本被執行。因爲直接在用戶的終端執行,惡意代碼可以直接獲取用戶的信息,利用這些信息冒充用戶向網站發起攻擊者定義的請求。前端
根據攻擊的來源,XSS攻擊能夠分爲存儲型(持久性)、反射型(非持久型)和DOM型三種。下面咱們來詳細瞭解一下這三種XSS攻擊:java
1.1 反射型XSS
當用戶點擊一個惡意連接,或者提交一個表單,或者進入一個惡意網站時,注入腳本進入被攻擊者的網站。Web服務器將注入腳本,好比一個錯誤信息,搜索結果等,未進行過濾直接返回到用戶的瀏覽器上。git
反射型 XSS 的攻擊步驟:github
URL
,其中包含惡意代碼。URL
時,網站服務端將惡意代碼從 URL
中取出,拼接在 HTML 中返回給瀏覽器。反射型 XSS 漏洞常見於經過 URL
傳遞參數的功能,如網站搜索、跳轉等。因爲須要用戶主動打開惡意的 URL
才能生效,攻擊者每每會結合多種手段誘導用戶點擊。面試
POST 的內容也能夠觸發反射型 XSS,只不過其觸發條件比較苛刻(須要構造表單提交頁面,並引導用戶點擊),因此很是少見。數據庫
若是不但願被前端拿到cookie,後端能夠設置 httpOnly
(不過這不是 XSS攻擊
的解決方案,只能下降受損範圍)後端
如何防範反射型XSS攻擊
對字符串進行編碼。
對url的查詢參數進行轉義後再輸出到頁面。
app.get('/welcome', function(req, res) {
//對查詢參數進行編碼,避免反射型 XSS攻擊
res.send(`${encodeURIComponent(req.query.type)}`);
});
複製代碼
1.2 DOM 型 XSS
DOM 型 XSS 攻擊,實際上就是前端 JavaScript
代碼不夠嚴謹,把不可信的內容插入到了頁面。在使用 .innerHTML
、.outerHTML
、.appendChild
、document.write()
等API時要特別當心,不要把不可信的數據做爲 HTML 插到頁面上,儘可能使用 .innerText
、.textContent
、.setAttribute()
等。
DOM 型 XSS 的攻擊步驟:
如何防範 DOM 型 XSS 攻擊
防範 DOM 型 XSS 攻擊的核心就是對輸入內容進行轉義(DOM 中的內聯事件監聽器和連接跳轉都能把字符串做爲代碼運行,須要對其內容進行檢查)。
1.對於url
連接(例如圖片的src
屬性),那麼直接使用 encodeURIComponent
來轉義。
2.非url
,咱們能夠這樣進行編碼:
function encodeHtml(str) {
return str.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
複製代碼
DOM 型 XSS 攻擊中,取出和執行惡意代碼由瀏覽器端完成,屬於前端 JavaScript 自身的安全漏洞。
1.3 存儲型XSS
惡意腳本永久存儲在目標服務器上。當瀏覽器請求數據時,腳本從服務器傳回並執行,影響範圍比反射型和DOM型XSS更大。存儲型XSS攻擊的緣由仍然是沒有作好數據過濾:前端提交數據至服務端時,沒有作好過濾;服務端在接受到數據時,在存儲以前,沒有作過濾;前端從服務端請求到數據,沒有過濾輸出。
存儲型 XSS 的攻擊步驟:
這種攻擊常見於帶有用戶保存數據的網站功能,如論壇發帖、商品評論、用戶私信等。
如何防範存儲型XSS攻擊:
除了謹慎的轉義,咱們還須要其餘一些手段來防範XSS攻擊:
1.Content Security Policy
在服務端使用 HTTP的 Content-Security-Policy
頭部來指定策略,或者在前端設置 meta
標籤。
例以下面的配置只容許加載同域下的資源:
Content-Security-Policy: default-src 'self'
複製代碼
<meta http-equiv="Content-Security-Policy" content="form-action 'self';">
複製代碼
前端和服務端設置 CSP 的效果相同,可是meta
沒法使用report
嚴格的 CSP 在 XSS 的防範中能夠起到如下的做用:
2.輸入內容長度控制
對於不受信任的輸入,都應該限定一個合理的長度。雖然沒法徹底防止 XSS 發生,但能夠增長 XSS 攻擊的難度。
3.輸入內容限制
對於部分輸入,能夠限定不能包含特殊字符或者僅能輸入數字等。
4.其餘安全措施
屏幕並非惟一的輸出機制,好比說屏幕上看不見的元素(隱藏的元素),其中一些依然可以被讀屏軟件閱讀出來(由於讀屏軟件依賴於可訪問性樹來闡述)。爲了消除它們之間的歧義,咱們將其歸爲三大類:
display
屬性(不佔據空間)display: none;
複製代碼
HTML5 新增屬性,至關於 display: none
<div hidden>
</div>
複製代碼
position
和 盒模型 將元素移出可視區範圍posoition
爲 absolute
或 fixed
,經過設置 top
、left
等值,將其移出可視區域。(可視區域不佔位)position:absolute;
left: -99999px;
複製代碼
position
爲 relative
,經過設置 top
、left
等值,將其移出可視區域。(可視區域佔位)position: relative;
left: -99999px;
複製代碼
如但願其在可視區域不佔位置,需同時設置 height: 0
;
margin-left: -99999px;
複製代碼
若是但願其在可視區域不佔位,需同時設置 height: 0
;
transform: scale(0);
複製代碼
若是但願不佔據空間,需同時設置 height: 0
translateX
, translateY
(佔據空間)transform: translateX(-99999px);
複製代碼
若是但願不佔據空間,需同時設置 height: 0
rotate
(佔據空間)transform: rotateY(90deg);
複製代碼
寬高爲0,字體大小爲0:
height: 0;
width: 0;
font-size: 0;
複製代碼
寬高爲0,超出隱藏:
height: 0;
width: 0;
overflow: hidden;
複製代碼
opacity: 0;
複製代碼
visibility
屬性 (佔據空間)visibility: hidden
複製代碼
z-index
屬性 (佔據空間)position: relative;
z-index: -999;
複製代碼
再設置一個層級較高的元素覆蓋在此元素上。
clip-path: polygon(0 0, 0 0, 0 0, 0 0);
複製代碼
讀屏軟件不可讀,佔據空間,可見。
<div aria-hidden="true">
</div>
複製代碼
在說瀏覽器事件代理機制原理以前,咱們首先了解一下事件流的概念,早期瀏覽器,IE採用的是事件捕獲事件流,而Netscape採用的則是事件冒泡。"DOM2級事件"把事件流分爲三個階段,捕獲階段、目標階段、冒泡階段。現代瀏覽器也都遵循此規範。
事件代理又稱爲事件委託,在祖先級 DOM 元素綁定一個事件,當觸發子孫級DOM元素的事件時,利用事件冒泡的原理來觸發綁定在祖先級 DOM 的事件。由於事件會從目標元素一層層冒泡至 document 對象。
添加到頁面上的事件數量會影響頁面的運行性能,若是添加的事件過多,會致使網頁的性能降低。採用事件代理的方式,能夠大大減小注冊事件的個數。
事件代理的當時,某個子孫元素是動態增長的,不須要再次對其進行事件綁定。
不用擔憂某個註冊了事件的DOM元素被移除後,可能沒法回收其事件處理程序,咱們只要把事件處理程序委託給更高層級的元素,就能夠避免此問題。
容許給一個事件註冊多個監聽。
提供了一種更精細的手段控制 listener
的觸發階段(能夠選擇捕獲或者是冒泡)。
對任何 DOM
元素都是有效的,而不只僅是對 HTML
元素有效。
addEventListener 接受3個參數,分別是要處理的事件名、實現了 EventListener 接口的對象或者是一個函數、一個對象/一個布爾值。
target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
複製代碼
options(對象) | 可選
capture: Boolean
。true 表示在捕獲階段觸發,false表示在冒泡階段觸發。默認是 false。
once:Boolean
。true 表示listener 在添加以後最多隻調用一次,listener 會在其被調用以後自動移除。默認是 false。
passive: Boolean
。true 表示 listener 永遠不會調用 preventDefault()
。若是 listener 仍然調用了這個函數,客戶端將會忽略它並拋出一個控制檯警告。默認是 false。
useCapture(Boolean) | 可選
useCapture
默認爲 false。表示冒泡階段調用事件處理程序,若設置爲 true,表示在捕獲階段調用事件處理程序。
如將頁面中的全部click事件都代理到document上:
document.addEventListener('click', function (e) {
console.log(e.target);
/** * 捕獲階段調用調用事件處理程序,eventPhase是 1; * 處於目標,eventPhase是2 * 冒泡階段調用事件處理程序,eventPhase是 3; */
console.log(e.eventPhase);
}, false);
複製代碼
與 addEventListener
相對應的是 removeEventListener
,用於移除事件監聽。
setTimeout
只能保證延時或間隔不小於設定的時間。由於它實際上只是將回調添加到了宏任務隊列中,可是若是主線程上有任務尚未執行完成,它必需要等待。
若是你對前面這句話不是很是理解,那麼有必要了解一下 JS的運行機制。
(1)全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。
(2)主線程以外,還存在"任務隊列"(task queue)。
(3)一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重複上面的第三步。
如 setTimeout(()=>{callback();}, 1000)
,即表示在1s以後將 callback
放到宏任務隊列中,當1s的時間到達時,若是主線程上有其它任務在執行,那麼 callback
就必需要等待,另外 callback
的執行也須要時間,所以 setTimeout
的時間間隔是有偏差的,它只能保證延時不小於設置的時間。
setTimeout
的偏差咱們只能減小執行屢次的 setTimeout
的偏差,例如倒計時功能。
倒計時的時間一般都是從服務端獲取的。形成偏差的緣由:
1.沒有考慮偏差時間(函數執行的時間/其它代碼的阻塞)
2.沒有考慮瀏覽器的「休眠」
徹底消除 setTimeout
的偏差是不可能的,可是咱們減小 setTimeout
的偏差。經過對下一次任務的調用時間進行修正,來減小偏差。
let count = 0;
let countdown = 5000; //服務器返回的倒計時時間
let interval = 1000;
let startTime = new Date().getTime();
let timer = setTimeout(countDownStart, interval); //首次執行
//定時器測試
function countDownStart() {
count++;
const offset = new Date().getTime() - (startTime + count * 1000);
const nextInterval = interval - offset; //修正後的延時時間
if (nextInterval < 0) {
nextInterval = 0;
}
countdown -= interval;
console.log("偏差:" + offset + "ms,下一次執行:" + nextInterval + "ms後,離活動開始還有:" + countdown + "ms");
if (countdown <= 0) {
clearTimeout(timer);
} else {
timer = setTimeout(countDownStart, nextInterval);
}
}
複製代碼
若是當前頁面是不可見的,那麼倒計時會出現大於100ms的偏差時間。所以在頁面顯示時,應該從新從服務端獲取剩餘時間進行倒計時。固然,爲了更好的性能,當倒計時不可見(Tab頁切換/倒計時內容不在可視區時),能夠選擇中止倒計時。
爲此,咱們能夠監聽 visibityChange
事件進行處理。
[2] www.ecma-international.org/ecma-262/6.…
[3] www.xuanfengge.com/js-realizes…
謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。github.com/YvetteLau/B…