Cover Credits to Oleg Frolov

What is DOM in JavaScript?

Document Object Model(DOM)文檔模型, 簡單來說就是HTML節點,包含了父子及兄弟之間層級元素關係,可以透過附加程式碼去創建、修改、刪除節點或進行事件處理。

DOM能做到什麼?

如上段圖示所見DOM產生樹狀結構,JS可以透過DOM去操控節點,控制CSS,增加互動效果。

以下先以目前學習到「獲取、增加節點」JS語法筆記。

JavaScript
//get elements form dom
document.getElmentbyId();//唯一元件
document.querySelector();//單個元件
document.querySelectorAll();//遍歷複數同層元素

//add elements
document.createElment('');//創建元素
el.textcontext=''//增加文字
el.appendChild('');//將建立的元素放在對應節點之下

el.innerHTML='<p>'+'HI'+'</p>';//將元素放到DOM

如何取得節點(Node)

document.getElmentbyID

Html使用具有唯一性質標籤”id”命名元素,在JS返回一個元素。

HTML
<h1 id="title"></h1>
JavaScript
const el =document.getElementById('title');
console.log(el);
//Output:<h1 id="title"></h1>

document.getElementsByClassName

可取得單或複數同類名元素。

HTML
<div class="block"></div>
<div class="block"></div>
JavaScript
let el=document.getElementsByClassName('block');
el.length(el)
//Output:2

document.querySelector

取單個元素,若有複數同命名只會取第一個值。

HTML
<ul>
<li class="name">Dorami</li>
<li class="name">Doraemon</li>
</ul>
JavaScript
const list=document.querySelector('.name')
console.log(list);
//Output:Dorami

document.querySelectorAll

可選取重複類名相同元素,解析網頁時會由上至下遍歷DOM取出「全部相同」節點以類陣列方式回傳。

HTML
<ul>
<li class="name"></li>
<li class="name"></li>
</ul>
JavaScript
const list=document.querySelectorAll('.name');
console.log(list.length);
//Output: 2

用JS改變結構

可以透過JS兩種方式動態新增節點,分別是createElement, innerHTML,不過前者會寫比較多行程式碼,後者將字串組合後一次帶入即可,我們請大雄跟家人簡介做示範。

CreateElement

JS中使用CreateElement增加節點,將該元素節點透過appendChild加入DOM指定節點之下。

HTML
<ul class="family"></ul>
JavaScript
//createElement

let getel=document.querySelector('.family')
const family=[
{ name:'NOBITA',
  food:'pancake',
img:'https://chinesedora.com/character/nobita_mes.gif'
},
{ name:'DoRAEMON',
  food:'Dorayaki',
img:'https://chinesedora.com/character/doraemon12.png'
},
{ name:"NOBITA's Dad",
	food:'Dorayaki',
  img:'https://chinesedora.com/images/nobifa.jpg'
},
{ name:"NOBITA's Mom",
	food:'Dorayaki',
img:'https://chinesedora.com/images/nobima.jpg'
}
]

function addEl(){
 for (let i=0;i<family.length;i++){
//name
let addName=document.createElement('li');
addName.textContent=family[i].name;
getel.appendChild(addName);
   
//food
let addFood=document.createElement('p');
addFood.textContent='我喜歡吃'+family[i].food;
addName.appendChild(addFood);
   
//img
let addImg=document.createElement('img')
addImg.src=family[i].img;
addName.appendChild(addImg);
}
}
  
addEl();

innerHTML

可以透過DOM屬性處理已存在HTML節點,若個別帶入資料,得再迴圈之外放空字串,將每筆資料放入宣告空字串顯示結果。

HTML
<ul class="family"></ul>
JavaScript
//InnerHTML

let getel=document.querySelector('.family')
const family=[
{ name:'NOBITA',
  food:'pancake',
img:'https://chinesedora.com/character/nobita_mes.gif'
},
{ name:'DoRAEMON',
  food:'Dorayaki',
img:'https://chinesedora.com/character/doraemon12.png'
},
{ name:"NOBITA's Dad",
	food:'Dorayaki',
  img:'https://chinesedora.com/images/nobifa.jpg'
},
{ name:"NOBITA's Mom",
	food:'Dorayaki',
img:'https://chinesedora.com/images/nobima.jpg'
}
]

function addEl(){
let str=''
 for (let i=0;i<family.length;i++){
let addInfo=` 
    <li>
        <p>${family[i].name}</p>
        <p>我喜歡吃${family[i].food}</p>
        <img src="${family[i].img}">
      </li>
    `;
str+=addInfo;
getel.innerHTML=str;
}
}



addEl();

可以看到函式內使用兩者處理方式不同,後者雖然程式碼行數較少,但innerHTML在運用上要小心XSS問題,下方淺談何為XSS。

預防潛在 XSS(Cross-Site Scripting)攻擊

跨站腳本攻擊(Cross-Site Scripting, XSS),Attacker透過網站漏洞結合用戶操作方式,如:輸入字串欄位、URL、Cookie 埋入惡意腳本至目標網站,之後訪問的用戶進行瀏覽便成為攻擊對象。

XSS三種攻擊方式[1] :Reflected, DOM-based XSS, Stored XSS,稍為解釋在不同場景會發生的攻擊情境。

反射型攻擊(Reflected)

Attacker透過輸入欄位寫入惡意腳本。當送出請求到伺服器後會運行程式碼,伺服器返回給用戶畫面會包含剛才輸入腳本後所顯示的結果,之後有其他用戶訪問帶有這個攻擊腳本的特殊請求時,伺服器會將腳本反射回用戶的瀏覽器執行此帶有惡意腳本的畫面。

儲存型攻擊(Stored XSS)

用戶在訂閱網站服務填寫個人資料,如信用卡號等…,Attacker欲提取資料或攻擊網站,可以在欄位中埋下JS腳本存進網站資料庫,往後用戶在這頁提交敏感資料,便可能有盜取資料風險。

文檔物件模型攻擊(DOM-based XSS)

與反射型雷同,不同之處在於DOM-based XSS是在用戶端瀏覽器前端執行,而不是在伺服器。透過Javascript 漏洞將惡意腳本嵌入DOM節點之中,當用戶輸入欄位操作,執行Attacker的腳本,進行竊取信息等..攻擊行為。

innerHTML和createElement 跟XSS有什麼關聯?

這裡用儲存型攻擊(Stored XSS)解釋,透過CreateElement建立的input,我們將用戶輸入的參數轉成文本節點,所以在欄位輸入<script></script>提交內容送到資料庫,會被當作純文字(plaintext)處理,不過轉為String,不代表安全,Attacker還是可以透過其他手法,繞過一些簡單的防護機制攻擊。

因此,需要額外安全機制像是:內容安全政策[2](Content Security Policy, CSP),允許網站擁有者制定一組規則,指定哪些來源的資源可以被載入到網頁中,在用戶送出時請瀏覽器在幫忙檢查一次,若有不符允許行為,可以阻擋這些資源的載入。

JavaScript
const getEl = document.querySelector('.form');
const addInput = document.createElement('input');

//用戶在欄位輸入內容
const userType="<script>'alert('恩我在攻擊你')';</script>";
//轉為純文字
const textNode=document.createTextNode(userType);

addInput.appendChild(textNode);
getEl.appendChild(addInput);

而相同情境以innerHTML寫法,會被html視為程式碼執行Attacker腳本。

JavaScript
const getEl = document.querySelector('.form');
const addInput = document.createElement('input');

// 假設用戶在欄位輸入內容
const userType = "<script>alert('恩我在攻擊你');</script>";

// innerHTML 將用戶輸入的內容設置到 input 元素中
addInput.innerHTML = userType;

getEl.appendChild(addInput);

不過,上述並非指createElement vs. innerHTML 選擇前者安全性較高,以筆者目前搜集資料來看,還是得取決於情境使用,例如你無法掌控用戶會提供網站什麼資料,像是輸入欄位建議用createElement,降低XSS攻擊。

而此文中提及CreateElement增加節點,僅是其中一項基本預防措施,目前看到有關XSS技術文章,大多建議網站做驗證或過濾字串等…增加安全機制寫法,減少被攻擊的可能性。

上述以是初學XSS理解範圍做筆記,若往後對XSS有更近一步學習再做整理

此為紀錄學習過程,如內容有誤,歡迎留言指教。
偶爾會畫圖來理解程式碼,若內文圖像或流程圖對你理解有幫助,歡迎自行取用。

參考:

[ 1 ]零基礎資安系列(二)-認識 XSS(Cross-Site Scripting)

[ 2 ]CSP:你可能不知道的內容安全策略(Content-Security-Policy, CSP)