• 診断項目 No.34/127
  • HTMLの基本衛生
  • 重大度 中(medium)

Content-Typeが明示されているか?charset(文字コード)指定の重要性・設定方法・診断基準

Content-Typeが明示されているとは、応答ヘッダーでMIMEタイプとcharsetを宣言している状態です。欠落時のMIMEスニッフィングとXSSのリスク、文字コードの優先順位、Apache・nginx・IIS・PHP・Next.jsの設定例と診断基準を解説します。

結論(Content-Typeが明示されている)

Content-Typeが明示されているとは、HTTPレスポンスヘッダーのContent-Typeで、本文の種類(MIMEタイプ)と、HTMLなどテキスト系では文字コード(charset)まで宣言している状態です。例は「text/html; charset=UTF-8」です。欠けるとブラウザが内容から種類や文字コードを推測し、文字化けやエスケープの前提が崩れることによるXSSの原因になり得るため、OWASP ASVS 5.0.0 V4.1.1やIPAが明示を求めています。

この記事のポイント

  • Content-Typeは本文の種類(MIMEタイプ)と文字コード(charset)をブラウザに伝えるヘッダーです。
  • OWASP ASVS 5.0.0 V4.1.1(L1)とIPA「安全なウェブサイトの作り方」5-(viii)が、ヘッダーでの明示を求めています。
  • ヘッダーのcharsetはmeta宣言より優先されるため、両者が食い違うと全ページが文字化けすることがあります。
  • 診断はトップページの最終応答を見て、テキスト系なのにcharsetが無い、またはヘッダー自体が無い場合を△とします。
  • 設定時は実ファイルの文字コード(UTF-8かShift_JISか)に合わせ、X-Content-Type-Options: nosniffも併用します。
診断項目名Content-Typeが明示されている
項目IDresponse.content-type
分類HTMLの基本衛生
重大度・配点の重み中(medium) 重み 2
関連する標準・ガイドラインOWASP ASVS 5.0.0 V4.1.1(Level 1:本文を持つ全応答に実体と一致するContent-Typeとcharset)/IPA「安全なウェブサイトの作り方」1.5 XSS 対策 5-(viii)(Content-Typeに文字コードを指定)/RFC 9110 8.3(Content-Type)/CWE-116(Improper Encoding or Escaping of Output)

Content-Typeが明示されているとは

Content-Typeが明示されているとは、トップページ(転送後の最終応答)のHTTPレスポンスヘッダーに Content-Type が存在し、HTMLなどのテキスト系応答では 文字コード(charset)まで明示されている 状態です。

Content-Type: text/html; charset=UTF-8
  • text/html がメディアタイプ(type/subtype)、いわゆるMIMEタイプです。
  • charset=UTF-8 がパラメータ(文字コード)です。大文字小文字は区別されません。

Content-Type は「この本文が何であるか(MIMEタイプ)」と「どの文字コードで解釈すべきか(charset)」をブラウザに伝えるヘッダーです。これが欠けると、ブラウザは本文の内容から種類や文字コードを推測(スニッフィング)します。推測の結果が開発者の意図と異なると、文字化けだけでなく、エスケープ処理の前提が崩れてクロスサイト・スクリプティング(XSS)の原因になり得ます。

OWASP ASVS 5.0.0 は V4.1.1 で「本文を持つすべてのHTTP応答が、実際の内容に一致するContent-Typeヘッダーを持ち、text/*・*/+xml・*/xml ではcharsetパラメータで安全な文字エンコーディングを指定すること」をLevel 1要件としています。IPAも「HTTPレスポンスヘッダのContent-Typeフィールドに文字コード(charset)を指定する」ことを、XSSの根本的解決策の一つ(5-(viii))に挙げています。

Content-Typeとcharsetの明示はなぜ必要か

ヘッダーが無い場合のブラウザの動作(RFC 9110)

RFC 9110 8.3 は、送信者は原則として Content-Type を付けるべき(SHOULD)とし、ヘッダーが無い場合、受信者は application/octet-stream とみなすか、内容を調べて種類を判断してよい(MAY)としています。ブラウザは後者、つまりWHATWG MIME Sniffing 標準に沿った推測を行います。同RFCは、推測によって送信者の意図と異なる解釈が生じ得ることをセキュリティ上の懸念として挙げています。

charsetが無い場合の問題

HTMLでは、ヘッダーにcharsetが無くても、本文先頭(1024バイト以内)の <meta charset="UTF-8"> で文字コードを宣言できます。ただし、次の理由からヘッダーで明示するのが最も確実です。

  • metaの位置が後ろ過ぎる、あるいは記述が無いと、ブラウザは推測に頼ります。
  • 歴史的には、文字コード推測を悪用した攻撃(UTF-7でスクリプトを書き、古いIEにUTF-7と推測させる手法)が知られています。IPAが5-(viii)を挙げる背景はこの問題です。
  • CSSやJSON、XMLなど、HTML以外のテキスト応答ではmetaによる宣言ができません。

文字コードが決まる優先順位(WHATWG HTML)

WHATWG HTML標準のエンコーディング判定では、おおむね次の順で文字コードが決まります。

  1. 本文先頭のBOM(バイト順マーク)
  2. HTTPヘッダー Content-Type の charset
  3. 本文先頭部分の <meta charset> または <meta http-equiv="Content-Type">
  4. それでも決まらない場合はブラウザの推測(地域設定などに依存)

ヘッダーのcharsetはmetaより優先されるため、ヘッダーとmetaが食い違っている と、metaに書いた文字コードは無視されます。CMSの移行でHTMLはUTF-8に変わったのに、Webサーバーの設定が charset=Shift_JIS のまま残っている、といったケースでは全ページが文字化けします。ヘッダーを設定するときは、実ファイルとmetaの記述をそろえることが重要です。

実体と一致するContent-Typeを返す

ASVS V4.1.1 は「実際の内容と一致する」ことも求めています。代表的な対応は次のとおりです。

内容適切なContent-Type
HTMLtext/html; charset=UTF-8
CSStext/css; charset=UTF-8
JavaScripttext/javascript; charset=UTF-8(RFC 9239により application/javascript は廃止扱い)
JSON(APIの応答)application/json
XML・RSSapplication/xml; charset=UTF-8、application/rss+xml; charset=UTF-8
PDFapplication/pdf

利用者がアップロードしたファイルを配信する場合、拡張子や送信元が申告したタイプをそのまま返さず、サーバー側で決めた安全なタイプ(不明なら application/octet-stream と Content-Disposition: attachment)にすることも重要です。

Content-Typeについてよくある誤解

  • 「metaでcharsetを書いていればヘッダーは不要」:HTMLの表示には効きますが、ASVS V4.1.1やIPA 5-(viii)はヘッダーでの指定を求めています。metaの有無は「文字コードが明示されている」で評価します。
  • 「X-Content-Type-Options: nosniff があれば十分」:nosniffは「宣言されたタイプを尊重せよ」という指示であり、Content-Type自体が無ければ守るべき宣言がありません。両方そろって初めて意味を持ちます。nosniffは「MIMEスニッフィング対策」で評価します。
  • 「Shift_JISは危険」:Shift_JISやEUC-JPでも、正しく宣言して一貫して使えば問題ありません。危険なのは宣言が無い・実体と食い違う状態です。新規構築ではUTF-8を推奨します。

Content-Typeが明示されているの診断基準(セキュリティAIチェッカー)

取得方法

トップページ(入力URLから最大4回の転送を追跡した最終応答)をGETで取得し、そのレスポンスヘッダー Content-Type を評価します。巡回した下層ページを含む全応答の評価は、「Content-Typeと文字コードを全テキスト応答へ明示」で行います。このページはトップページ1応答での明示に集中しています。

判定条件

判定条件得点への影響
○ 問題なし(pass)Content-Typeがあり、テキスト系(text/*、application/xml、application/*+xml)ならcharsetパラメータもある。テキスト系以外(画像・JSON等)はContent-Typeがあればpass重み2 × 1.00
△ 要確認(warn)Content-Typeが無い(または空)、もしくはテキスト系なのにcharsetが無い重み2 × 0.45
× 要改善(fail)使用しません―
対象外(na)使用しません―
  • charsetの判定は ; charset=値 の形式(前後の空白・引用符を許容)を確認します。
  • 根拠欄には実際の値を表示し、charsetが無い場合は「(charset指定なし)」と付記します。Content-Typeが無い場合は「Content-Typeなし」と表示します。
  • 宣言されたタイプが実体と一致しているか(HTMLなのに text/plain を宣言していないか等)は、この項目では判定しません。ヘッダーの有無とcharsetの有無だけを評価します。
  • ×ではなく△としているのは、HTMLではmeta宣言で実害が抑えられている場合が多く、単独で直ちに攻撃が成立するとは限らないためです。

自治体サイトでの判定例

応答ヘッダー判定
Content-Type: text/html; charset=UTF-8○
Content-Type: text/html; charset=Shift_JIS○(宣言と実体が一致していることが前提)
Content-Type: text/html(metaでUTF-8宣言あり)△
Content-Typeなし△

Content-Typeが明示されていないとどんなリスクがあるか

  • XSSの成立条件をつくる:サーバー側のエスケープは特定の文字コードを前提に行われます。ブラウザが別の文字コードで解釈すると、エスケープをすり抜けた文字列がタグやスクリプトとして解釈される可能性があります(CWE-116)。
  • コンテンツの誤解釈:利用者がアップロードしたファイルや外部連携データが、Content-Type欠落によりHTMLやスクリプトとして解釈されると、蓄積型XSSにつながります。
  • 文字化けによる情報伝達の失敗:お知らせや手続案内が文字化けすると、住民に正確な情報が届きません。
  • 検索エンジンやスクリーンリーダーの誤動作:アクセシビリティにも影響します。

Content-Typeとcharsetの設定方法(Apache・nginx・IIS・PHP・Next.js)

Apache(.htaccess / httpd.conf)

# HTML・テキスト系の既定文字コードをUTF-8に
AddDefaultCharset UTF-8
AddCharset UTF-8 .html .htm .css .js .txt .xml .json

# 拡張子とMIMEタイプの対応を明示
AddType text/html .html .htm
AddType text/css .css
AddType text/javascript .js

AddDefaultCharset は text/html と text/plain に既定のcharsetを付与します。Shift_JISのサイトでUTF-8を指定すると文字化けするため、実ファイルの文字コードに合わせて値を決めてください。

nginx

http {
    include       mime.types;
    default_type  application/octet-stream;
    charset       utf-8;
    # text/html は常に対象のため列挙不要(列挙すると nginx -t で duplicate MIME type の警告が出ます)
    charset_types text/css text/plain text/xml text/javascript application/javascript application/json application/xml;
}

IIS

MIMEタイプは「MIMEの種類」機能で管理します。charsetを付ける場合は web.config で拡張子ごとに指定します。

<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以降の既定値)でも text/html にcharsetが付与されます。

Next.js(Vercel)

App Routerで生成されるHTMLには通常 text/html; charset=utf-8 が付きます。Route Handlerで独自応答を返す場合は明示します。

export async function GET() {
  return new Response("<p>ok</p>", {
    headers: { "Content-Type": "text/html; charset=utf-8" },
  });
}

段階的な導入と確認方法

  1. まず現状を確認します。
curl -sSI https://www.city.example.lg.jp/ | grep -i content-type
  1. HTMLファイルの実際の文字コードを確認します(既存ページがShift_JISとUTF-8で混在していないか)。
  2. 混在している場合は、一律指定せずディレクトリ単位で設定するか、UTF-8へ変換してから一律指定します。
  3. 適用後、代表的なページで文字化けが無いかをブラウザで確認します。
  4. X-Content-Type-Options: nosniff も併せて設定し、宣言したタイプが尊重されるようにします。
  5. 設定を変えたら、セキュリティAIチェッカーで再診断し、根拠欄に text/html; charset=UTF-8 のようにcharset付きの値が表示されることを確認します。

よくある質問(FAQ)

HTMLにmeta charsetを書いていれば、Content-Typeのcharsetは不要ですか?

HTMLの表示には効きますが、OWASP ASVS V4.1.1やIPA 5-(viii)はヘッダーでの指定を求めています。CSSやJSONなどHTML以外のテキストではmetaで宣言できないため、ヘッダーで明示するのが最も確実です。metaの有無は別項目「文字コードが明示されている」で評価します。

ヘッダーとmetaの文字コードが違うとどうなりますか?

ヘッダーのcharsetがmetaより優先されるため、metaに書いた文字コードは無視されます。HTMLをUTF-8に移行したのにサーバー設定がShift_JISのまま、といった場合は全ページが文字化けします。

Shift_JISのサイトは危険ですか?

Shift_JISやEUC-JPでも、正しく宣言して一貫して使えば問題ありません。危険なのは宣言が無い状態や、実体と宣言が食い違う状態です。新規構築ではUTF-8を推奨します。

X-Content-Type-Options: nosniff を設定していれば十分ですか?

nosniffは「宣言されたタイプを尊重せよ」という指示なので、Content-Type自体が無ければ守るべき宣言がありません。Content-Typeとnosniffは両方そろって初めて意味を持ちます。

charsetが無いとセキュリティAIチェッカーではどう判定されますか?

テキスト系(text/*、application/xml、application/*+xml)なのにcharsetが無い場合と、Content-Type自体が無い場合は「△ 要確認」(重み2×0.45)です。HTMLではmeta宣言で実害が抑えられていることが多いため、×ではなく△としています。

JavaScriptファイルのContent-Typeは何にすればよいですか?

text/javascript; charset=UTF-8 が適切です。RFC 9239により application/javascript は廃止扱いになっています。

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

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

参考資料