#597 SC午後問題解説 2025秋-問1:SaaS型プロジェクト管理とXSS・CSRF対策の要点整理
近年の情報処理安全確保支援士試験(SC)では、Webアプリケーションの実務的な設計ミスを突いた問題が増えています。2025年午後Ⅰ問1では、SaaS型のプロジェクト管理サービス「Sサービス」を題材に、格納型XSS・CSRFトークン悪用・CSP・アップロードファイル設計といった、典型的かつ実践的な論点がコンパクトに詰め込まれていました。
本記事では、この2025年午後問1のシナリオをたどりながら、「何が問題だったのか」「なぜ脆弱性が成立したのか」「開発者はどのように設計・実装すべきだったのか」を、初学者の方にも分かるように整理します。単なる解答解説ではなく、「試験対策として覚えておくべきポイント」 もまとめているので、復習用・横展開(他年度問題への応用)用のメモとしても活用していただける内容になっています。
SC 2025 午後問1 学習レポート
― XSS・CSRF・CSP・アップロードファイル設計 ―
1. 問題の全体像と出題テーマ
(1) シナリオの概要
H社が提供する Sサービス(コンサル会社向けSaaS)が舞台。
企業は「プロジェクト」を登録し、その中で
スレッドによる情報共有
タスク管理(進捗・期限管理)
を行う。
利用者のロール:
管理者:会社内すべてのプロジェクトを操作可能
一般利用者:自分が担当するプロジェクトのみ閲覧・操作可
ある日、Sサービスを使う B社 からの問い合わせ:
一般利用者である元従業員ZのPCに、
本来アクセスできない別プロジェクトのファイルが残っていた。
→ 調査の結果、
格納型XSS + CSRFトークン悪用による権限昇格攻撃 が行われていたことが判明、という問題。
(2) Sサービスの主な3機能
ロール管理機能
画面:/management/role
ロール変更処理:/management/roleset
CSRF対策として csrf_token を用いる。
スレッド投稿・ファイルアップロード機能
許可拡張子:.docx, .xlsx, .pptx, .pdf, .jpg, .gif, .png など
サーバ保存名:ファイル識別子.拡張子(例:F1234567890.xlsx)
アクセスURL:/files/F1234567890.xlsx
プロジェクト進捗管理機能
「未完了かつ期限超過」のタスクを一覧表示。
表示例(本来あるべき姿):
<ul class="delayed-task">
<li>タスクA(締切日過ぎ1日)</li>
<li>タスクB(締切日過ぎ4日)</li>
<li>タスクC(締切日過ぎ5日)</li>
</ul>
2. 技術要素の整理
2.1 XSS(クロスサイトスクリプティング)
■ 定義
本来「ただのテキスト」として扱うべきユーザ入力が、
ブラウザで JavaScriptとして実行されてしまう攻撃。
■ 代表的な種類
反射型XSS(Reflected XSS)
攻撃コードがURLパラメータなどに埋め込まれ、そのレスポンスで「その場で反射」して実行される。
攻撃URLを踏んだ人だけが被害者。
格納型XSS(Stored XSS) ← 今回の問題
攻撃コードが一度サーバに保存され、
そのページを閲覧した人すべてのブラウザで実行される。例:掲示板投稿、コメント欄、プロフィール、タスク名など。
■ 今回のXSSの形
Zがタスク名として
個人タスク<script src="/files/F1234567890.xlsx"></script> を登録。プロジェクト進捗画面のHTMLに、エスケープされずにそのまま出力される:
<li>個人タスク<script src="/files/F1234567890.xlsx"></script>(締切日過ぎ5日)</li>B社管理者が画面を開くと、
<script src="..."> をブラウザが解釈して
/files/F1234567890.xlsx を スクリプトとして読み込み&実行。
→ これが 格納型XSS の典型パターン。
2.2 HTMLとエスケープ処理
■ HTMLの基本要素
<タグ> … 意味を持つマーク。例:<h1>, <div>, <ul>, <li>, <script>
属性 … タグに付く追加情報。例:class="...", src="...", id="..."
■ 危険な文字とエスケープ
HTML中で特別な意味を持つ文字:
文字 意味例 エスケープ後
< タグの開始 <
> タグの終了 >
& 特殊文字の開始 &
" 属性値の囲み "
' 属性値の囲み(場合) ' など
出力時エスケープの考え方:
ユーザ入力を HTML に埋め込む瞬間に、
これら特別な文字をエスケープして
「命令」ではなく、ただの文字として表示する。
■ 今回の問題の本質
本来:
<li>個人タスク<script src="/files/F1234567890.xlsx"></script>(締切日過ぎ5日)</li>
のように表示されるべきだった。実際:
<li>個人タスク<script src="/files/F1234567890.xlsx"></script>(締切日過ぎ5日)</li>
となってしまった。
→ プロジェクト進捗画面で、タスク名の出力時エスケープがされていなかった ことが根本原因。
2.3 アップロードファイルとレスポンスヘッダ
■ 拡張子チェックの限界
.xlsx だからといって、中身が本当にExcelとは限らない。
攻撃者は
拡張子:.xlsx
中身:<script>...</script> のようなJSコード
のファイルをアップロードできる。
→ 拡張子だけを信頼する設計は危険。
■ アップロードファイルに関する主なヘッダ
Content-Type
「これは何の種類のデータか」を示す。
例:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet(xlsx)
X-Content-Type-Options: nosniff
ブラウザに対し「Content-Type をちゃんと信じて、勝手に推測しないで」と指示。
ファイルをスクリプトとして誤解されるリスクを下げる。
Content-Disposition: attachment; filename="..."
「このレスポンスはダウンロード(保存)前提のファイルとして扱ってほしい」という指示。
画面内でそのまま実行されるのを避ける。
■ アップロードファイル対策の方向性(要点)
ユーザアップロードファイルは「実行」させない
ダウンロード専用(attachment)にする。
正しい Content-Type と nosniff を付ける。
/files ディレクトリからのスクリプト読み込みを CSP などで禁止する。
2.4 CSRFと csrf_token
■ CSRFとは
ログイン中のユーザのブラウザから、
別サイトを経由して「勝手にリクエスト」を送らせる攻撃。
例:悪意あるサイトを開いただけで、銀行振込リクエストが送られる…など。
■ csrf_token の役割
サーバが発行する、推測困難なランダム文字列。
フォームHTMLに hidden で埋め込む。
フォーム送信時に一緒に送信。
サーバ側で「正しいトークンが付いているか」を検証することで、
外部サイトからの偽リクエスト を拒否できる。
■ 今回の問題での使われ方
/management/role 画面のHTML内に csrf_token を hidden で埋め込み。
/management/roleset へのPOST時に csrf_token を送信。
本来は、ロール変更機能をCSRFから守るための仕組み。
■ XSSによる突破
攻撃用JS(F1234567890.xlsx の中身)は:
fetch("/management/role") でロール管理画面HTMLを取得。
正規表現で csrf_token の値をHTMLから抜き出す。
そのトークンを使って fetch("/management/roleset", { method: "POST", body: ...}) を実行し、
Z(U00331001)を管理者に昇格。
→ CSRFトークン自体は正しく実装されていたが、XSSによって「内側から」盗まれて悪用された。
覚えるべきポイント:
CSRFトークンは「外部サイトからの攻撃」には強いが、
XSSがあると「内部のスクリプト」から盗まれて無力化される。
2.5 CSP(Content Security Policy)
■ 概要
「どこからどんなリソース(スクリプト、画像など)を読み込んでよいか」を
ブラウザに指示するセキュリティポリシー。
設定はレスポンスヘッダ Content-Security-Policy で行う。
■ 今回の設定
Content-Security-Policy: default-src 'self';
意味:
基本的に、同一オリジン('self')からのリソースのみ読み込みOK。
外部ドメインからのスクリプトや画像は読み込み不可。
script-src の指定がなければ、script も default-src を継承 → 同一オリジンのスクリプトはOK。
■ なぜ防げなかったか?
攻撃スクリプトのURL /files/F1234567890.xlsx は 同じオリジン(self)。
そのため、CSP的には 許可対象 になってしまっていた。
外部ドメインからの攻撃は防げるが、自サイト内のアップロード領域 /files を悪用された 形。
より良い対策イメージ:
script-src '/static/js/' のように、
スクリプトを読み込むディレクトリを制限。/files などのアップロード領域からの script 読み込みは禁止。
2.6 X-Frame-Options と iframe
■ iframe とは
ページの中に「別ページをはめ込む枠」を作るHTML要素:
<iframe src="https://example.com"></iframe>YouTubeの埋め込み、地図などでよく使われる。
■ X-Frame-Options: SAMEORIGIN
X-Frame-Options: SAMEORIGIN
意味:
「同じオリジンのページからの iframe であれば、このページを表示してよいが、
他ドメインからの iframe の中には表示させない」。
→ クリックジャッキング(他サイトの透明iframeに重要画面を埋め込まれ、誤クリックさせられる攻撃)対策。
この問題の本筋ではないが、レスポンスヘッダ理解として押さえておく。
3. 攻撃フローの整理(時系列)
Z(一般利用者)が、スレッド投稿機能から
中身がJavaScriptの「偽Excelファイル」F1234567890.xlsx をアップロード。同じZがタスク管理画面から、新しいタスクを登録:
タスク名:個人タスク<script src="/files/F1234567890.xlsx"></script>
タスクが「未完了 & 締切超過」になったタイミングで、
プロジェクト進捗管理画面にそのタスクが表示される状況を作る。B社管理者がプロジェクト進捗管理画面にアクセス。
サーバはタスク名をエスケープしないままHTMLに埋め込み、
<li>個人タスク<script src="/files/F1234567890.xlsx"></script>...</li> を返す。B社管理者のブラウザがこのHTMLを解釈し、
<script src="/files/F1234567890.xlsx"></script> に従って攻撃JSを読み込み実行。攻撃JSは /management/role にアクセスし、HTMLから csrf_token を盗む。
盗んだトークンを使って /management/roleset にPOSTし、Z本人のロールを「管理者」に変更。
管理者権限を得たZは、本来見えないプロジェクトのファイルをダウンロードできるようになり、
そのファイルがZのPCに残っていた。
4. 防御策の整理
4.1 アプリケーションコード側
出力時エスケープ(最重要)
HTMLにユーザ入力を埋め込むときは、必ずエスケープ。
テンプレートエンジンの自動エスケープ機能を利用し、解除しない。
入力バリデーション
タスク名に明らかに不自然な文字列(タグ)を含めないなど、
ただし「バリデーションだけでは不十分」で、必ず出力時エスケープとセット。
HTMLサニタイズ
どうしても一部のHTMLを許可したい場合(Wiki等)は、
安全なタグだけ残し、それ以外を削除するライブラリを使う。
4.2 アップロードファイルの設計
拡張子だけを信頼しない。
ダウンロード専用として扱う:
Content-Disposition: attachment
正しい Content-Type の付与
X-Content-Type-Options: nosniff
可能なら
Web公開ディレクトリ外に保存し、ダウンロード用API経由で提供。
CSP/サーバ設定で、アップロード領域からのscript読み込みを禁止。
4.3 レスポンスヘッダ・CSP
Content-Security-Policy を適切に設定:
script-src でスクリプトの読み込み元を制限
default-src 'self' だけに頼らない
その他ヘッダによる防御・被害軽減:
X-Frame-Options: SAMEORIGIN(クリックジャッキング対策)
Strict-Transport-Security(HTTPS強制)
Set-Cookie の Secure, HttpOnly など
4.4 開発プロセス(試験で問われやすい)
自動診断ツール(DAST)だけに頼らず:
SAST(静的解析)
セキュリティ視点のコードレビュー
特にチェックすべき観点:
「ユーザ入力 → HTML出力」の経路
アップロードファイルの取り扱い
認証・認可・ロール変更など重要機能
5. 「記憶しておくべき」要点リスト(試験用)
◆ 用語と概念
反射型XSS
URLなどに仕込んだスクリプトがレスポンスでその場で反射して実行される。
格納型XSS(Stored XSS)
攻撃コードがサーバに保存され、ページ閲覧時に実行される。
掲示板・コメント・プロフィール・タスク名などで起きがち。
CSRFトークン(csrf_token)
ランダム値でフォーム毎/ユーザ毎に発行。
「本物の画面から送られたリクエストか」を判断する仕組み。
XSSがあると盗まれて悪用される点を必ず覚える。
CSP(Content-Security-Policy)
default-src 'self' : 同一オリジンのみ許可。
script-src でスクリプトの読み込み元を制御。
アップロード領域をscript-srcの対象に入れない設計が重要。
iframe / X-Frame-Options: SAMEORIGIN
iframe:ページ内に別ページを埋め込む枠。
SAMEORIGIN:同じオリジンからのframeだけ許可 → クリックジャッキング対策。
◆ レスポンスヘッダ(最低限覚えておきたいもの)
Content-Type: text/html; charset=UTF-8
データ種別と文字コード。
Cache-Control: no-store
機密情報ページのキャッシュ禁止。
Strict-Transport-Security: max-age=...; includeSubDomains
HSTS。有効期間中は必ずHTTPSでアクセス。
Set-Cookie: ...; Secure; HttpOnly
Secure:HTTPS通信時のみ送信。
HttpOnly:JavaScriptからのアクセス禁止(Cookie窃取対策)。
X-Content-Type-Options: nosniff
ブラウザにタイプ推測(スニッフィング)禁止を指示。
Content-Disposition: attachment; filename="..."
ダウンロード専用ファイルとして扱わせる。
Content-Security-Policy: default-src 'self';
同オリジン以外のリソース読み込みを禁止。
◆ 典型的な出題・解答パターン
「拡張子チェックだけでは不十分な理由」
→ 中身が任意のスクリプトでも、その拡張子を付けてアップロードできるため。
「CSRFトークンで防げないケース」
→ XSSでトークンが盗まれ、正規のトークン付きで不正リクエストを送られる場合。
「XSS対策として必須の処理」
→ 出力時エスケープ(HTMLエスケープ)。
さらに CSP や入力バリデーションで補強。
「アップロードファイルに対する安全な扱い」
→ Web画面で直接実行されないようにする(attachment、MIMEタイプ、nosniff、/filesとscript-srcの分離など)。
このレポートをざっと読み返すだけでも、
XSSの種類
HTMLエスケープの重要性
csrf_tokenとXSSの関係
頻出ヘッダの役割
が一気に復習できるはずです。
