結論(Content-Typeと文字コードを全テキスト応答へ明示)
Content-Typeと文字コードを全テキスト応答へ明示とは、トップページだけでなく下層ページも含むすべての応答に、実体と一致するContent-Typeヘッダーを付けることです。text/*やXML系の応答には「; charset=UTF-8」のように文字コードも明示します。サーバーとブラウザの文字コード認識を一致させ、文字コード誤認によるXSSやMIMEスニッフィング、文字化けを防ぎます。OWASP ASVS 5.0.0 V4.1.1とIPA「安全なウェブサイトの作り方」が求める基本対策です。
この記事のポイント
- Content-Typeはメディアタイプと文字コードをブラウザへ伝え、HTMLとして解釈するか・どの文字コードで読むかを決めます。
- IPA「安全なウェブサイトの作り方」はXSSの根本的解決5-(viii)として、Content-Typeへのcharset指定を求めています。
- application/jsonはcharsetパラメータが定義されておらず、画像・PDFなどのバイナリも文字コード指定は不要です。
- セキュリティAIチェッカーはトップページと下層ページ(最大3ページ)を確認し、1ページでも欠落があれば×と判定します。
- 設定を変える前に、ページの実際の文字コード(UTF-8かShift_JISか)を必ず棚卸しします。
| 診断項目名 | Content-Typeと文字コードを全テキスト応答へ明示 |
|---|---|
| 項目ID | crawl.content-type-charset-coverage |
| 分類 | HTMLの基本衛生 |
| 重大度・配点の重み | 中(medium) 重み 3 |
| 関連する標準・ガイドライン | OWASP ASVS 5.0.0 V4.1.1(L1:本文を持つ全HTTP応答に、実体と一致するContent-Typeとcharsetを付ける)、ASVS V3.4.4(L2:全応答に X-Content-Type-Options: nosniff)、IPA「安全なウェブサイトの作り方」1.5 XSS 根本的解決 5-(viii)(Content-Typeに文字コードを指定)、RFC 9110 §8.3(Content-Type)・§8.3.2(charset) |
Content-Typeと文字コードを全テキスト応答へ明示するとは
Content-Typeと文字コードを全テキスト応答へ明示するとは、サイトが返すHTTP応答すべてに Content-Type ヘッダーを付け、テキスト系(text/*)とXML系(application/xml、application/〜+xml)の応答には charset パラメータで文字コードを明示することです。
Content-Type: text/html; charset=UTF-8
トップページだけ正しく設定されていても、下層のCMS出力ページや別アプリ、静的HTMLの一部で文字コード宣言が漏れていることはよくあります。そのためセキュリティAIチェッカーのこの項目は、「全ページで揃っているか」を見る網羅性(coverage)の項目として設計されています。単一ページのHTML内 <meta charset> を見る「文字コードが明示されている」とは別の、レスポンスヘッダー側の確認です。
Content-Typeとcharsetの役割
Content-Type: text/html; charset=UTF-8 のように、Content-Typeはメディアタイプ(何のデータか)と、パラメータ(文字の符号化方式など)を伝えます(RFC 9110 §8.3、charsetは§8.3.2)。ブラウザはこの値をもとに、次のことを決めます。
- HTMLとして解釈するか、プレーンテキストとして表示するか
- どの文字コードでバイト列を文字に戻すか
charsetが伝わらないとき、ブラウザはどう文字コードを決めるか
charsetが伝わらない場合、ブラウザはおおむね次の順で文字コードを決めます。
- BOM(バイト順マーク)
- HTML内の
<meta charset>(先頭1024バイト以内) - 親フレームの文字コード
- 内容からの推測
- ロケール既定値
推測の結果がサーバー側の想定と食い違うと、文字化けだけでなく、エスケープ処理の前提が崩れることがあります。
なぜContent-Typeと文字コードの明示がセキュリティ対策になるのか
文字コード誤認によるXSS(IPA 5-(viii))
IPAの「安全なウェブサイトの作り方」は、XSSの根本的解決の一つとして「HTTPレスポンスヘッダのContent-Typeフィールドに文字コード(charset)を指定する」(5-(viii))を挙げています。
典型例として、UTF-7で +ADw-script+AD4- のように書かれた文字列は、サーバーのHTMLエスケープでは < > を含まないため素通りし、ブラウザがUTF-7と判定すると <script> として解釈されるという問題が知られています。現在の主要ブラウザはUTF-7をHTMLの文字コードとして扱わないため、この攻撃そのものは過去のものになりつつあります。しかし、Shift_JISやEUC-JPなどマルチバイト文字コードの判定違いによりエスケープ済み文字列の境界がずれる問題など、「サーバーとブラウザで文字コードの認識を一致させる」という原則は今も有効です。
OWASP ASVS 5.0.0 V4.1.1の要件
ASVS 5.0.0 V4.1.1(Level 1)は、本文を持つすべてのHTTP応答に、実際の内容と一致するContent-Typeを付け、text/、/+xml、/xml のようなメディアタイプではcharsetパラメータで安全な文字コード(UTF-8、ISO-8859-1など)を指定することを求めています。あわせてV3.4.4(Level 2)は、全応答に X-Content-Type-Options: nosniff を付けることを求めています。
charsetが不要なメディアタイプ
| メディアタイプ | charsetの扱い |
|---|---|
application/json | RFC 8259はJSONのメディアタイプ登録で charset パラメータを定義しておらず、JSONはUTF-8で交換することが求められます。付けても準拠した受信側には効果がないため、本項目でも要求しません |
| 画像・PDF・フォント・ZIPなどのバイナリ | 文字コードの概念がないため対象外です |
application/javascript / text/javascript | text/javascript は text/* に含まれるため本項目の対象になります |
Content-Typeと文字コードについてよくある誤解
- 「HTMLに
<meta charset="UTF-8">があるから十分」:HTML内の宣言はブラウザが先頭を読んで初めて効くもので、ヘッダーで先に宣言する方が確実です。ASVS・IPAともにヘッダーでの指定を求めています。両方を一致させるのが最善です。 - 「Content-Typeはサーバーが自動で付ける」:静的ファイルは拡張子から推定されますが、PHPやCGIの出力、CMSの独自出力、エラーページ、リバースプロキシ経由の応答では欠けることがあります。
- 「nosniffを付ければContent-Typeは不要」:nosniffはContent-Typeを前提に推測を止める仕組みです。Content-Typeが無い・誤っている応答は、nosniff下では正しく表示されない、またはブロックされる原因になります。nosniffの網羅性は「nosniffを全応答へ適用」で確認します。
Content-Typeと文字コードを全テキスト応答へ明示の診断基準(セキュリティAIチェッカー)
取得方法
- V2診断で取得できた同一オリジンの巡回ページ(
<a href>・GETフォームのaction等から抽出。ログアウト・削除系のURLや、css・js・画像・PDF等の拡張子は除外)のうち、トップページ以外の先頭2件を追加でGET取得します。 - 対象はトップページと合わせて最大3ページです(取得失敗・本文上限超過・別オリジンへのリダイレクトは除外)。
- リクエストは1件あたりタイムアウト5秒、本文上限180KB、リダイレクト最大2回で、追加診断全体のリクエスト上限(40件)の範囲で行います。
- 各応答の
Content-Typeヘッダー値を判定します。
判定条件
| 判定 | 条件 | 得点への影響 |
|---|---|---|
| ○ 問題なし(pass) | 全ページでContent-Typeがあり、text/* と application/xml・application/〜+xml の応答にはすべて ; charset= がある | 重み3 × 1.00 |
| × 要改善(fail) | 本文を持つ応答でContent-Typeが欠落している、または text/*・XML系の応答に charset パラメータがない(1ページでも該当すればfail) | 重み3 × 0 |
| 対象外(na) | なし(トップページは必ず取得済みのため常に判定可能) | — |
- charsetの判定は
;\s*charset=の有無で行い、値の種類(UTF-8、Shift_JIS等)は問いません。ただし実体と一致しない値は文字化けの原因になるため、修正時は必ず実際の符号化方式と合わせてください。 - 本文が空の応答(204等)はContent-Typeを要求しません(ASVSの要件も「本文を持つ応答」が対象です)。
application/jsonや画像などはContent-Typeさえあればpassです。image/svg+xmlのようにapplication/以外の+xml型も本診断ではcharsetを要求しませんが、ASVSの趣旨では付与が望まれます。- 証拠欄には「該当ページ数/判定ページ数」を表示します。
- スコアは「pass=1.00、warn=0.45、fail=0」の係数に重みを掛けた加重平均で計算され、na項目は分母から除外されます。
自治体サイトでの判定例
| 状況 | 判定 |
|---|---|
トップページは text/html; charset=UTF-8 だが、下層の旧来の静的HTMLディレクトリがApache既定のまま text/html(charsetなし)で配信されている | ×(1/3ページ) |
CMSで生成した全ページが text/html; charset=utf-8、お知らせのRSSが別取得対象 | ○(巡回対象はHTMLのみ) |
PHPで作られた施設予約の入口ページが、default_charset を空にした環境で header('Content-Type: text/html') とだけ出力している | × |
Content-Typeや文字コードの明示が漏れているとどんなリスクがあるか
- 文字コード誤認によるXSS:charset不明の応答で、ブラウザが攻撃者の意図した文字コードで解釈すると、サーバー側のエスケープが無効化される可能性があります。現行ブラウザでの成立条件は限られますが、旧来の端末や組込みブラウザでは残る可能性があります。
- MIMEスニッフィングによる誤解釈:Content-Typeが無いと、ブラウザが内容からHTMLやスクリプトと推測することがあります。利用者がアップロードしたファイルや、エラーメッセージに入力値を含む応答がHTMLとして描画されると、XSSの足がかりになります。
- 表示崩れ・情報の誤伝達:文字化けにより、避難情報や手続き案内が読めなくなるという可用性上の被害もあります。自治体サイトではこちらの影響も無視できません。
Content-Typeと文字コードの設定方法(Apache・nginx・IIS・PHP・Next.js)
Apache(.htaccess / httpd.conf)
# text/plain と text/html に既定のcharsetを付与
AddDefaultCharset UTF-8
# その他のテキスト系拡張子にもcharsetを付与(mod_mime)
<IfModule mod_mime.c>
AddCharset UTF-8 .html .htm .css .js .txt .xml .csv .rss .atom .svg
</IfModule>
AddDefaultCharset は既定でOffで、指定すると text/plain と text/html の応答にcharsetを追加します。Shift_JISで作られた古いページが残っている場合、全体をUTF-8と宣言すると文字化けするため、ディレクトリ単位で AddDefaultCharset Shift_JIS を指定するか、先に文字コードをUTF-8へ変換してください。
nginx
http {
charset utf-8;
# 既定の charset_types(text/html, text/xml, text/plain 等)に加えたいもの
charset_types text/css text/plain text/xml application/javascript application/rss+xml application/atom+xml image/svg+xml;
}
charset はngx_http_charset_moduleのディレクティブで、既定値は off です。text/html は常に対象で、それ以外は charset_types(既定は text/html・text/xml・text/plain・text/vnd.wap.wml・application/javascript・application/rss+xml)に含まれるものだけにcharsetが付きます。
IIS(web.config)
<configuration>
<system.webServer>
<staticContent>
<remove fileExtension=".html" />
<mimeMap fileExtension=".html" mimeType="text/html; charset=UTF-8" />
<remove fileExtension=".css" />
<mimeMap fileExtension=".css" mimeType="text/css; charset=UTF-8" />
</staticContent>
</system.webServer>
<system.web>
<globalization requestEncoding="utf-8" responseEncoding="utf-8" />
</system.web>
</configuration>
ASP.NETの動的ページは system.web 配下の globalization 設定で応答の文字コードが決まります。静的ファイルは mimeMap でcharset付きのMIMEタイプを指定します。
PHP
<?php
// 出力より前に送る
header('Content-Type: text/html; charset=UTF-8');
// php.ini 側でも default_charset = "UTF-8"(PHP 5.6以降の既定値)を確認
Next.js(Vercel)
Next.jsのページ応答は既定で text/html; charset=utf-8 になります。Route Handlerで独自に応答を返す場合は明示します。
export async function GET() {
return new Response("本文", {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}
段階的な導入手順
| 手順 | 内容 |
|---|---|
| 1 | 全ページの実際の文字コードを棚卸しします(UTF-8とShift_JISが混在していないか) |
| 2 | 混在があれば、まず古いページをUTF-8へ変換するか、ディレクトリ単位で正しいcharsetを宣言します |
| 3 | サーバー全体の既定値を設定し、HTML内の <meta charset> も同じ値に揃えます |
| 4 | あわせて X-Content-Type-Options: nosniff を全応答へ付けます(「nosniffを全応答へ適用」) |
設定後の確認方法
# ヘッダーだけ確認
curl -sI https://www.example.lg.jp/ | grep -i '^content-type'
curl -sI https://www.example.lg.jp/kurashi/index.html | grep -i '^content-type'
# エラーページも確認(本項目の対象外でも整えておく)
curl -s -o /dev/null -D - https://www.example.lg.jp/no-such-page | grep -i '^content-type'
Content-Type: text/html; charset=UTF-8 のようにcharsetまで表示されれば対応済みです。設定を変えたら、セキュリティAIチェッカーで再診断し、証拠欄の該当ページ数が0になっていることを確認します。
サイトを壊さないための注意点
- 実体と違うcharsetを宣言しない:Shift_JISのファイルをUTF-8と宣言すると全面的に文字化けします。宣言の変更より先に実体を確認してください。
- CSV・ダウンロードファイル:Excelで開く前提のCSVをShift_JISで配布している場合、UTF-8を一律付与すると文字化けします。拡張子ごとに設定を分けてください。
- CDN・プロキシのキャッシュ:ヘッダー変更後はキャッシュを削除しないと古い応答が残ります。
よくある質問(FAQ)
HTMLに meta charset があれば、Content-Typeヘッダーのcharsetは不要ですか?
不要ではありません。HTML内の宣言はブラウザが先頭を読んで初めて効くもので、ヘッダーで先に宣言する方が確実です。ASVS・IPAともにヘッダーでの指定を求めており、ヘッダーとmeta charsetの値を一致させるのが最善です。
application/json にもcharsetを付ける必要がありますか?
必要ありません。RFC 8259はJSONのメディアタイプ登録でcharsetパラメータを定義しておらず、JSONはUTF-8で交換することが求められます。セキュリティAIチェッカーもJSONにはcharsetを要求せず、Content-Typeがあればpassです。
charsetの値はUTF-8でなければいけませんか?
診断ではcharsetの有無だけを判定し、UTF-8かShift_JISかは問いません。ただし実体と違う値を宣言すると全面的に文字化けするため、実際の符号化方式と合わせてください。
nosniffを付けていればContent-Typeは省略してよいですか?
省略できません。nosniffはContent-Typeを前提にブラウザの推測を止める仕組みです。Content-Typeが無い・誤っている応答は、nosniff下では正しく表示されない、またはブロックされる原因になります。
UTF-7を使ったXSSは今でも起こりますか?
現在の主要ブラウザはUTF-7をHTMLの文字コードとして扱わないため、この攻撃そのものは過去のものになりつつあります。ただしShift_JISやEUC-JPの判定違いでエスケープの前提が崩れる問題や、旧来端末・組込みブラウザでの残存リスクがあるため、文字コードの明示は今も有効です。
トップページは正しいのに×になるのはなぜですか?
この項目は下層ページを含む全ページで揃っているかを見る網羅性の項目で、1ページでも欠落があれば×になります。下層の旧来の静的HTMLディレクトリや別アプリ、PHPの独自出力でcharsetが漏れていることがよくあります。証拠欄の「該当ページ数/判定ページ数」を手がかりに確認してください。
この項目を含む127項目を、URLを入れるだけで無料診断します。