【Step-By-Step】高頻面試題深刻解析 / 週刊03

本週面試題一覽:javascript

  • 什麼是XSS攻擊,XSS 攻擊能夠分爲哪幾類?咱們如何防範XSS攻擊?
  • 如何隱藏頁面中的某個元素?
  • 瀏覽器事件代理機制的原理是什麼?
  • setTimeout 倒計時爲何會出現偏差?

更多優質文章可戳: github.com/YvetteLau/B…css

11. 什麼是XSS攻擊,XSS攻擊能夠分爲哪幾類?咱們如何防範XSS攻擊?

1. XSS攻擊

XSS(Cross-Site Scripting,跨站腳本攻擊)是一種代碼注入攻擊。攻擊者在目標網站上注入惡意代碼,當被攻擊者登錄網站時就會執行這些惡意代碼,這些腳本能夠讀取 cookie,session tokens,或者其它敏感的網站信息,對用戶進行釣魚欺詐,甚至發起蠕蟲攻擊等。html

XSS 的本質是:惡意代碼未通過濾,與網站正常的代碼混在一塊兒;瀏覽器沒法分辨哪些腳本是可信的,致使惡意腳本被執行。因爲直接在用戶的終端執行,惡意代碼可以直接獲取用戶的信息,利用這些信息冒充用戶向網站發起攻擊者定義的請求。前端

XSS分類

根據攻擊的來源,XSS攻擊能夠分爲存儲型(持久性)、反射型(非持久型)和DOM型三種。下面咱們來詳細瞭解一下這三種XSS攻擊:java

1.1 反射型XSS

當用戶點擊一個惡意連接,或者提交一個表單,或者進入一個惡意網站時,注入腳本進入被攻擊者的網站。Web服務器將注入腳本,好比一個錯誤信息,搜索結果等,未進行過濾直接返回到用戶的瀏覽器上。git

反射型 XSS 的攻擊步驟:github

  1. 攻擊者構造出特殊的 URL,其中包含惡意代碼。
  2. 用戶打開帶有惡意代碼的 URL 時,網站服務端將惡意代碼從 URL 中取出,拼接在 HTML 中返回給瀏覽器。
  3. 用戶瀏覽器接收到響應後解析執行,混在其中的惡意代碼也被執行。
  4. 惡意代碼竊取用戶數據併發送到攻擊者的網站,或者冒充用戶的行爲,調用目標網站接口執行攻擊者指定的操做。

反射型 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.appendChilddocument.write()等API時要特別當心,不要把不可信的數據做爲 HTML 插到頁面上,儘可能使用 .innerText.textContent.setAttribute() 等。

DOM 型 XSS 的攻擊步驟:

  1. 攻擊者構造出特殊數據,其中包含惡意代碼。
  2. 用戶瀏覽器執行了惡意代碼。
  3. 惡意代碼竊取用戶數據併發送到攻擊者的網站,或者冒充用戶的行爲,調用目標網站接口執行攻擊者指定的操做。

如何防範 DOM 型 XSS 攻擊

防範 DOM 型 XSS 攻擊的核心就是對輸入內容進行轉義(DOM 中的內聯事件監聽器和連接跳轉都能把字符串做爲代碼運行,須要對其內容進行檢查)。

1.對於url連接(例如圖片的src屬性),那麼直接使用 encodeURIComponent 來轉義。

2.非url,咱們能夠這樣進行編碼:

function encodeHtml(str) {
    return str.replace(/"/g, '"')
            .replace(/'/g, ''')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;');
}
複製代碼

DOM 型 XSS 攻擊中,取出和執行惡意代碼由瀏覽器端完成,屬於前端 JavaScript 自身的安全漏洞。

1.3 存儲型XSS

惡意腳本永久存儲在目標服務器上。當瀏覽器請求數據時,腳本從服務器傳回並執行,影響範圍比反射型和DOM型XSS更大。存儲型XSS攻擊的緣由仍然是沒有作好數據過濾:前端提交數據至服務端時,沒有作好過濾;服務端在接受到數據時,在存儲以前,沒有作過濾;前端從服務端請求到數據,沒有過濾輸出。

存儲型 XSS 的攻擊步驟:

  1. 攻擊者將惡意代碼提交到目標網站的數據庫中。
  2. 用戶打開目標網站時,網站服務端將惡意代碼從數據庫取出,拼接在 HTML 中返回給瀏覽器。
  3. 用戶瀏覽器接收到響應後解析執行,混在其中的惡意代碼也被執行。
  4. 惡意代碼竊取用戶數據併發送到攻擊者的網站,或者冒充用戶的行爲,調用目標網站接口執行攻擊者指定的操做。

這種攻擊常見於帶有用戶保存數據的網站功能,如論壇發帖、商品評論、用戶私信等。

如何防範存儲型XSS攻擊:

  1. 前端數據傳遞給服務器以前,先轉義/過濾(防範不了抓包修改數據的狀況)
  2. 服務器接收到數據,在存儲到數據庫以前,進行轉義/過濾
  3. 前端接收到服務器傳遞過來的數據,在展現到頁面前,先進行轉義/過濾

除了謹慎的轉義,咱們還須要其餘一些手段來防範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 的防範中能夠起到如下的做用:

  1. 禁止加載外域代碼,防止複雜的攻擊邏輯。
  2. 禁止外域提交,網站被攻擊後,用戶的數據不會泄露到外域。
  3. 禁止內聯腳本執行(規則較嚴格,目前發現 GitHub 使用)。
  4. 禁止未受權的腳本執行(新特性,Google Map 移動版在使用)。
  5. 合理使用上報能夠及時發現 XSS,利於儘快修復問題。

2.輸入內容長度控制

對於不受信任的輸入,都應該限定一個合理的長度。雖然沒法徹底防止 XSS 發生,但能夠增長 XSS 攻擊的難度。

3.輸入內容限制

對於部分輸入,能夠限定不能包含特殊字符或者僅能輸入數字等。

4.其餘安全措施

  • HTTP-only Cookie: 禁止 JavaScript 讀取某些敏感 Cookie,攻擊者完成 XSS 注入後也沒法竊取此 Cookie。
  • 驗證碼:防止腳本冒充用戶提交危險操做。

點擊查看更多

12.如何隱藏頁面中的某個元素?

隱藏類型

屏幕並非惟一的輸出機制,好比說屏幕上看不見的元素(隱藏的元素),其中一些依然可以被讀屏軟件閱讀出來(由於讀屏軟件依賴於可訪問性樹來闡述)。爲了消除它們之間的歧義,咱們將其歸爲三大類:

  • 徹底隱藏:元素從渲染樹中消失,不佔據空間。
  • 視覺上的隱藏:屏幕中不可見,佔據空間。
  • 語義上的隱藏:讀屏軟件不可讀,但正常佔據空。

徹底隱藏

1.display 屬性(不佔據空間)
display: none;
複製代碼
2.hidden 屬性 (不佔據空間)

HTML5 新增屬性,至關於 display: none

<div hidden>
</div>
複製代碼

視覺上的隱藏

1.利用 position 和 盒模型 將元素移出可視區範圍
  1. 設置 posoitionabsolutefixed,經過設置 topleft 等值,將其移出可視區域。(可視區域不佔位)
position:absolute;
left: -99999px;
複製代碼
  1. 設置 positionrelative,經過設置 topleft 等值,將其移出可視區域。(可視區域佔位)
position: relative;
left: -99999px;
複製代碼

如但願其在可視區域不佔位置,需同時設置 height: 0;

  1. 設置 margin 值,將其移出可視區域範圍(可視區域佔位)。
margin-left: -99999px;
複製代碼

若是但願其在可視區域不佔位,需同時設置 height: 0;

2.利用 transfrom
  1. 縮放(佔據空間)
transform: scale(0);
複製代碼

若是但願不佔據空間,需同時設置 height: 0

  1. 移動 translateX, translateY(佔據空間)
transform: translateX(-99999px);
複製代碼

若是但願不佔據空間,需同時設置 height: 0

  1. 旋轉 rotate (佔據空間)
transform: rotateY(90deg);
複製代碼
3.設置其大小爲0

寬高爲0,字體大小爲0:

height: 0;
width: 0;
font-size: 0;
複製代碼

寬高爲0,超出隱藏:

height: 0;
width: 0;
overflow: hidden;
複製代碼
4.設置透明度爲0 (佔據空間)
opacity: 0;
複製代碼
5.visibility屬性 (佔據空間)
visibility: hidden
複製代碼
6.層級覆蓋,z-index 屬性 (佔據空間)
position: relative;
z-index: -999;
複製代碼

再設置一個層級較高的元素覆蓋在此元素上。

7.clip-path 裁剪 (佔據空間)
clip-path: polygon(0 0, 0 0, 0 0, 0 0);
複製代碼

語義上的隱藏

aria-hidden 屬性 (佔據空間)

讀屏軟件不可讀,佔據空間,可見。

<div aria-hidden="true">
</div>
複製代碼

11. 使用JS將元素從頁面中移除

點擊查看更多

13.瀏覽器事件代理機制的原理是什麼?

事件流

在說瀏覽器事件代理機制原理以前,咱們首先了解一下事件流的概念,早期瀏覽器,IE採用的是事件捕獲事件流,而Netscape採用的則是事件冒泡。"DOM2級事件"把事件流分爲三個階段,捕獲階段、目標階段、冒泡階段。現代瀏覽器也都遵循此規範。

事件代理機制的原理

事件代理又稱爲事件委託,在祖先級 DOM 元素綁定一個事件,當觸發子孫級DOM元素的事件時,利用事件冒泡的原理來觸發綁定在祖先級 DOM 的事件。由於事件會從目標元素一層層冒泡至 document 對象。

爲何要事件代理?

  1. 添加到頁面上的事件數量會影響頁面的運行性能,若是添加的事件過多,會致使網頁的性能降低。採用事件代理的方式,能夠大大減小注冊事件的個數。

  2. 事件代理的當時,某個子孫元素是動態增長的,不須要再次對其進行事件綁定。

  3. 不用擔憂某個註冊了事件的DOM元素被移除後,可能沒法回收其事件處理程序,咱們只要把事件處理程序委託給更高層級的元素,就能夠避免此問題。

  4. 容許給一個事件註冊多個監聽。

  5. 提供了一種更精細的手段控制 listener 的觸發階段(能夠選擇捕獲或者是冒泡)。

  6. 對任何 DOM 元素都是有效的,而不只僅是對 HTML 元素有效。

addEventListener

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,用於移除事件監聽。

點擊查看更多

14. setTimeout 倒計時爲何會出現偏差?

setTimeout 只能保證延時或間隔不小於設定的時間。由於它實際上只是將回調添加到了宏任務隊列中,可是若是主線程上有任務尚未執行完成,它必需要等待。

若是你對前面這句話不是很是理解,那麼有必要了解一下 JS的運行機制。

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 事件進行處理。

點擊查看更多

參考文章:

[1] MDN addEventListener

[2] www.ecma-international.org/ecma-262/6.…

[3] www.xuanfengge.com/js-realizes…

謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。github.com/YvetteLau/B…

關注公衆號,加入技術交流羣

相關文章
相關標籤/搜索