淺談 XSS (Cross-Site Scripting) 攻擊
何謂 XSS (Cross-Site Scripting) 攻擊?
XSS,全稱為 Cross-Site Scripting(跨網站指令碼),是一種攻擊者透過植入的惡意腳本來攻擊使用者瀏覽器的攻擊手法。攻擊者會藉由各種手段將惡意腳本注入網頁,當受害者訪問這些網頁時,惡意腳本就會在使用者的瀏覽器中被執行。
由於這種 XSS 攻擊的惡意程式碼試運行在受害者的瀏覽器環境上,因此,用更專業的詞彙來說,Cross-site scripting 是一種用戶端程式碼插入攻擊。
XSS攻擊之所以被稱為 "跨站腳本" 攻擊,其核心在於它巧妙地利用了網站對用戶輸入的信任機制,從而繞過瀏覽 器的同源政策。這種攻擊的獨特之處在於:
- 信任的濫用:雖然惡意腳本可能源自外部,但因為它是通過受信任的用戶 "代理" 提交的,所以在網站看來,這個腳本仍然是來自 "可信來源"。
- 繞過防禦:這種方式巧妙地規避了同源政策的限制。因為從網站的角度來看,這個腳本是在它自己的域內執行的,而非來自外部網站。
XSS攻擊可能造成的損害
因為在 Web 的世界中 JavaScript 幾乎可以做到任何事,所以 XSS 的攻擊能做到的事情也是五花八門。舉例來說:
- 竊取使用者資料:攻擊者可以獲取受害者的 cookie、session tokens,進一步竊取帳號控制權。
- 偽造使用者行為:攻擊者可以在使用者的瀏覽器中執行惡意行為,例如偽造表單提交、假冒使用者發表留言或按讚。
- 植入惡意程式碼:攻擊者甚至可以在網站中植入更多的惡意程式碼,擴大攻擊影響。
- 執行社交工程攻擊:攻擊者可以利用被感染的網頁顯示虛假的登錄頁面或其他欺騙性內容,誘導使用者提供更多敏感信息。
XSS 的類性
XSS 攻擊主要可以分為三種類型:反射型 XSS、儲存型 XSS 以及 DOM 型 XSS。每種類型的攻擊都有其特點和方式,以下我們會詳細解釋攻擊者如何注入惡意腳本以及這些腳本是如何被執行的。
反射型 XSS(Reflected XSS)
反射型 XSS 是最常見的攻擊方式。反射型 XSS 通常出現在需要使用者輸入表單資料來動態生成回應的情況,例如搜尋框或查詢參數。攻擊者會將惡意腳本包含在請求的 URL 中,當受害者點擊該 URL,伺服器會把攻擊者的輸入直接反射到回應中,導致瀏覽器執行惡意程式碼。
注入方法與執行機制
攻擊者通常會構造一個 URL,將惡意指令碼作為查詢參數。例如:
http://example.com/search?query=<script>alert('You have been hacked!')</script>
在這裡,攻擊者將惡意的 JavaScript 程式碼作為 query 參數提交,目的是讓伺服器將這段指令碼嵌入到回應的 HTML 中,並在受害者的瀏覽器中執行。因為此手法需透過特定網址點入,因此攻擊者通常會以釣魚手法、社交工程等方式誘騙受害者點入連結。
範例
假設伺服器端程式碼未對使用者輸入進行適當處理,就可能會出現以下情況:
const http = require('http');
const url = require('url');
http.createServer((req, res) => {
const queryObject = url.parse(req.url, true).query;
res.writeHead(200, { 'Content-Type': 'text/html' });
// 直接將用戶輸入寫入回應中
res.end(`<html><body>Search result: ${queryObject.query}</body></html>`);
}).listen(8080);
在上述範例中,使用者輸入的內容會被直接插入 HTML 中,導致惡意指令碼在受害者的瀏覽器中執行。當受害者訪問攻擊者給 出的 URL 時,瀏覽器會直接顯示彈窗,顯示「You have been hacked!」。
儲存型 XSS(Stored XSS)
儲存型 XSS 是當攻擊者將惡意指令碼儲存在伺服器上,使其他使用者在訪問相關內容時執行這段指令碼。這類 XSS 攻擊通常發生在具備留言板、評論功能或其他可以持久儲存使用者輸入的網站。
注入方法與執行機制
攻擊者可以透過提交惡意內容至伺服器,例如在評論欄位中輸入以下內容:
<script>alert('Your account is compromised!')</script>
如果伺服器沒有對這些內容進行適當的過濾與處理,這段指令碼會被儲存在伺服器的資料庫中,並在其他使用者瀏覽該評論時被執行。
範例
伺服器端將使用者的輸入儲存到資料庫中,並且在顯示時直接將其插入 HTML 中:
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
let comments = []; // 假設這裡是我們的簡單儲存空間
app.post('/comment', (req, res) => {
comments.push(req.body.comment); // 將使用者的評論儲存起來
res.send('Comment added successfully!');
});
app.get('/comments', (req, res) => {
let commentsHtml = comments.map(comment => `<div>${comment}</div>`).join('');
res.send(`<html><body>${commentsHtml}</body></html>`);
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});