前端筆記(三) 前端如何防範XSS,淺談JS中各種寬高屬性

NO IMAGE

前端如何防範XSS

XSS ( Cross Site Scripting )

跨站指令碼攻擊, 是一種攻擊者向使用者的瀏覽器注入惡意程式碼指令碼的攻擊。

XSS攻擊的種類:

  • 持續型XSS攻擊

攻擊者輸入惡意程式碼並通過評論或表單提交等方式將其提交到網站, 而網站的後端卻對使用者的資料不做任何處理進而直接存入資料庫, 當其他使用者訪問該網站並且需要請求網站的評論資料時網站後端直接從資料庫中取出帶有惡意程式碼的資料返回給使用者頁面此時使用者就會遭受到攻擊, 比如while { alert( ‘Attack!’ ) } 這樣的惡意指令碼。

  • 反射型XSS攻擊

使用者向網站傳送get請求某個網頁的url, 但使用者點開的是帶攻擊的url類似於:

/* http://xxx?name=<script>alert('233')</script>   <-- evil url
ctx.render('index.html', { name:name });
--index.html--
<html>
<div>${name}</div>  
</html>
*/
  • 基於DOM的XSS攻擊

它是反射型攻擊的一種, 此時伺服器返回的頁面是正常的, 只是頁面在執行JS時會將惡意指令碼一併執行。

/*
http://xxx?name=<script>alert('233')</script>     <-- evil url
--index.js-- 
var name = getparm('name');
document.querySelector('div').innerHTML = name;
*/

防止XSS注入的一些方法:

  • 對使用者輸入進行轉義
    function htmlEncode(html) {
var sub = document.createElement('div');
sub.textContent != null ? sub.textContent = html : sub.innerText = html;
var output = sub.innerHTML;
sub = null;
return output;
}
//既然有轉義當然也有反轉義的方法
function htmlDecode(text) {
var sub = document.createElement('div');
sub.innerHTML = text;
var output = sub.textContent || sub.innerText;
sub = null;
return output;
}
  • 對資料進行編碼
    /*
@ escape()
@ encodeURI()
@ encodeURIComponent()
-- 對應的解碼方法 --
@ unescape()
@ unencodeURI()
@ unencodeURIComponent()
*/
  • CSP( Content Security Policy )

CSP的實質就是白名單制度, 開發者明確告訴客戶端, 哪些外部資源可以載入和執行, 等同於提供白名單。它的實現和執行全部由瀏覽器完成, 開發者只需提供配置, CSP大大增強了網頁的安全性。

有兩種方法可以啟用CSP, 一種是通過設定HTTP頭資訊的Content-Security-Policy 欄位, 另外一種是通過網頁中的<meta> 標籤。

<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">

CSP通過設定限制選項來提供我們所需的白名單:

/*
@ script-src: 外部指令碼
@ style-src: 樣式表
@ img-src: 影象
@ media-src: 媒體檔案 ( 音訊和視訊 )
@ font-src: 字型檔案
@ object-src: 外掛 ( 比如Flash )
@ child-src: 框架
@ connect-src: HTTP 連線 ( 通過 XHR、WebSockets、EventSource等 )
......
*/

default-src 用來設定上面每個選項的預設值:

Content-Security-Policy: default-src 'self'     // 限制所有的外部資源, 只能從當前域名載入

其他的一些限制:

/*
@ block-all-mixed-content:HTTPS 網頁不得載入 HTTP 資源(瀏覽器已經預設開啟)
@ upgrade-insecure-requests:自動將網頁上所有載入外部資源的 HTTP 連結換成 HTTPS 協議
@ sandbox:瀏覽器行為的限制,比如不能有彈出視窗等
*/

使用report-uri 還可以記錄XSS注入的行為並報告給指定的url:

Content-Security-Policy: default-src 'self'; ...; report-uri https://retroastro.com;

設定多個值, 用空格分隔:

Content-Security-Policy: script-src 'self' https://host1.com https://host2.com

script-src 中的一些特殊值:

/*
@ nonce: 每次HTTP迴應給出一個授權token,頁面內嵌指令碼必須有這個token,才會執行。
@ hash: 列出允許執行的指令碼程式碼的Hash值,頁面內嵌指令碼的雜湊值只有吻合的情況下,才能執行。
*/
/* nonce值的例子:
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
<script nonce=EDNnf03nceIOfn39fn3e9h3sdfa>
// some code
</script>
設定的nonce值要與內嵌指令碼中的nonce相等才會執行程式碼
*/
/*  hash值的例子:
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
<script> //somecode </script>
伺服器給出一個允許執行程式碼片段的hash值, 對應的程式碼才會執行, 因為hash值相等。
*/

JavaScript的各種寬高屬性

網上看到有一篇部落格講的挺好挺全面的, 有需要的可以去看看:

戳這裡看JS中的各種寬高圖解

  • 自己寫的一個小demo

場景:實現bilibili右側導航欄的切換導航效果。


// HTML
<main>
<div class="container">
<div class="part">Live</div>
<div class="part">Comics</div>
<div class="part">Drama</div>
<div class="part">Native</div>
<div class="part">Music</div>
<div class="part">Dance</div>
<div class="part">Game</div>
<div class="part">Science</div>
<div class="part">Life</div>
<div class="part">Kichiku</div>
<div class="part">Advertise</div>
<div class="part">Entertain</div>
<div class="part">Fashion</div>
<div class="part">TVshows</div>
<div class="part">Film</div>
<div class="part">Movies</div>
</div>
</main>
<aside class="elevator-module">
<div class="nav-list">
<div class="item active" data-id=0>直播</div>
<div class="item" data-id=1>動畫</div>
<div class="item" data-id=2>番劇</div>
<div class="item" data-id="3">國創</div>
<div class="item" data-id="4">音樂</div>
<div class="item" data-id="5">舞蹈</div>
<div class="item" data-id="6">遊戲</div>
<div class="item" data-id="7">科技</div>
<div class="item" data-id="8">生活</div>
<div class="item" data-id="9">鬼畜</div>
<div class="item" data-id="10">廣告</div>
<div class="item" data-id="11">娛樂</div>
<div class="item" data-id="12">時尚</div>
<div class="item" data-id="13">TV劇</div>
<div class="item" data-id="14">影視</div>
<div class="item" data-id="15">電影</div>
</div>
<div class="s-line"></div>
<div class="back-top">
<div class="circle"></div>
</div>
</aside>
    // CSS
.elevator-module{
position:fixed;
top:20px;
left:50%;
margin-left:590px;
transition:all .4s ease;
z-index:10;
}
.elevator-module .nav-list{
position:relative;
background-color:#f6f9fa;
border:1px solid #e5e9ef;
overflow:hidden;
border-radius:4px;
}
.elevator-module .nav-list .item{
width:48px;
height:32px;
line-height:32px;
text-align:center;
transition:all .3s;
cursor:pointer;
}
.elevator-module .nav-list .item.on, .elevator-module .nav-list .item:hover{
background-color:#00a1d6;
color:#fff;
}
.nav-list .item.active{
color:#fff;
background:#00a1d6;
}
.elevator-module .s-line{
position:relative;
border-left:1px solid #ddd;
border-right:1px solid #ddd;
height:9px;
width:30px;
margin:0 auto;
}
.elevator-module .back-top:hover{
background-color:#00a1d6;
border-color:#00a1d6;
}
.elevator-module .back-top{
position:relative;
display:block;
cursor:pointer;
height:50px;
background-color:#f6f9fa;
overflow:hidden;
border: 1px solid #e5e9ef;
border-radius:4px;
text-align: center;
}
.circle{
display:inline-block;
width:30px;
height:30px;
margin-top:10px;
border-radius:50%;
background:#ffafc9;
}
.part{
width:800px;
height:600px;
margin:20px auto;
background:#00a1d6;
text-align:center;
line-height:600px;
font-size:13em;
font-family:Comic Sans MS;
color:#fff;
font-weight:700;
border-radius:4px;
}
.container{
width:1160px;
margin:0 auto;
position:relative;
}
    // JavaScript
var ElevatorModule = {
init() {
this.scrollEvent();
},
scrollEvent() {
var elevator = document.querySelector('.elevator-module');
var navList = document.querySelector('.nav-list');
var items = document.querySelectorAll('.nav-list .item');
var modules = document.querySelectorAll('.part');
var backtop = document.querySelector('.back-top');
window.addEventListener('scroll', slipper);
navList.addEventListener('click', moveTo);
backtop.addEventListener('click', fly);
function slipper() {
for ( var item of items ) {
var rect = item.getBoundingClientRect();
var scrollTop = document.documentElement.scrollTop;
var item_top = rect.top   scrollTop; //獲取右側導航每個分割槽上方到頁面最頂部的距離
var fetch = getLocation();
var id = item.getAttribute('data-id');
if ( item_top >= fetch[id] - 100 ) {  //移到哪個分割槽右側的hover就停留在哪
items.forEach((el) => {
el.classList.remove('active'); 
})
item.classList.add('active');
}
}
}
var flag = true;
//定位到指定分割槽
function moveTo() {
var e = window.event;
items.forEach((item) => {
item.classList.remove('active');
})
if ( e.target && e.target.nodeName == 'DIV' ) {
var id = e.target.getAttribute('data-id');
e.target.classList.add('active');
var fetch = getLocation();
if ( flag ) {
AnimationFrame(fetch[id]);
id ? flag = false : flag = true; 
//設定節流效果, 即在運動的過程中不允許再次觸發AnimationFrame函式
}
}
}
//scrollTop過渡
function AnimationFrame(future_top) {
let current_top = document.documentElement.scrollTop;
var timer = setInterval( () => {
if ( current_top > future_top ) {
current_top -= 60;
document.documentElement.scrollTop = current_top;
} else if ( current_top < future_top ) {
current_top  = 60;
document.documentElement.scrollTop = current_top;
}
if ( current_top >= future_top - 30 && current_top <= future_top   30 ) { 
//當移動到指定的範圍內時直接定位到對應的位置
document.documentElement.scrollTop = future_top;
clearInterval(timer);
flag = true;
}
},10)
}
//獲取pageY
function getLocation() {
var arr = [];
modules.forEach((module) => {
var thing = pageY(module);
arr.push(thing);
})
return arr;
}
//獲取頁面中間每個分塊上方到頁面最頂部的距離
function pageY(el) {
if ( el.offsetParent ) {
return el.offsetTop   pageY(el.offsetParent);
} else {
return el.offsetTop;
}
}
//平滑返回頁面頂部
function fly() {
cancelAnimationFrame(timer);
var timer = requestAnimationFrame( function fn() {
var top = window.pageYOffset;
if ( top > 0 ) {
document.documentElement.scrollTop = top - 80;
timer = requestAnimationFrame(fn);
} else {
cancelAnimationFrame(timer);
}
})
}
}
}
ElevatorModule.init();

PS:

一些需要注意的常見js事件屬性:

  • event.stopPropagation()

終止事件在傳播過程的捕獲、目標處理或起泡階段進一步傳播。呼叫該方法後,該節點上處理該事件的處理程式將被呼叫,事件不再被分派到其他節點。

  • event.preventDefault()

該方法將通知 Web 瀏覽器不要執行與事件關聯的預設動作(如果存在這樣的動作)。

  • 不論滑鼠指標穿過被選元素或其子元素,都會觸發 mouseover 事件。對應mouseout
  • 只有在滑鼠指標穿過被選元素時,才會觸發 mouseenter 事件。對應mouseleave

參考文章:

https://www.cnblogs.com/caizh…  XSS

https://www.cnblogs.com/zzgbl…  轉義方法

http://www.ruanyifeng.com/blo…   CSP