• 診断項目 No.55/127
  • HTMLの基本衛生
  • 重大度 低(low)

文字コードが明示されているとは?charset指定の方法・ブラウザの判定順序・診断基準

文字コードが明示されているとは、Content-Typeヘッダーのcharsetやmeta charsetでUTF-8を宣言していることです。文字化けやエスケープ回避型XSSを防ぐ理由、Apache・nginx・IIS・PHPの設定例、診断基準を解説します。

結論(文字コードが明示されている)

文字コードが明示されているとは、HTTPのContent-Typeヘッダー(text/html; charset=UTF-8)またはHTML先頭のmeta charsetで、ページの文字コードをUTF-8と宣言していることです。明示が無いとブラウザが推測し、文字化けや、サーバーのエスケープとブラウザの解釈がずれることによるXSSの原因になります。IPA「安全なウェブサイトの作り方」もヘッダーでの指定を求めています。セキュリティAIチェッカーはUTF-8の明示で○、UTF-8以外や宣言なしで△と判定します。

この記事のポイント

  • ブラウザはBOM→手動指定→HTTPヘッダーのcharset→先頭1024バイト内のmeta→推測の順で文字コードを決めます。
  • HTTPヘッダーの指定はmetaより優先されるため、両方を指定するなら値を一致させます。
  • HTMLで有効な文字コードはUTF-8だけで、meta charsetは文書の最初の1024バイト以内に置きます。
  • UTF-7攻撃は現在のブラウザでは成立しませんが、文字コードの解釈のずれによるXSSは今でも起こり得ます。
  • Shift_JISからの移行は、ファイルの変換とヘッダー・metaの切り替えを同時に行わないと全ページが文字化けします。
  • 診断はヘッダーを優先して宣言を読み取り、UTF-8なら○、UTF-8以外や宣言なしなら△(重み1)です。
診断項目名文字コードが明示されている
項目IDhtml.charset
分類HTMLの基本衛生
重大度・配点の重み低(low) 重み 1
関連する標準・ガイドラインIPA「安全なウェブサイトの作り方」5-(viii)(HTTPレスポンスヘッダのContent-Typeフィールドに文字コード(charset)を指定する)/OWASP ASVS 5.0.0 V4.1.1(L1: 本文を持つ全応答に、charsetを含む正しいContent-Typeを付ける)、V1.2.1(L1: 文脈に応じた出力エンコーディング)/HTML Living Standard(文書の文字エンコーディングの指定、エンコーディング判定アルゴリズム)/CWE-116(出力の不適切なエンコードまたはエスケープ)

文字コードが明示されているとは

文字コードが明示されているとは、サーバーやHTMLが、ページを解釈するための文字コード(UTF-8、Shift_JISなど)をブラウザに宣言していることです。宣言の方法は2つあります。

Content-Type: text/html; charset=UTF-8
<meta charset="utf-8">

ブラウザは、受け取ったバイト列をどの文字コードで解釈するかを決めてからHTMLを解析します。サーバーやHTMLが文字コードを明示していないと、ブラウザは内容から推測します。推測が外れると文字化けが起きるだけでなく、過去には、攻撃者が送り込んだ文字列によって特殊な文字コードとして解釈させ、サーバー側のエスケープ処理をすり抜けてスクリプトを実行させる攻撃(UTF-7を悪用したXSSなど)がありました。

IPAの「安全なウェブサイトの作り方」は、XSS対策の根本的解決の1つ(5-(viii))として、HTTPレスポンスヘッダのContent-Typeに文字コードを指定することを挙げています。セキュリティAIチェッカーのこの項目は、トップページについて、HTTPの Content-Type ヘッダーまたはHTMLの <meta> 要素でUTF-8が明示されているかを確認します。

関連する標準

  • IPA「安全なウェブサイトの作り方」5-(viii):HTTPレスポンスヘッダのContent-Typeフィールドに文字コード(charset)を指定する
  • OWASP ASVS 5.0.0 V4.1.1(L1):本文を持つ全応答に、charsetを含む正しいContent-Typeを付ける
  • OWASP ASVS 5.0.0 V1.2.1(L1):文脈に応じた出力エンコーディング
  • HTML Living Standard:文書の文字エンコーディングの指定、エンコーディング判定アルゴリズム
  • CWE-116:出力の不適切なエンコードまたはエスケープ

ブラウザは文字コードをどう決めるのか

ブラウザが文字コードを決める順序

HTML Living Standardのエンコーディング判定アルゴリズムは、おおむね次の順で文字コードを決めます。

  1. BOM(バイトオーダーマーク)があればそれに従う
  2. 利用者が手動で指定していればそれに従う(任意)
  3. 転送層(HTTPの Content-Type ヘッダーの charset)が指定していればそれに従う
  4. 文書の先頭部分(推奨は最初の1024バイト)を事前走査し、<meta charset> や <meta http-equiv="Content-Type"> を探す
  5. 親文書の文字コード、過去の訪問時の情報、自動判定、既定値の順に推測する

つまり、HTTPヘッダーの指定は <meta> より優先されます。ヘッダーとmetaが食い違うとヘッダーが勝つため、両方を指定する場合は一致させる必要があります。

HTMLでの指定方法と制約

<meta charset="utf-8">
<!-- 次の書き方も同じ意味(古い形式) -->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

MDNによると、<meta charset> の値は utf-8(大文字小文字は区別しない)でなければならず、UTF-8がHTMLで唯一有効な文字コードです。また、文字コードを宣言する <meta> 要素は文書の最初の1024バイト以内に完全に収める必要があります。HTML Living Standardでは、charset 属性を持つ meta 要素は1文書に1つまでです。

文字コードの明示はなぜセキュリティ対策になるのか

UTF-7攻撃と現在のブラウザ

HTML Living Standardは、UTF-7、CESU-8、BOCU-1、SCSU、EBCDIC、UTF-32などをブラウザが対応してはならない文字コードとして明記しており、現在の主要ブラウザではUTF-7を悪用した古典的な攻撃は成立しません。

今でも起こり得る「解釈のずれ」

ただし、文字コードの不一致によってサーバー側のフィルターとブラウザの解釈がずれる問題(マルチバイト文字の解釈差を使ったエスケープ回避など)は、現在でも起こり得ます。2024年にはSonarの研究者が、charsetの指定が無い(または不正な)応答を、ブラウザの自動判定によってISO-2022-JPとして解釈させ、XSSにつなげる手法を公表しています。文字コードの明示は、こうした「解釈のずれ」を減らすための基本対策です。

文字コードについてよくある誤解

  • 「<meta charset> があればヘッダーは不要」:IPAとASVS V4.1.1はHTTPヘッダーでの指定を求めています。metaは1024バイトを超えた位置にあると無視される可能性があり、ヘッダーと食い違うとヘッダーが優先されます。
  • 「Shift_JISを明示していれば問題ない」:明示自体は推測を防ぎますが、HTMLの仕様上の文字コードはUTF-8のみが有効です。フォームの入力値を他システムへ渡す際の変換や、機種依存文字の扱いでも不整合が起きやすくなります。
  • 「<meta http-equiv="Content-Type" content="text/html"> で指定済み」:charset= が無ければ文字コードは指定されていません。

文字コードが明示されているの診断基準(セキュリティAIチェッカー)

取得対象

  • 診断URLへのGETで取得したトップページ(最終URL)の応答ヘッダー Content-Type と、HTML本文(最大2MB)。
  • HTML全体の文字コード判定は行わず、宣言の有無と値を確認します。BOMの有無は判定に使いません。

判定方法

  1. ヘッダーの文字コード:Content-Type から ;\s*charset\s*=\s*["']?([\w.:-]+) で値を取り出します。
  2. HTMLの文字コード:最初に現れる <meta ... charset=値> を <meta\b[^<>]*?\bcharset\s*=\s*["']?\s*([\w.:-]+) で取り出します。<meta charset="..."> と <meta http-equiv="Content-Type" content="...; charset=..."> の両方に一致し、charset= を含まない http-equiv は宣言と見なしません。
  3. ブラウザと同じくヘッダーの値を優先し、ヘッダーに無ければmetaの値を「宣言された文字コード」とします。

判定条件

判定条件得点への影響
○ 問題なし(pass)宣言された文字コードがUTF-8(utf-8/utf8、大文字小文字を問わない)。根拠欄に、ヘッダーとmetaのどちらで確認したかを表示重み1を満点で加算
△ 要確認(warn)文字コードの宣言はあるがUTF-8以外(例:Shift_JIS、EUC-JP)。根拠欄に値を表示1×0.45=0.45点
△ 要確認(warn)ヘッダーにもmetaにも文字コードの宣言がない1×0.45=0.45点
× 要改善(fail)/対象外(na)使用しない—

HTTPヘッダー側のcharsetが巡回した全ページに付いているかは、別項目「Content-Typeと文字コードを全テキスト応答へ明示」(crawl.content-type-charset-coverage、重み3)で厳しく判定しています。本項目は、トップページで「UTF-8が明示されているか」を軽い重みで確認する位置付けです。Content-Typeヘッダーそのものの有無は「Content-Typeが明示されている」の項目で確認します。

自治体サイトでの判定例

トップページの状態判定
Content-Type: text/html; charset=UTF-8○(ヘッダーで確認)
ヘッダーは text/html のみ、HTMLに <meta charset="utf-8">○(metaで確認)。ただし crawl.content-type-charset-coverage ではヘッダーのcharset欠落として指摘されます
古いCMSで <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> のみ△(UTF-8以外)
ヘッダーにもHTMLにも指定がない△(宣言なし)

文字コードが明示されていないとどんなリスクがあるか

攻撃シナリオ(文字コードの解釈のずれ)

  1. サイトは文字コードを明示しておらず、サーバー側はShift_JISを前提にエスケープ処理をしている。
  2. 攻撃者は、マルチバイト文字の先頭バイトを使って直後の " や \ を「文字の一部」として扱わせる入力を送る。
  3. サーバー側とブラウザ側で文字の区切り方が異なると、エスケープしたはずの引用符が属性値を閉じる文字として機能し、XSSにつながる可能性がある。

実際の事例

実際の事例として、2005年にGoogleのリダイレクト処理などのページで、Content-Typeに文字コードが指定されていなかったためにUTF-7でエンコードした攻撃文字列が実行される脆弱性が報告され、修正されています(現在のブラウザはUTF-7に対応していません)。

想定される被害

  • 文字化けによる重要情報(避難所名、申請期限など)の誤表示
  • フォーム入力値の文字化け・欠落による申請データの不整合
  • エスケープ処理の回避によるXSS

文字コードをUTF-8で明示する設定方法(Apache・nginx・IIS・PHP・Next.js)

1. サーバーでUTF-8を指定

Apache

# Apache (.htaccess または httpd.conf)
AddDefaultCharset UTF-8
# 拡張子別に指定する場合
AddCharset UTF-8 .html .css .js .json .xml .txt

nginx

# nginx(http / server / location)
charset utf-8;
charset_types text/html text/css text/plain text/xml application/javascript application/json;

IIS(web.config)

<!-- IIS web.config:静的HTMLのMIMEタイプにcharsetを付ける例 -->
<system.webServer>
  <staticContent>
    <remove fileExtension=".html" />
    <mimeMap fileExtension=".html" mimeType="text/html; charset=utf-8" />
  </staticContent>
</system.webServer>

PHP

// PHP
header('Content-Type: text/html; charset=UTF-8');
// php.ini の default_charset = "UTF-8"(PHP 5.6以降の既定値)

Next.js(Vercel)

Next.js(Vercel)は、ページ応答に Content-Type: text/html; charset=utf-8 を付与し、<meta charset="utf-8"> も出力します。独自のRoute Handlerでテキストを返す場合は、new Response(body, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } }) のように明示します。

2. HTMLの先頭でmetaを指定

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>○○市公式ホームページ</title>

<meta charset> は <head> の最初に置き、コメントや長い <script> より前に書いて1024バイト以内に収めます。

3. Shift_JIS等からUTF-8への移行

ファイルの文字コードを変換しないままヘッダーだけUTF-8にすると、全ページが文字化けします。次の順で進めます。

  1. テンプレート、静的HTML、CSV・JSONなどのデータファイルの文字コードを棚卸しする
  2. ファイルをUTF-8(BOMなし)に変換し、metaとヘッダーを同時にUTF-8へ切り替える
  3. フォームの送信先(PHP、CGI、外部システム連携)が受け取る文字コードを確認し、必要なら変換処理を修正する
  4. 機種依存文字(丸数字、はしご高など)の表示を確認する

4. 確認方法

curl -sI https://www.city.example.lg.jp/ | grep -i '^content-type'
curl -s  https://www.city.example.lg.jp/ | head -c 1024 | grep -oi '<meta[^>]*charset[^>]*>'

ブラウザでは開発者ツールのコンソールで document.characterSet が UTF-8 になっていることを確認します。

サイトを壊さないための注意点

  • AddDefaultCharset UTF-8 をShift_JISのファイルが残るディレクトリに適用すると文字化けします。ディレクトリ単位で、実ファイルの文字コードと一致させます。
  • ヘッダーとmetaで異なる文字コードを指定しないでください。ヘッダーが優先され、metaに合わせて作ったファイルが文字化けします。
  • CSVのダウンロードなど、Excelでの表示を考慮してShift_JISで配布しているファイルは、HTMLとは別にContent-Typeを設定します。

よくある質問(FAQ)

meta charsetがあればHTTPヘッダーのcharsetは不要ですか?

不要ではありません。IPAとASVS V4.1.1はHTTPヘッダーでの指定を求めています。metaは1024バイトを超えた位置にあると無視される可能性があり、ヘッダーと食い違うとヘッダーが優先されます。

Shift_JISを明示していれば問題ありませんか?

明示自体はブラウザの推測を防ぎますが、HTMLの仕様上有効な文字コードはUTF-8だけです。フォーム入力値を他システムへ渡す際の変換や機種依存文字の扱いでも不整合が起きやすく、診断ではUTF-8以外として△になります。

文字コードを指定しないとなぜXSSにつながるのですか?

サーバー側のエスケープとブラウザの文字の解釈がずれると、エスケープしたはずの引用符が属性値を閉じる文字として機能することがあるためです。2024年にはSonarの研究者が、charset指定の無い応答をISO-2022-JPとして解釈させてXSSにつなげる手法を公表しています。

UTF-7を使ったXSSは今でも起きますか?

現在の主要ブラウザでは成立しません。HTML Living StandardはUTF-7などをブラウザが対応してはならない文字コードとして明記しています。ただし、他の文字コードの解釈差を使ったエスケープ回避は今でも起こり得ます。

meta http-equiv="Content-Type" content="text/html" で指定済みですか?

指定済みではありません。charset= が無ければ文字コードは指定されていません。診断でも、charset= を含まないhttp-equivは宣言と見なしません。

文字コードの明示が○でも、別の項目で指摘されるのはなぜですか?

この項目はトップページでヘッダーかmetaのどちらかにUTF-8があれば○です。HTTPヘッダー側のcharsetが巡回した全ページに付いているかは、別の項目(重み3)で厳しく判定しているため、metaだけの場合はそちらで指摘されます。

セキュリティAIチェッカーを利用する

この項目を含む127項目を、URLを入れるだけで無料診断します。

参考資料