質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

エスケープ処理

エスケープ処理とは、一連の文字や一文字に対して、一定の規則に従って別の意味を適用する処理過程です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

XSS

XSS【クロスサイトスクリプティング】は、 ソフトウェアのセキュリティホールの一つで、Webサイトに脆弱性が あることからその脆弱性を利用し攻撃する手法です。 主に、入力フォームなどから悪意あるスクリプトを挿入し 該当ページを閲覧したブラウザ上でそのスクリプトを実行します。

意見交換

クローズ

8回答

2567閲覧

XSS対策のエスケープ、いつする派?

origa3

総合スコア22

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

エスケープ処理

エスケープ処理とは、一連の文字や一文字に対して、一定の規則に従って別の意味を適用する処理過程です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

XSS

XSS【クロスサイトスクリプティング】は、 ソフトウェアのセキュリティホールの一つで、Webサイトに脆弱性が あることからその脆弱性を利用し攻撃する手法です。 主に、入力フォームなどから悪意あるスクリプトを挿入し 該当ページを閲覧したブラウザ上でそのスクリプトを実行します。

3グッド

7クリップ

投稿2023/09/12 22:03

編集2023/09/12 22:08

3

7

テーマ、知りたいこと

XSS 対策を実装しているのですが、いつエスケープしたらいいのかいろいろなご意見があるようで疑問です。
使用言語は、HTML、CSS、JavaScript、PHP、MySQLです。

背景、状況

掲示板のコメントを例にします。

保存の際は JavaScript の fetch() を使い、PHP の PDO() を介し、MySQL でデータベースに保存しています。

逆に出力は PHP から {"id": 1, "comment": "<p>hello</p>"} のような JSON を echo し、 JavaScript で HTML を生成します。

この流れにあたって XSS 対策のタイミングは下記などいくつか考えられそうです。

タイミング

➀ データベースに保存する際にエスケープした値にしておく

PHP

1<?php 2$data = ["comment" => "<p>hello</p>"]; 3$data["comment"] = htmlspecialchars($data["comment"], ENT_QUOTES, "UTF-8"); // ➀はここでエスケープ 4$stmt = $pdo->prepare("INSERT INTO comment_table (comment) VALUES (:comment)"); 5$stmt->bindParam(":comment", $data["comment"], PDO::PARAM_STR); 6$stmt->execute();
➁ データベースには素のまま保存し、PHP から echo する際にエスケープする

PHP

1$stmt = $pdo->query("SELECT * FROM comment_table WHERE id = 1"); 2$data = $stmt->fetch(PDO::FETCH_ASSOC); // この data は ["id" => 1, "comment" => "<p>hello</p>"] のような値 3$data["comment"] = htmlspecialchars($data["comment"], ENT_QUOTES, "UTF-8"); // ➁はここでエスケープ 4echo json_encode($data);
③ データベースには素のまま保存し、PHP から echo する際も素のまま echo し、JSON を HTML として描画する際に JavaScript でエスケープする

JavaScript

1fetch('getComment.php') 2 .then(response => response.json()) 3 .then(data => { // この data は {"id": 1, "comment": "<p>hello</p>"} のような値 4 addNewComment(data); 5 }) 6 .catch(error => { 7 }); 8 9function addNewComment(data) { 10 const ulElement = document.querySelector('.comment-list'); 11 const liElement = document.createElement('li'); 12 liElement.className = 'comment'; 13 liElement.textContent = escapeHtml(data); // ➂はここでエスケープ 14 ulElement.appendChild(liElement); 15} 16 17function escapeHtml(unsafe) { 18 return unsafe 19 .replace(/&/g, "&amp;") 20 .replace(/</g, "&lt;") 21 .replace(/>/g, "&gt;") 22 .replace(/"/g, "&quot;") 23 .replace(/'/g, "&#039;"); 24}

いつエスケープするのがセオリーというか、安全というか、実装しやすいというか…
一体どうしたらいいのでしょうか?

glyzinieh, 68user, PICCA👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

回答8

#1

maisumakun

総合スコア145184

投稿2023/09/12 22:20

編集2023/09/12 22:21

④ データベースには素のまま保存し、PHP から echo する際も素のまま echo し、JSON を HTML として描画する際に.textContentなどテキストとして描画されるAPIを使う

手作業でエスケープせずに済むならそれに越したことはありません。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#2

origa3

総合スコア22

投稿2023/09/12 23:23

maisumakun様、ありがとうございます。.textContentはそういった使い方のものだったのですね。おかげ様で自前のescapeHtml()などが不要になり、わかりやすく実装できそうです。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#3

68user

総合スコア2005

投稿2023/09/13 02:13

実際にエスケープするべきところで適切なエスケープを行う、です。
同じデータでも、テキストノードで使用する、属性内で使用する、href 内で使用する、<script></script> 内で使用する、それぞれで行うべきエスケープが異なりますので、事前にエスケープはしないことをお勧めします。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#4

otn

総合スコア84571

投稿2023/09/13 03:54

「➀ データベースに保存する際」は、そうしたいと思った気持ちの理由が分からない。強いて推測すると、SQLを自分で組み立てる場合にはSQLの特殊文字をエスケープする必要がありますが、それとの混同か?あるいは、昔、SQLの時でなく入力直後にhtmlspecialcharsしてた人たちがいたので、その流れですかね。

「②PHP から echo する際にエスケープする」はおそらく、PHPで直接HTMLを出力する際にテキスト部分では、echo htmlspecialchars(~~);等とするので、何故そうするかの理解なしに「echo する際にhtmlspecialcharsする」との結果だけ覚えていて、使ったのかと思います。結果だけ記憶するにしても「PHPで直接HTMLを出力する際にはテキスト部分を echo する際にhtmlspecialcharsする」と前提付きで記憶すれば良いのですが。

「一般論として、分担としてどこでやるのが適当か?」ということなら、「最終的にHTMLを作るところ」というのが正しいかと思います。主な理由は、
・それまでの間で色々処理があるor今後処理が入るかも知れない
・エスケープ済みのデータと未エスケープのデータをどれがどうかを把握しておかなければならない。場合によっては「HTMLのテキストに出力するためのエスケープ」以外のエスケープが複合するのもありえるので「このデータはこのエスケープは済みだがこのエスケープはまだ」とか色々大変。

もちろん、「今後処理が入る可能性はゼロ」「把握はちゃんと出来る」ということであれば、その限りでは無いのですが、「もしかしたら処理が入るかもリスク」「把握を間違うかもリスク」を負うだけのメリットが無いでしょうね。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#5

ikedas

総合スコア4337

投稿2023/09/14 02:02

編集2023/09/14 03:58

一般に、エスケープするのは「データをシリアライズするとき」だと考えます。「データを媒体に出力するとき」と言ってもいい。以下説明します。

コンピュータ内では、(比較的高級なプログラミング言語を使用するかぎり) データは構造を持っているけれど特定のフォーマットを持たない抽象的なものとして扱われます。特定のフォーマットが必要になるのは、データがネットワークや外部記憶などの媒体上に置かれるときです。つまり、データを媒体に出力するためにバイトの並びに変換する (シリアライズする) 時点から、媒体からコンピュータに入力するためにバイトの並びをデータに変換する (デシリアライズする) 時点までの間です。

例: DOM木 (データ) をシリアライズすればHTMLテキスト (バイトの並び) になりますし、逆にHTMLテキストをデシリアライズしてDOM木にすることができます。

さて、エスケープ/アンエスケープの処理はこのシリアライズ/デシリアライズ処理の仕様の一部です。データがエスケープされずに媒体上に出力されたり、エスケープされたままのものをコンピュータ内でデータとして扱ったりすると、XSSなどの脆弱性の原因となります。ですから媒体への出力/からの入力とエスケープ/アンエスケープを含むシリアライズ/デシリアライズの処理を常にセットで行うことが望ましいです。ご質問の例でいえばそれぞれ次のようなセットが考えられます。

PHP部分

  • データ (配列や特定のクラス) のJSONテキストへのシリアライズと、ネットワークへの出力。

javaScript部分

  • ネットワークからのJSONテキストの入力と、データへのデシリアライズ。
  • データのHTMLテキストへのシリアライズと、ネットワークへの出力。

常に上のようなセットで入出力の処理をすることで、コンピュータ上のデータ媒体上のバイトの並び、そしてその間のシリアライズ/デシリアライズ処理、といったものがそれぞれ明確に区別され、エスケープ処理の漏れを心配する必要はなくなります。一方、「echoで出力する時点」のような「点」でエスケープの適否を論じる限り、処理漏れへの懸念はなくならないのではないでしょうか。

具体的な方式としては、データをシリアライズするためのメソッドなりユーティリティ関数なりを用意してデータを出力する際は必ずそれを通して得た結果を出力する、といったことをするといいと思います。長い。


[回答後追記] データベースサーバへの問い合わせと結果の取得についても、エスケープ処理が適切に行われないとSQLインジェクションのような脆弱性の原因となりますから、上で述べたのと同様の考慮が必要と言えます。しかしデータベースアクセスではほとんどの場合にクライアントAPIを通じてシリアライズ/デシリアライズ処理が暗黙に行われるため、適切な方法を用いればまず問題にならないです。ご質問の例のようにパラメタ化クエリとバインド変数を使うとか、自前でSQL文を書く代わりにORマッパを使うとか。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#6

origa3

総合スコア22

投稿2023/09/15 01:34

68user様、ありがとうございます。仰る通りでした。よく考えたら .textContent だけで全てまかなえるわけではありませんね。危うくmaisumakun様のご回答を曲解するところでした。

otn様、ありがとうございます。➀➁は➂よりエスケープ箇所が格段に少なく済むためモレずらいのかなと考えました。しかし「最終的にHTMLを作るところ」というのは理由も明確で納得でした。以前まさに処理の過程でエスケープしたか否かを失念し、いちいちconsole.logで確認していた記憶がございます。

ikedas様、ありがとうございます。いつもすごいレベルですね…。抽象論から理解の深度が見てとれます。具体論もそれぞれ納得でした。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#7

shinoharat

総合スコア1676

投稿2023/09/15 06:59

XSS 対策は「出力時」に行うのが鉄則なので、①はお勧めできません。
IPA の「安全なウェブサイトの作り方(改訂第7版)」の「3.5.3 誤った対策」には以下のように記載されています。

クロスサイト・スクリプティングの脆弱性への対策における「入力チェック」は、そもそも漏れが生じやすく、根本的な対策にはなりません。

(引用元: https://www.ipa.go.jp/security/vuln/websecurity/about.html)

--

②と③の比較ですが、他の方の回答にもある通り、HTMLを組み立てる際にエスケープする方が管理しやすいと思います。

なので、僕なら③を選びます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#8

origa3

総合スコア22

投稿2023/09/15 07:06

shinoharat様、ありがとうございます。そのような明確な出典をご提示いただけると大変参考になります。やはり皆様➂みたいですね。私も今後は➂での実装を進めて参ります。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

最新の回答から1ヶ月経過したため この意見交換はクローズされました

意見をやりとりしたい話題がある場合は質問してみましょう!

質問する

関連した質問