サニタイズとは?Vue3でのリッチテキストの安全な取り扱い方法

ウェブセキュリティの基石、サニタイズの重要性を解説。Vue3でのリッチテキストのサニタイズ方法とその実装例を紹介します。安全なウェブアプリケーション開発のための必読ガイド。

2023年10月01日

前提

  • リッチテキストを扱うVue3プロジェクトが存在する

サニタイズとは?

サニタイズは、ユーザーからの入力データを安全に扱うためのプロセスです。具体的には、悪意のあるコードやスクリプトを含む可能性のある入力データをクリーンな形式に変換することを指します。サニタイズを行うことで、XSS攻撃などのセキュリティリスクを大幅に軽減することができます。

XSS攻撃の防止

サニタイズを行わないと、悪意のあるユーザーがスクリプトを埋め込むことができ、そのスクリプトが他のユーザーのブラウザで実行される可能性があります。これにより、ユーザーのデータが盗まれる、ウェブサイトが改ざんされるなどのリスクが生じます。

法的・規制上のリスクの軽減

ユーザーのデータを適切に保護しない場合、データ保護法やプライバシー法に違反する可能性があります。サニタイズを行うことで、これらの法的リスクを軽減することができます。

信頼性の確保

サニタイズを適切に行うことで、ウェブサイトやアプリケーションの信頼性を維持・向上させることができます。ユーザーは、安全に情報を共有・閲覧できるプラットフォームを好む傾向があります。

XSS(クロスサイトスクリプティング)攻撃

XSS攻撃は、ウェブアプリケーションの脆弱性を悪用して、悪意のあるスクリプトを他のユーザーのブラウザ上で実行させる攻撃手法です。攻撃者は、ウェブページにスクリプトを埋め込むことで、他のユーザーの情報を盗んだり、ユーザーに偽の操作をさせたりすることができてしまいます。

Vue3で懸念されている最たるものが v-html です。
静的サイトでの利用や、管理者のみが扱う場合には(自分は)スルーしてますが、v-htmlが非推奨とされている通り、扱いには十分な配慮が必要です。

現在の実装

以前簡単に書いたコードです。
https://github.com/yutahhh/firebase-study/tree/feature/%238

<template>
  <div>
    <v-container>
      <v-row>
        <v-col
          cols="12"
          md="8"
        >
          <v-card
            class="color-pallet h-100"
          >
            <v-card-text>
              <div v-html="richText" />
            </v-card-text>
            <v-card-actions>
              <v-btn
                color="#08ffc8"
                @click="switchColor='cool'"
              >
                クール系
              </v-btn>
              <v-btn
                color="#ffb6b9"
                @click="switchColor='cute'"
              >
                かわいい系
              </v-btn>
            </v-card-actions>
          </v-card>
        </v-col>
        <v-col
          cols="4"
          class="d-none d-md-block"
        >
          <v-card
            title="ペットの冒険"
            subtitle="ここから下の画像が追従するよ"
            class="mb-8"
          />
          <v-card class="sidebar-sticky">
            <v-img src="https://placekitten.com/300/300" />
          </v-card>
        </v-col>
      </v-row>
    </v-container>
  </div>
</template>

<script setup lang="ts">
# 省略
const richText = `
  <h1>ペットの冒険</h1>
  <h2>うちのペット</h2>
  <p>こんにちは、みんな!私の家にはかわいいペットがいるんだ。名前はミケとポチ。ミケは黒猫で、ポチは元気な犬だよ。一緒に遊ぶのが大好きで、家族みんなで楽しい時間を過ごしているんだ。</p>
  <h2>冒険の始まり</h2>
  <p>ある日、ミケとポチは一緒に冒険に出かけることになったんだ。ミケは猫なのに、なんだか冒険者みたいな気分になるんだよ。</p>
  <h3>森への旅</h3>
  <p>まずは森へ行ってみることにしたよ。木々の間をくぐり抜けながら歩くと、鳥のさえずりが聞こえてきてとっても心地よかった。ポチはワンワンと元気に駆け回って、私たちを楽しませてくれたんだ。</p>
  <h3 class="highlight">川での出来事</h3>
  <p>森を抜けると、きれいな川が広がっていたんだ。ミケは初めての水辺に興味津々で近づいていったよ。すると、ミケがジャンプして川に飛び込んでしまったんだ!でも大丈夫、私たちはすぐに助けてあげたんだ。ミケはびしょ濡れになっちゃったけれど、みんなで笑っていたよ。</p>
  <h3>大冒険の終わり</h3>
  <p>冒険は楽しい思い出と共に終わったんだけれど、ミケとポチはいつも一緒にいるから、これからもたくさんの冒険が待っていると思うんだ。ペットとの冒険は最高だね!</p>
  <p>それではまたね!</p>
`
</script>

<style lang="sass">
# 省略
</style>

ここにサニタイズ対応を挟んで改修します。

改修

sanitize-htmlを使用して、でリッチテキストをサニタイズしていきます。

1. まず、sanitize-htmlをインストールします。(yarn)

$ yarn add sanitize-html
# typescriptの場合は以下も入れます
$ yarn add @types/sanitize-html -D

2. 次に、コンポーネントの<script>セクションにsanitize-htmlをインポートし、リッチテキストのサニタイズを行います。

<script setup lang="ts">
import sanitizeHtml from 'sanitize-html';

// ... その他のコード ...

// サニタイズ設定
const sanitizeOptions = {
  allowedTags: ['h1', 'h2', 'h3', 'p', 'img'],
  allowedAttributes: {
    'img': ['src', 'alt']  // デフォルトでもonerrorなどのイベントハンドラは許可していないが、明示的に。
  }
}

// リッチテキストのサニタイズ
const xssText = `<p>攻撃を仕掛けるテキスト</p><img src="存在しない画像" onerror="alert('XSS攻撃!')" />` 
</script>

3. 最後に、<template>セクションのv-htmlディレクティブを更新して、サニタイズされたテキストを使用します。

<div v-html="sanitizeHtml(xssText, sanitizeOptions)" />

共有

こちらに動作環境があります。
https://study.theblueback.com/
※ 既存の実装と合わせるため、ちょっと変えてます。ハリボテですが「XSS攻撃」ボタンで検証できるようにしています。

【今回の作業ブランチ】
https://github.com/yutahhh/firebase-study/tree/feature/%2311

お疲れ様でした。

筆者情報
IT業界経験6年目のフルスタックエンジニア。
フロントエンドを軸として技術を研鑽中でございます。