はじめに
「サニタイズ」と「エスケープ」、どちらもセキュリティ対策で使われる用語ですが、違いを正確に説明できますか?
この記事では、両者の違いをコード例と図解で分かりやすく解説します。
結論:一言でいうと
| 処理 | 目的 | データへの影響 |
|---|---|---|
| サニタイズ | 危険な部分を 除去・無害化 する | 一部が失われる |
| エスケープ | 特殊文字を 別の表記に変換 する | 保持される |
サニタイズとは
サニタイズ(sanitize)は「消毒する」という意味で、危険な部分を取り除く処理です。
コード例(Python)
import re
def sanitize(text: str) -> str:
"""scriptタグを除去する"""
return re.sub(r'<script.*?>.*?</script>', '', text, flags=re.DOTALL)
# 使用例
user_input = "<script>alert('XSS')</script>こんにちは"
result = sanitize(user_input)
print(result) # → "こんにちは"
処理のイメージ
入力: "<script>alert('XSS')</script>こんにちは"
↓ サニタイズ(除去)
出力: "こんにちは"
サニタイズでは、危険と判断された部分が完全に消えます。元のデータの一部が失われることを意識しましょう。
エスケープとは
エスケープ(escape)は、特殊文字を無害な別の表記に変換する処理です。
コード例(Python)
import html
def escape(text: str) -> str:
"""HTMLの特殊文字をエスケープする"""
return html.escape(text)
# 使用例
user_input = "<script>alert('XSS')</script>"
result = escape(user_input)
print(result) # → "<script>alert('XSS')</script>"
処理のイメージ
入力: "<script>alert('XSS')</script>"
↓ エスケープ(変換)
出力: "<script>alert('XSS')</script>"
ブラウザで表示すると、<script>alert('XSS')</script> という文字列がそのまま見える(実行はされない)。
エスケープでは元のデータ内容は保持されます。表示上は元の文字として見えますが、コードとしては実行されません。
変換対応表(HTMLエスケープ)
| 元の文字 | エスケープ後 |
|---|---|
< |
< |
> |
> |
& |
& |
" |
" |
' |
' |
使い分けの指針
エスケープを使うケース
ユーザー入力をそのまま表示したい場合に使います。
# コメント投稿など、入力内容をそのまま表示したい場合
user_comment = "<b>太字にしたかった</b>"
escaped = html.escape(user_comment)
# → "<b>太字にしたかった</b>" として表示される
サニタイズを使うケース
一部のHTMLタグは許可しつつ、危険なタグは除去したい場合に使います。
import bleach
def sanitize_rich_text(text: str) -> str:
"""許可されたタグのみ残し、他は除去"""
allowed_tags = ['b', 'i', 'u', 'a', 'p', 'br']
return bleach.clean(text, tags=allowed_tags, strip=True)
# 使用例
user_input = "<b>太字</b><script>alert('XSS')</script>"
result = sanitize_rich_text(user_input)
print(result) # → "<b>太字</b>alert('XSS')"
bleachライブラリはpip install bleachでインストールできます。
実践:Pydanticでの実装例
FastAPIなどで使われるPydanticでは、バリデーターを使って入力時にエスケープ処理を入れられます。
from pydantic import BaseModel, field_validator
import html
class CommentRequest(BaseModel):
content: str
@field_validator('content')
@classmethod
def escape_content(cls, v: str) -> str:
return html.escape(v.strip())
再利用可能なカスタム型として定義
from typing import Annotated
from pydantic import BaseModel, BeforeValidator
import html
# エスケープ関数
def escape_html(v: str) -> str:
return html.escape(v.strip())
# カスタム型として定義
EscapedStr = Annotated[str, BeforeValidator(escape_html)]
# どのモデルでも使い回せる
class CommentRequest(BaseModel):
content: EscapedStr
class PostRequest(BaseModel):
title: EscapedStr
body: EscapedStr
多層防御の考え方
サニタイズ・エスケープだけでは不十分です。多層防御を心がけましょう。
| 対策場所 | 方法 |
|---|---|
| 入力時 | Pydanticなどでバリデーション・エスケープ |
| 出力時 | テンプレートエンジンの自動エスケープ |
| HTTP |
Content-Security-Policyヘッダー |
| Cookie |
httponly属性、secure属性 |
まとめ
| 項目 | サニタイズ | エスケープ |
|---|---|---|
| 処理内容 | 危険な部分を除去 | 特殊文字を変換 |
| データ | 一部が失われる | 保持される |
| 主な用途 | リッチエディタ、ファイル名 | コメント表示、ログ出力 |
| ライブラリ例 | bleach, DOMPurify | html.escape, textContent |