JavaScript: / の \ によるエスケープのみによるセキュリティ対策は禁止

Security 11月 16, 2013
(Last Updated On: 2018年8月4日)

RFC 4696をもう一度読みなおしてみると/もエスケープ可能文字に定義してありました。JavaScriptのエスケープシークエンスの処理の部分も間違っていたので全面的に書き直します。

RFC 4696(JSON)の定義では

string = quotation-mark *char quotation-mark

char = unescaped /
escape (
%x22 / ; ” quotation mark U+0022
%x5C / ; \ reverse solidus U+005C
%x2F / ; / solidus U+002F
%x62 / ; b backspace U+0008
%x66 / ; f form feed U+000C
%x6E / ; n line feed U+000A
%x72 / ; r carriage return U+000D
%x74 / ; t tab U+0009
%x75 4HEXDIG ) ; uXXXX U+XXXX

escape = %x5C ; \

となっているので / のエスケープは禁止しなくてもOKです。ECMAScriptの文字リテラルの定義は

EscapeSequence ::
CharacterEscapeSequence
0 [lookahead  DecimalDigit]
HexEscapeSequence
UnicodeEscapeSequence

CharacterEscapeSequence ::
SingleEscapeCharacter
NonEscapeCharacter

SingleEscapeCharacter ::
one of ‘ ” \ b f n r t v

NonEscapeCharacter ::
SourceCharacter but not one of EscapeCharacter or LineTerminator

EscapeCharacter ::
SingleEscapeCharacter
DecimalDigit
x
u

となっているいます。多少分かりづらいのですが、エスケープ文字\の後には普通の文字が来てもOKと書いてあります。したがって、/を \/と書くこと自体は仕様通りなので問題ありません。

何故、/ はエスケープが必要ない文字であるにも関わらずエスケープするのでしょうか? Web環境でエスケープする理由はタグを不正に終了させない為です。分り易い属性の場合、例えば

<div class={{sytle_name}}> メッセージ </div>

のようなコードがあった場合に、{{style_name}}がクオートで囲まれていない為、”style /”に変換され/が最後にあると

<div class=style /> メッセージ </div>

のようにタグを終了させる事ができます。これのみで直接攻撃可能になる訳ではありませんが、HTMLの構造が破壊される事が問題です。そもそもダブルクオートで属性値を括ていない事が間違っているのですが、こういった間違いがあっても問題とならないようにするのがフェイルセーフ対策、つまりセキュリティ対策です。

次にJavaScriptの例です。良く見かける”</“を”<\/“に変換するコードは

<script> 
  .... some code ... 
  var val="{{some_value}}"

で{{some_value}}の中身に</script>があると前のスクリプトタグが終了させられてしまいます。タグを作る < > 文字がエスケープされていない場合、簡単にJavaScriptをインジェクションできます。これを防ぐためにタグを終了させてしまう”</”を”<\/“に変えるという対策が利用されています。

参考:

そもそもの問題はタグを不正に閉じられたり作られたりする事が問題なので、< > が誤ってHTMLパーサーに解釈されない対策を行わなければなりません。そのためにはHTMLパーサーでは解釈されず、JavaScriptパーサーのみが解釈するエスケープ方法で変換します。”/”の”\/“エスケープが無効な訳ではありませんが「適切ですか?」と問われた場合にはどう答えるべきでしょうか?

結論としては、\/によるセキュリティ対策は有用ですが、これだけで安全だとお薦めできる対策ではありません。”</“を”<\/“と書き換える対策は本筋ではないと思います。

HTMLの書き方とエスケープ方法が間違っているというそもそも論もありますが、タグを /> で不正終了させないために/\でエスケープしても、一応は役立ちますが違っている感は否めません。”</” を ”<\/” と変換している場合、この変換ではタグ内からタグの不正終了(属性の例)を防ぐこともできません。

間違えなければ良いのですが、特にテンプレートエンジンなどを利用している場合、エスケープ方法が間違っていたり、正しいHTMLになっていない場合はよくあります。

HTMLパーサーで誤解釈されないよう< >のエスケープを行った上で、/ のエスケープを行う事には大賛成です。エスケープする場合、”/”はUnicode(JavaScript/JSON)またはHEX形式(JavaScriptのみ)でエスケープした方が安全です。色々考えましたが、やっぱり / を \でエスケープする手法は禁止とした方が良いか知れませんね。

最初に投稿に間違いがあり、混乱を招いたかも知れません。失礼致しました。

参考:

追記2:
このエントリは良く読まれているようなので、解説不足の部分を補足します。JavaScript文字列をエスケープする場合に重要な事は「HTMLパーサーに解釈されるコンテクストで、HTML要素の一部として解釈されないようにする事」です。JSONのエスケープも参考にしてください。出力のセキュリティ対策は出力先を正しく理解すれば簡単です :)

追記1:
/ を\/とエスケープする事には何とも言えない「もやもやとした違和感」を感じているのですが、その原因は

  • そもそも\/はエスケープ処理でない(\が無視されているだけ)
  • エスケープ処理ではないためHTML特殊文字となる / がそのまま現れてる
  • エスケープ文字を無視する仕様

だと思います。実際には3番目のおかしなエスケープ文字の利用はLL系では結構ゆるいようです。

Ruby

[yohgaki@dev tmp]$ irb
2.0.0p247 :002 > puts "\d\y\z"
dyz
 => nil 
 

Perl

[yohgaki@dev tmp]$ perl
print "\d\k\z"

dkz

Python

[yohgaki@dev tmp]$ python
Python 2.7.5 (default, Oct  8 2013, 12:19:40) 
[GCC 4.8.1 20130603 (Red Hat 4.8.1-1)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print "\d\e\z"
\d\e\z
>>> 

PHP

[yohgaki@dev tmp]$ php -a
Interactive shell

php > echo '\x\y\z';
\x\y\z
php > 

RubyとPerlはJavaScriptに似たような動作をしています。PythonとPHPも似たりよったりで、Ruby/Perl/JavaScriptは無効なエスケープではエスケープ文字を削除する仕様ですが、PythonとPHPはエスケープ文字を出力しています。この仕様も「もやもや」しますが、普通なのかも知れません。

もやもやするのでC/C++でも試してみました。警告は出ますがバイナリは作成されます。JavaScript/Ruby/Perlはこの動作を踏襲しているのかも知れませんね。

[yohgaki@dev tmp]$ cat t.c
#include <stdio .h>

int main() {
	printf("\d\y\z");
	return 0;
}
[yohgaki@dev tmp]$ gcc t.c
t.c: 関数 ‘main’ 内:
t.c:4:9: 警告: 不明なエスケープシーケンス: '\d' [デフォルトで有効]
  printf("\d\y\z");
         ^
t.c:4:9: 警告: 不明なエスケープシーケンス: '\y' [デフォルトで有効]
t.c:4:9: 警告: 不明なエスケープシーケンス: '\z' [デフォルトで有効]
[yohgaki@dev tmp]$ ./a.out
dyz

投稿者: yohgaki