はじめに
Web アプリケーション開発において、ユーザーからの入力を安全に処理することは最も基本的かつ重要なセキュリティ対策です。
この記事では、サニタイズの一般的な知識を解説した上で、Rails や React など主要なフレームワークでの実践方法を紹介します。
サニタイズとは
サニタイズ(Sanitize) とは、外部から入力されるデータに含まれる不正なコードや危険な文字列を検知し、無害化する処理のことです。日本語では「無害化」「消毒」とも訳されます。
サニタイズを行わないと、攻撃者がフォームや URL パラメータなどを通じて悪意のあるコードを注入し、XSS(クロスサイトスクリプティング)や SQL インジェクションといった攻撃が成立してしまいます。
サニタイズ・エスケープ・バリデーションの違い
これらは混同されやすいですが、それぞれ異なる役割を持っています。
| 用語 | 目的 | 具体例 |
|---|---|---|
| サニタイズ | 危険なデータを無害化する(総合的な概念) |
<script> タグの除去、SQL の特殊文字の無効化 |
| エスケープ | 特殊文字を意味を持たない文字列に変換する |
< → <、' → \'
|
| バリデーション | 入力値が期待する形式かを検証する | メールアドレスの形式チェック、数値の範囲チェック |
エスケープはサニタイズの一手法です。サニタイズは「危険なものを除去・変換する」という広い概念であり、エスケープはその中の「特殊文字を別の文字に置き換える」という具体的な手法を指します。
バリデーションはサニタイズとは別の工程で、「そもそも不正な入力を受け付けない」という水際対策です。
サニタイズが防ぐ攻撃
XSS(クロスサイトスクリプティング)
悪意のあるスクリプトを Web ページに埋め込み、他のユーザーのブラウザ上で実行させる攻撃です。
<!-- ユーザーが入力した値がそのまま出力されると... -->
<p>こんにちは、<script>alert('XSS')</script>さん</p>
スクリプトが実行されると、Cookie の窃取、セッションハイジャック、フィッシングなどの被害につながります。
対策: 出力時に HTML エスケープを行い、<script> が <script> として表示されるようにします。
SQL インジェクション
ユーザー入力を SQL クエリに直接埋め込むことで、データベースを不正に操作する攻撃です。
-- ユーザーが入力した値をそのまま埋め込むと...
SELECT * FROM users WHERE name = '' OR '1'='1' --'
-- すべてのユーザー情報が漏洩する
対策: プリペアドステートメント(パラメータ化クエリ)を使い、入力値がSQL文の構造を変えられないようにします。
その他の攻撃
- コマンドインジェクション: OS コマンドに不正な入力を注入
- LDAP インジェクション: LDAP クエリに不正な入力を注入
-
パストラバーサル: ファイルパスに
../を注入して権限外のファイルにアクセス
いずれも「外部入力をそのまま信頼してはならない」という共通の原則に基づいています。
HTML エスケープの基本
XSS 対策の最も基本的な手法が HTML エスケープです。以下の特殊文字を安全な文字参照に変換します。
| 変換前 | 変換後 | 理由 |
|---|---|---|
< |
< |
タグの開始と解釈されるのを防ぐ |
> |
> |
タグの終了と解釈されるのを防ぐ |
& |
& |
文字参照の開始と解釈されるのを防ぐ |
" |
" |
属性値の区切りと解釈されるのを防ぐ |
' |
' |
属性値の区切りと解釈されるのを防ぐ |
これにより、<script>alert('XSS')</script> はブラウザ上では単なるテキストとして表示され、スクリプトとしては実行されません。
出力コンテキストごとのエスケープ
重要なのは、エスケープは出力するコンテキストによって方法が異なるという点です。
| コンテキスト | エスケープ方法 | 例 |
|---|---|---|
| HTML 本文 | HTML エスケープ |
< → <
|
| HTML 属性値 | HTML エスケープ + 引用符で囲む | href="..." |
| JavaScript | JavaScript エスケープ |
' → \'
|
| URL パラメータ | URL エンコード |
< → %3C
|
| SQL | プリペアドステートメント |
? プレースホルダ |
| CSS | CSS エスケープ | \3C |
HTML エスケープだけでは、JavaScript の文脈に埋め込まれた値は防げません。出力する場所に応じた適切なエスケープが必要です。
Rails でのサニタイズ
Rails は デフォルトで自動エスケープ が有効になっており、XSS に対して強い防御を持っています。
自動エスケープ(デフォルト)
<%# 自動的に HTML エスケープされる(安全) %>
<%= @user.name %>
@user.name に <script>alert('XSS')</script> が含まれていても、自動的にエスケープされます。
html_safe の危険性
html_safe は自動エスケープを無効化するメソッドです。ユーザー入力に対して直接使ってはいけません。
# 危険: XSS 脆弱性
params[:comment].html_safe
# 安全: 先にエスケープしてから html_safe
h(params[:comment]).gsub(/\n/, "<br>").html_safe
# より安全: simple_format を使う
simple_format(@comment.body)
sanitize メソッド
ユーザー入力の HTML を許可しつつ、危険なタグだけを除去します。
<%# 許可タグのみ残し、script 等は除去 %>
<%= sanitize(@article.body, tags: %w[b i em strong a], attributes: %w[href]) %>
Rails のサニタイズ関連メソッド一覧
| メソッド | 動作 | 使いどころ |
|---|---|---|
<%= %> |
自動エスケープ | 通常の値の出力(デフォルトで安全) |
h / html_escape
|
手動エスケープ |
html_safe と組み合わせる場合 |
html_safe |
エスケープを無効化 | 自分で生成した安全な HTML のみ |
sanitize |
許可タグ以外を除去 | ユーザー入力の HTML を許可したい場合 |
strip_tags |
全タグを除去 | HTML を一切許可しない場合 |
simple_format |
改行を <br>/<p> に変換 |
テキストの改行を反映したい場合 |
SQL インジェクション対策
Rails の ActiveRecord はデフォルトでプリペアドステートメントを使用します。
# 安全: プレースホルダを使用
User.where("name = ?", params[:name])
User.where(name: params[:name])
# 危険: 文字列を直接埋め込み
User.where("name = '#{params[:name]}'") # SQL インジェクション脆弱性!
React でのサニタイズ
React は JSX のデフォルトで自動エスケープ が有効です。
自動エスケープ(デフォルト)
function Greeting({ name }) {
// name に <script> が含まれていても自動的にエスケープされる
return <p>こんにちは、{name}さん</p>;
}
React は JSX 内の {} で埋め込まれた値を自動的にエスケープするため、通常の使い方では XSS は発生しません。
dangerouslySetInnerHTML の危険性
dangerouslySetInnerHTML は React のエスケープを無効化し、HTML をそのまま埋め込みます。名前に "dangerous" と入っているとおり、慎重に使う必要があります。
// 危険: ユーザー入力をそのまま埋め込み
function Comment({ body }) {
return <div dangerouslySetInnerHTML={{ __html: body }} />;
}
DOMPurify によるサニタイズ
dangerouslySetInnerHTML を使う必要がある場合は、DOMPurify でサニタイズしてから埋め込みます。
npm install dompurify
import DOMPurify from 'dompurify';
function SafeHtmlContent({ html }) {
const cleanHtml = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href', 'target'],
});
return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
}
DOMPurify は <script> タグやイベントハンドラ(onclick など)を除去し、許可されたタグ・属性だけを残します。
React のサニタイズまとめ
| 方法 | 安全性 | 使いどころ |
|---|---|---|
JSX {}
|
自動エスケープ(安全) | 通常のテキスト表示 |
dangerouslySetInnerHTML |
危険(手動対策が必要) | リッチテキストの表示 |
DOMPurify + dangerouslySetInnerHTML
|
安全 | ユーザー入力の HTML を許可する場合 |
フレームワーク共通のパターン
Rails と React のサニタイズ対策を比較すると、共通のパターンが見えてきます。
| 観点 | Rails | React |
|---|---|---|
| デフォルトの自動エスケープ | <%= %> |
JSX {}
|
| エスケープを無効化する方法 |
html_safe / raw
|
dangerouslySetInnerHTML |
| HTML を許可するサニタイズ |
sanitize ヘルパー |
DOMPurify |
| 全タグの除去 | strip_tags |
DOMPurify(タグ許可なし) |
どのフレームワークでも原則は同じ です。
- デフォルトの自動エスケープを信頼する(最も基本的で効果的)
- エスケープを無効化する機能は、ユーザー入力に対して直接使わない
- HTML を許可する必要がある場合は、サニタイズライブラリを使う
多層防御の考え方
サニタイズはセキュリティ対策の一部であり、単一の対策に依存するのは危険です。複数のレイヤーで対策を行う 多層防御(Defense in Depth) が推奨されます。
[入力] → バリデーション → ビジネスロジック → サニタイズ/エスケープ → [出力]
↑ ↑
不正な形式を拒否 出力コンテキストに応じた無害化
- 入力バリデーション: 文字種・長さ・形式の制限(水際対策)
- 出力エスケープ: コンテキストに応じたエスケープ(最重要)
- プリペアドステートメント: SQL インジェクション対策
- Content Security Policy(CSP): ブラウザレベルでのスクリプト実行制御
- WAF(Web Application Firewall): ネットワークレベルでの不正リクエスト遮断
特に重要なのは、サニタイズは「入力時」ではなく「出力時」に行うという原則です。入力時にサニタイズしてしまうと、データの用途が変わったときに適切なエスケープができなくなります。出力するコンテキスト(HTML、SQL、JavaScript など)に応じて、その都度適切な方法でエスケープするのが安全です。
まとめ
サニタイズは Web セキュリティの基本中の基本です。
- サニタイズ = 危険なデータの無害化(エスケープはその一手法)
- エスケープ = 特殊文字を別の文字に変換して無害化
- バリデーション = 不正な入力を受け付けない
どのフレームワークでも、「デフォルトの自動エスケープを活用し、それを無効化する機能はユーザー入力に対して使わない」という原則は共通です。Rails の html_safe も React の dangerouslySetInnerHTML も、名前が示すとおり「意図的にリスクを取る」行為であり、使う場面では必ずサニタイズを併用してください。
参考記事・データ
- サニタイジングとは?行わない場合のリスクや実施方法 - LANSCOPE
- サニタイジングとは?概要から実施方法を解説 - ITトレンド
- インジェクション脆弱性への向き合い方 - Flatt Security
- SQL Injection Prevention - OWASP Cheat Sheet
- 【Rails】html_safeの脆弱性とその対策 - Qiita
- RailsにおけるXSS対策 - Zenn
- Securing React Apps with DOMPurify - OpenReplay
- How to prevent XSS with dangerouslySetInnerHTML - DEV