がるの健忘録 このページをアンテナに追加 RSSフィード

2018-06-03

[]フレームワークで「使われてる関数&クラス&メソッド」簡単に調査してみた

すんげぇ斜めに調査。

一旦の対象は、cakePHP、Laravel、CodeIgniter、Slimの4種。

ちと前に「その当時の最新版」をインストールした環境が残ってたので、一旦はそれつかって簡単に確認。


「全フレームワークで使われている関数&クラス」と「各フレームワークの使用量上位」。

調査には https://github.com/gallu/little-tools/blob/master/php_used_function_analysis.php 使いました。

やり方はおおむね


php php_used_function_analysis.php laravel_test/vendor/ > laravel

php php_used_function_analysis.php CodeIgniter-3.1.0/system/ > codeigniter

php php_used_function_analysis.php cake_test/vendor/ > cake

php php_used_function_analysis.php slim_test/vendor/ > slim

こんな感じ。

大体、各フレームワークのvendor配下のブツをまるっと調査対象。


細かい分析とかバージョンの変更とか種類の追加とかは後日やるとして、今回は雑感と生データだけ。


「全体で使われてる子たち」を見ての雑感

ReflectionClass、ReflectionObject あるなぁ、と。この辺は「フレームワークならでは」なのかしらん??

class_existsとかちょっと下がってfile_exists、interface_existsとかproperty_existsとか同上。

is系の型チェックも各種取り揃え。

mt_randも興味深く。

version_compareもあるよねぇ的な。

extension_loadedも結構ちゃんとチェックしてるのねぇ。

usleepへぇ。

__destructが「全部のフレームワークにとりあえず存在している」のも興味深く。いやおいちゃん的には好みな方向なのですが。

register_shutdown_functionもあるかぁやっぱり。

clearstatcacheが「全部のフレームワーク」で使ってるのか興味深い。

memory_get_usageとかあるやっぱり気になるんだねぇ。

ctype_digitとかctype_alphaとかctype_alnumとか、おいちゃんは好みだけど、へぇへぇへぇ。

is_uploaded_file & move_uploaded_file も地味に健在w


フレームワークは………まぁ「コード量」が違うから色々とあるなぁ、的な。


全域で使われてる子

class method__construct
classException
functionsprintf
functioncount
functionsubstr
functionis_array
functionpreg_match
functionstrlen
classDateTime
functionstr_replace
functionarray_merge
functionstrpos
functionimplode
classRuntimeException
functionis_null
functionis_string
functionin_array
functionexplode
classstdClass
functionfunction_exists
functionfunc_get_args
functionget_class
functiontrim
functionstrtolower
classReflectionClass
functionclass_exists
functionmethod_exists
functionarray_map
functiondirname
functionpreg_replace
functionarray_keys
functionarray_key_exists
functionmt_rand
functionis_object
functionfile_get_contents
functionstr_repeat
functioncall_user_func
functiontime
functionarray_pop
functionarray_shift
functioncall_user_func_array
classPDO
functioncurrent
functionarray_values
functionfile_exists
functionrewind
functionfopen
classDateTimeZone
functionreset
functiondefined
functionstream_get_contents
functiontrigger_error
functionunlink
functionkey
functionord
functionis_callable
functionrtrim
functionrealpath
functionversion_compare
functionstrtotime
functionarray_replace
functionserialize
functionfclose
functionunserialize
functionis_dir
functionchr
class method__call
functionis_bool
functionsort
functionini_get
functionis_numeric
functionflush
functiongetenv
functionfwrite
functionis_int
functionvar_export
functioncopy
functionpreg_split
functionpreg_quote
functionarray_unique
functionbase64_encode
functionarray_slice
functionis_resource
functionstrtoupper
functionarray_unshift
functionfread
functiondate
functionmicrotime
functionintval
classerror
classBadMethodCallException
functionerror_reporting
functionpreg_replace_callback
functionextension_loaded
functionfile
functionset_error_handler
functiongettype
functionfunc_num_args
functionstrtr
functionob_start
functionjoin
functionend
functionis_file
classReflectionObject
functionucfirst
functionstrrpos
functionltrim
functionmkdir
functionhexdec
classUnexpectedValueException
functionfilter_var
functionarray_flip
functionarray_diff
functioninterface_exists
functionini_set
class method__get
functionbasename
functionpreg_match_all
functionarray_reverse
functionhash
functiondefine
functionmb_strlen
functionusleep
functionbin2hex
functionrename
functionheader
functionis_scalar
functionexec
functionhtmlspecialchars
functionglob
functionmb_substr
functionmd5
functionsubstr_count
functiontouch
functionproperty_exists
functionis_readable
functionis_subclass_of
functionstripos
functionround
functionis_float
functionrmdir
functionpathinfo
functionksort
classDateInterval
functionescapeshellarg
functionfloor
functionfeof
class method__destruct
functionerror_get_last
functionuniqid
functionfunc_get_arg
functionhttp_build_query
functiondate_default_timezone_get
functiondebug_backtrace
functionarray_search
functionconstant
functioneach
functionis_writable
functionchmod
functionarray_intersect_key
functionmb_convert_encoding
functionsubstr_replace
functionflock
functionextract
functionrawurlencode
class method__isset
functionob_get_level
functionfgets
functionget_resource_type
functioniconv
functionbase64_decode
functionshell_exec
functionregister_shutdown_function
class method__set
functionob_end_clean
functionvsprintf
classdirectory
functionset_exception_handler
functionarray_intersect
functionabs
functionceil
functionprint_r
functionparse_url
functionstrncmp
functionftruncate
functionaddcslashes
functionarray_sum
functionarray_walk
functionparse_str
functionmb_strtolower
functiongmdate
functionclearstatcache
functionmb_strpos
functionwordwrap
functionarray_replace_recursive
functionmemory_get_usage
functionget_object_vars
functionstrcasecmp
functionstrstr
functionctype_digit
functionerror_log
functionstream_set_timeout
functionctype_alpha
functionstr_ireplace
functionob_get_contents
functionarray_change_key_case
functionmail
functionctype_alnum
functionstrcspn
functionis_uploaded_file
functionmove_uploaded_file

cakePHP 3.5.5

functionvar_dump67683
functionsprintf1518
class method__construct1142
classException1108
functionsubstr918
functioncount797
functionis_array698
functionstrlen664
classRuntimeException648
functionFile617
functionstrpos604
functionpreg_match595
functionimplode570
classInvalidArgumentException568
functionstr_replace524
functionin_array498
functionstrtolower423
functionexplode392
functionis_string380
functiontrim375
functionstr_repeat372
functionarray_merge360
functiondirname259
functionpreg_replace238
functionarray_map227
functionarray_keys213
functionrealpath201
functionfile_put_contents195
functionend195
functionfile_get_contents188
functionis_dir185
functionfile_exists184
functionrtrim183
functionarray_pop173
functionfunction_exists160
functionarray_filter158
functiondefined156
functionarray_key_exists153
functionget_class153
functionclass_exists146
functiondefine145
classLogicException140
functionrewind140
classError137
functioncurrent137
functiongetenv136
classPDO134
functiontrigger_error133
functionHash129
functionstrtoupper128
functionLink127
functiontime125
functionmax122
functionis_numeric120
functionis_object118
functionLog114
functionkey105
functionarray_values104
functiongetType103
functionextract102
functionmkdir102
functionputenv101

CodeIgniter 3.1.0

functioncount281
functionis_array243
functionfunction_exists207
functiondefined187
functionstr_replace130
functionpreg_match124
class method__construct119
functionstrlen118
functionstrpos117
functionsubstr109
functionin_array103
functionimplode99
functionpreg_replace89
functionstrtolower88
functionfile_exists87
functiontrim81
functionstrtoupper72
functionsprintf52
functionpack47
functionclass_exists47
functionexplode46
functionis_string46
functionarray_keys44
functiondate44
functionis_int41
functionis_object37
functiontime36
functionrtrim36
functionis_bool31
functionmd530
classstdClass29
functionord28
functionsscanf26
functionis_numeric26
functionis_resource26
functionarray_merge26
functionheader23
functionversion_compare23
classdirectory22
functiontrigger_error22
functionpreg_match_all21
functionctype_digit21
functionmb_strlen21
functionstripos21
functionmethod_exists21
functionpreg_quote21
functionhtmlspecialchars20
classerror20
functionis_dir20
functionini_get20

Laravel Framework 5.5.20

class method__construct1816
classException1480
functionsprintf1299
functioncount785
functionsubstr701
classInvalidArgumentException651
functionis_array494
functionpreg_match474
functionstrlen457
classDateTime447
functionstr_replace436
functionAssert422
functionarray_merge421
functionstrpos414
functionimplode412
classRuntimeException409
classGenerator393
functionis_null363
functionis_string361
functionin_array326
functionexplode320
classstdClass318
functionfunction_exists315
functionfunc_get_args305
classClosure304
functionget_class302
functiontrim278
functionstrtolower239
classReflectionClass233
classReflection231
functionclass_exists214
functionmethod_exists212
classgenerator205
classLogicException192
functionarray_map191
functiondirname189
class method__toString183
functionpreg_replace177
functionarray_keys174
functionarray_key_exists160
functionmt_rand153
functionis_object152
functionfile_get_contents151
classError151
functionstr_repeat148
functioncall_user_func147
functiontime141
classERROR136
functionrange135
functionarray_pop132
functionarray_shift130
functioncall_user_func_array128
classPDO125
functioncurrent123
functionjson_encode119
functionarray_values118
functionfile_exists114
functionrewind114
functionFile114
functionfopen113
classDateTimeZone113
functionarray_filter111
functionreset108
functiondefined108
classDOMDocument103

Slim 3.?.?*1

functionsprintf499
class method__construct450
classException332
classstdClass258
functioncount236
classReflection215
classInvalidArgumentException182
classDateTime178
functionis_string163
functionsubstr162
functioncall_user_func_array161
functionfunc_get_args161
functionget_class147
classRuntimeException134
functionAssert126
functionis_array122
classERROR120
functionstrpos118
functionpreg_match118
classReflectionClass111
functionstrlen106
functionimplode94
functionarray_merge92
classDateTimeZone85
functionis_object83
class method__toString82
functionstr_replace79
functiondirname79
functionclass_exists76
functiontrim74
functionrange73
functionjson_encode67
functionin_array67
functionis_bool59
functionexplode57
functionmethod_exists56
functionfile_get_contents51
functioncurrent49
functionarray_keys48
functiontrigger_error47
functionarray_shift44
functionfopen44
classDOMDocument37
functionlog37
classArrayObject37
functiondefined37
functionstrtolower37
classSplObjectStorage36
classReflectionMethod36
functioncall_user_func35
functionversion_compare35
functionfread34
functionarray_map34
functionis_resource33
functionfile_exists33
functionfseek32
functionvar_export32
classgenerator31
functionfunction_exists31
functiondate31
functionkey31
functionis_null30
functionjson_decode30

*1:バージョン確認の仕方が不明でした……

2014-01-03

[]「冗長な言葉で」Webアプリケーションセキュリティの一部を書いてみる

ん…年末の、escapeとかvalidateとかsanitizeとかprepared-statementとかの一連のお話をみて、ちょうど良い機会なので「自分なりに」かみ砕いたものを書いてみようかなぁ、っと。

単純に「自分の中で整理したい」のに糅てて加えて「間違いがあったらきっと突っ込んでもらえるに違いない」という淡いというよりは甘い期待、込みでw

推敲はしていますが、初稿は「夜中の寝不足」で書いているので。ノリその他、微妙におかしいのは気にしない。


基本、所謂injection系とかを想定しています。…XSSってinjection系に入れてもよいのかしらん? って思うのですが、その辺を「いかんモンぶっ込み系」って括ると似たようなモンなんじゃないかなぁ、っと。


大まか、プログラムなんで「入力 → 処理 → 出力」という流れでの説明になります。

ただ、出力先が、コマンドラインだったりmailだったり(mailはコマンドラインなのかTCP/IP通信なのか、ってのは置いといて)、HTMLだったりRDBだったりしますので、その辺も込み込みで。

また、セッションハイジャックとかその辺については言及をしていませんのでご注意のほどを。

だから「Webアプリケーションセキュリティの一部」です。


ちなみに、流れ的には、Webアプリケーション「じゃないプログラム」のセキュリティにも「同じような事が言える部分は多い」ので、そんなつもりでれっつらごー。


まずは入力を受け取ります。


受け取った入力ですが。

多くの場合において「業務的にあずましくない値」は、速やかに突っぱねる事が推奨されています。

具体的には、例えば日付を入力するところで「12月32日」とか入ってくるのは、それが「入力者の強い思いと願いと願望と懇願」であったとしても、受け入れるには少々難のある値になります。

或いは、購入アイテム数をマイナス値にする行為は「一昨日来やがれすっとこどっこい」と突き返す強い心が必要になります*1


というわけで、まず入力値を確認して「業務的に明らかにあずましくない値」が含まれていないかどうかチェックをしましょう。

含まれている場合「お手数ですが、入力値または目またはあんたの脳みそをご確認の上、再度ご依頼くださいませ」と、丁重に突き返す必要があります。


大まか、この「業務的にあずましくない値が含まれているかどうかのチェック」を、validate、なんていう用語で呼称するケースが多いようです。


若干余談。

validateの一つの手法として「予め想定されている値一覧、の中に入っているモノしか認めない」という、大変に狭量なやり口が存在します。

昔は「デフォルト不許可」なんていう言い方で、特にネットワーク系の指定(ルータの指定なんかが顕著ですな)で出てきたのですが、ソフト系だと「ホワイトリスト(許可リスト)」なんて言い方をする事が多いですね。

やり口としては大変に狭量ですが、それだけに「がっちりガードしやすい」特徴があるので、お勧めです。

この真逆にあるのが「デフォルト許可」或いは「ブラックリスト(不許可リスト)」。リストに漏れがあるとてきめんでひどい事になるので、こちらはあまりお勧めしません。


また、このvalidateですが。

値によっては「突っぱねてよいかどうかが難しい」ところも、色々とございます。

年齢に「数字じゃない文字ぶっこんできたら」NG出せますが*2、例えばそれが5とか8とか、或いは100とか110とか122とか入力された場合に「…ん…多分嘘くさいけど…」とは思いつつ、入力者が「絶対にジャンヌカルマンさんではない」と否定しきれる根拠も薄いわけで。そこを無碍に突っぱねてしまって「本当によいのか?」というあたりには色々と疑問が出てくるところです。

郵便番号も割合に面倒ですね。「フォーマット的に正しい」と「存在する」との間には、日本海溝くらいには深い溝が存在します。

あとはemailが面倒ですかね。「RFC的にあり得ない」docomoの正式なアドレスとか滅びればいいのに。ついでに「フォーマット的にvalidである」と「到達する」にも溝がありますが、到達するemailアドレスが「ご本人の所有管理下にあるかどうか」ってのもまた、考慮すべきポイントになります*3


閑話休題


まぁ「どこまでを許容するか」或いは「どこからを許容しないか」ってのは、色々と(システム起因以外で)難しい問題もあるので。

この辺は割合「ビジネスに直結するケース」なので、お客様と存分に話し合うのがよろしいかと思われます。

んでまぁ「ビジネス的に明らかに許容できねぇだろヲイ」てな値は、鉄の意志を持って突っぱねてください。


さて。

このタイミングで時々「後々、攻撃に使われやすい文字(列)」を対象に「(攻撃にならないような文字(列)に)置換したり削除したり」といった事を、推奨したりしなかったり、という状況があるようです。

人によって用語の用い方が異なるのですが、「フィルタリング」「サニタイズ」なんて用語を見かける事が多いですかね。

気持ちがわからんではないのですが、個人的には、このタイミングで「「後々、攻撃に使われやすい文字(列)」を対象に「置換したり削除したり」」は、業務要件に変な縛りを入れる事が増えるのを散見しているのもあるので、あんまりお勧めしません。

そも「後々、攻撃に使われやすい文字(列)」の定義が曖昧ですし。出力シーンによって変わるから。


処理はまぁ適宜。


んでもって、出力。

XSSにしても*-Injectionにしても、発生しているのは「データだけを記述すべきところに、データではない別の意味ある言葉が混じる」のが「あずましくないよ」ってのが、一番の根っこ。

この部分を踏まえた上で、対策の話に進みませう。


例えばHTMLで。INPUTエレメントのvalueアトリビュートに、「デフォルトの値」だけを入れたいのに、ちょっときかせちゃいけない気を利かせて「"><script> alert('test');</script><"」なんてな値を入れると。

<input value="値">ってなのが展開されて<input value=""><script> alert('test');</script><"">となってなんか「値だけを入れたかったのに外にはみ出して勝手にHTMLの続きの指示書いちゃってるよをい」てな事になり、あんまりあずましくない。

あぁちなみに<>は本当は半角ですが、色々と面倒なんで全角で書いてます。


例えばSQLで。検索したいので「SELECT * FROM hoge_tbl WHERE id = '検索する値';」なんて感じにしたいところで、検索したい値に「'; UPDATE items SET price=1;--」なんて入れてみると。

お手軽に「SELECT * FROM hoge_tbl WHERE id = ''; UPDATE items SET price=1;--';」となって、例えばitemsが商品のお値段でここがECサイトだったりすると、割と心温まる大売り出しとなるわけでございます*4


前者のケースにおいて"と<>のあたりが「放置すると悪さする子」であり、後者においては'や;が悪い子ちゃんです(実際にはもっと色々あるので注意)。


ちなみに「ちゃんとTCP/IPでしゃべる」SMTP通信で。

mailの本文のうち、偶然「1文字目に.(ドット)、2文字目に改行」がはいると、それ以降のmail本文が「切り捨てられる」という楽しい事象も存在しえます。

…過去に本当に実務でそれに一度遭遇したときは、気づくのに少々時間がかかったものです。


ここで重要であり必要な根っこは「値は"値"として扱われること」。

言い換えると「値が、お外にはみ出さないようにすること」が尤も需要にして肝要です。


さて。

古来から割とよく使われている「お外にはみ出さない」為の方法として「ある特定の文字(列)を、別の文字(列)に置換する」方法がとられます。

これをエスケープ処理とか言いますが…ちょいとこの辺をかみ砕いてみましょう。


まず「エスケープシーケンス」ってもんがあります。

これは「通常では書き表しにくいものを表現するため」の記法です。一応念のために書くと「書けない」訳ではないですバイナリエディタ使えばなんだって書けるしねぇ。

ただまぁ普通そこまでやる人はあんまりいないので、普通にテキストエディタとかで書きやすいようにするために、\nとか\rとか\0とか、っていうのを使って記述するわけですね。

この辺を「エスケープシーケンス」って言います。

JISのエスケープシーケンス」とかを調べるのも面白いかもしれないけど、ちょいと余談だねぇ。


ちなみに「JISのエスケープシーケンス」と同じような手法を、昔の、J-PHONEとかVodafoneとかの絵文字が、やっていた記憶がある。

んで、なんか特定の条件で「エスケープシーケンスだけが」はずされちゃって、奇妙な文字化けをする…みたいな事象が過去にあったんだよねぇ、たしか。


閑話休題


んで。

上述で書いた「JISのエスケープシーケンス」なんかもそうなのですが、多くの「出力先」には、そこの世界特有の「こいつはただの文字ぢゃねぇ、特別なヤツなんだ!」っていう、依怙贔屓されている文字ってのがあります。

HTMLだと<>が割と典型ですし、SQLだと'や;が典型です。URIですと=や&や?は特別な子ですし、コマンドラインでも'や&や;は特権階級です。CSV(カンマ区切りの表記)だと、カンマと改行がハイカーストな存在ですね。


ただ、ここで問題があります。

HTMLで、<>をただの文字として出力したいこともありましょう。

SQL文としてぶち込むデータの中に'や;を使いたいシーンもございましょう。

URIに付けたいパラメタで&や?を含めたいと駄々をこねられる事もありましょう。

コマンドライン経由で送りたいmailで、何かの理由で&が付けられてしまう事もありましょう。

CSVのデータの中に、文字としてのカンマを使いたいとか願う子羊もおりましょう。


こーゆー状況で尤も手っ取り早いのは「使えません仕様です」で切り捨てる事です。「システム上の制限です」とか言い張るのは、非常に楽で、それ故に今でも「使われる時は使われる」手法ですね。

…実際、CSVで「データにカンマを含める事は出来ません。それは仕様です」と言われて、色々と難儀した記憶がよみがえってきたりします。

ただまぁそうやってあらゆる面倒を「仕様です」で切り捨てると、いつか「あなたは不必要です。それが仕様です」とか言われてしまいそうなので、もうちょっとまじめに真剣に真摯に、その辺は考えていきたいところです。


というわけで「どうにか、特権階級な子を、一般庶民に引きずり下ろす手法」というのが考慮されてくるわけです。

HTMLにおいて、<は<、>は>と書くと、表示的には<だったり>だったりする「庶民のための、表示用文字」を得る事ができます。

SQLにおいて、データとしての'は、''という風に「二つ重ねて」あげると大人しくなります。MySQLだと\'って書き方もありですね。

URLにおいて、=や&や?は、%3Dや%26や%3Fって書くと無問題です。

CSVでは「ダブルクォートの中のカンマ」は、一般庶民である、という風習が根付いております。


というわけで、上述のように「特権階級から引きずり下ろす悲報じゃなくて秘法」があるわけで、これを一般的に「エスケープ処理」なんていう言い方をする事が多いです。

んでもって、この秘法が「値が、お外にはみ出さないようにすること」の実際の手法として様々な盤面でありとあらゆる万能手法として…使われるかと思いきや、必ずしもそうにあらず。


んと。

上述の「特権階級な子を、一般庶民に引きずり下ろす手法」ですが、どれも中途半端です。

ちなみにコマンドラインのほうを言及していないのは「コマンドラインが、shなのかbashなのかzshなのかcshなのかkshなのかtcshなのか」でも変わる上に色々面倒にすぎるので、言及さえ避けておりますこの根性なし < 俺。


もう一つ。各秘法は「その局面においてのみ有効」です。

つまり「対HTML用のエスケープの秘法」はSQLに対しては威力が薄く、「URL特化型エスケープの秘法」は、コマンドラインについては無力にも等しい存在です。

んでもって「あらゆる局面で有効な、万能型のエスケープの秘法」は、存在しません。

ちゃんと出力先を見極めて、あまたの秘法から「ただ一つの、有効な術」を選び出さなければならないのです。


さらに。

各秘法について「用意された、完全なる関数とかクラスとか」があればよいのですが、その「用意された子」ですら「実装上のミス」の可能性があるので。

「値が、お外にはみ出さないようにすること」の実際の手法は、必ずしも「エスケープ処理1択」にはならないしなれないんですね。


現在。

HTMLについては、PHPだとhtmlspecialchars関数によるエスケープがよく用いられています。

コマンドラインは「可能な限り避けろ」という、心温まるお話をよく耳にします。どうしても不可避な場合、escapeshellarg関数を用いるか、或いは可能なら「環境変数標準入力つかってデータを渡す」といいよねぇ、的なお話がちらほら。

CSVは割と簡単にエスケープ可能なので、自作でみなさんやってらっしゃる気がする。…関数あったら書き直しますんで教えてくださいませ*5


んでもってSQLですが。各APIともにエスケープ関数(メソッド)は用意されているのですが。

昨今では「原理的に安全」な、静的プリペアドステートメントを用いる、というのがおナウなヤングの基本です。

(静的)プリペアドステートメントは、若干動作に「限定的」な所があるのですが。具体的には、テーブル名やカラム名などを「プレースホルダ化する」事が出来ないのですが。

通常、ンなことはしなくても基本的な業務アプリやゲームアプリは一通り作成可能なので、あんまり考慮しなくてもよいと思われます。

あなたがもし、フレームワーク作るとか、phpMyAdminを超える、似たようなサービスを作るとか、そーゆーレベルに達してから「プリペアドだけじゃ要件満たせないんだよなぁ」と悩んで、エスケープ処理用の関数なりメソッドなりに手を出してください。


とまぁ、こんな形で入力された値から「明らかにあずましくないものを、どちらかというとビジネス的制約基準で突っぱね」つつ「出力先の都合にあわせて、適切に丁寧に、檻に閉じ込めたまま値として扱う」ってのが、このあたりの基本になります。


…なんか偉い事脱線しまくってる気もしますが。

なんかあったら、お気軽に突っ込んでくださりませ。

*1:…それをやらないばかりに、課金系のゲーム内通貨を、簡単なパラメタ操作で「増加できる」とかいう愛くるしいバグを、本当に作りやがったカス会社が居たのよマジで orz

*2:普段は16進数表記とかで年齢を呼称する事もありますが、プログラムを組む時は10進法に限定したいなぁ、と考えるのが恐らく一般的です

*3:某○○○○とかいうMLサービス、とっとと滅びればいいのに

*4SQLの複数文は最近ガードされている的な話もありますが、されていないケースもあるようなので

*5:処理的には「ダブルクォートを二重にした」うえで「全体をダブるクォートで囲む」でよいので、そんなに困らんとです

2012-03-02

[]「美しくないコード」の方向性

気付いたらまた追記などしたりするんだろうなぁ、とか思いつつ。

とりあえず現状気付いた「美しくないっていうかぶっちゃけ醜い」と感じるソースの傾向について。

もちろん、プログラムの美醜については色々な見解見地があると思うのですが。


基本的には…

・当たり前のことが出来ていない

という、割とみもふたもない一点に尽きるように思います。

もちろん「新しい技術」を得ることは大切なのですが、同じくらいに「得たはずの、当たり前の知識」がちゃんと実践できているかを、もう一度確認してみませんか?


諸悪莫作衆善奉行。

よいことをしなされ。わるいことはしなさんな。

五歳の童でも知っているが、八十の翁にしてなお行い難し。


そんな内容に気付いてもらえたらうれしいなぁ、ってのが、大まかな趣旨でやんす。


コメントがない:超深刻

大抵「ろくでもないソース」です B-p

警戒レベルを2〜3段跳ね上げましょう。

コメントの要不要に関するおいちゃんの見解は「コメントは補足説明 http://d.hatena.ne.jp/gallu/20101116/p1 」をご覧ください。

「コメントは不要」という意見に対して、おいちゃんは半歩たりとて引きません。


コメントがろくでもない:深刻

よく笑い話に出てくる「変数iに1を足す」の類。

「何のためにコメントを書くのか」を小一時間ほど。

まぁ「とりあえずコメントは書いてある」ので、その質をまともにすると、大分とよくなるのですが。


共通化ができていない:超深刻

「共通化が必要か不要か」についての議論は最早「火を見るより明らか」かとは思うのですが*1

実際に業務に入ると…まぁ「出来ない」「やれてない」「中途半端」 orz

どれくらい綺麗に「共通化できるか」は、割と本気で「技術レベルの一つの指標」になるので。この部分は、特に丁寧に突っ込んでいきたいか、と。

過去に「複数回」拝見した、栄誉ある状況を、思い出せる限りにおいて列挙してみましょう。

ちなみに、大抵のケースにおいて、テキストエディタの「コピー&ペースト」機能が大活躍しています。…いやまじで、コピペ禁止テキストエディタとか作るとよいんじゃなかろうか? って、真剣に思うことが多々あります。


ifの連打:超深刻

似たような処理のifが縦に並ぶとかいうケースが、まぁちらほらと。

if ($param1 == 'test') {
  $なんちゃら->メソッド('test');
} else 
if ($param1 == 'hoge') {
  $なんちゃら->メソッド('hoge');
} else 
if ($param1 == 'foo') {
  $なんちゃら->メソッド('foo');
} else 
if ($param1 == 'bar') {
  $なんちゃら->メソッド('bar');
} else 
if ($param1 == 'hogebar') {
  $なんちゃら->メソッド('hogebar');
} else 
if ($param1 == 'boo') {
  $なんちゃら->メソッド('boo');
}

…なにしたいんだろう?

せめて最後にelse句があればかろうじて「許可された文字列かそれ以外かを判定したいのかなぁ」と、わずかながらに思わなくもないけど。


上述なら素直に

  $なんちゃら->メソッド($param1);

で終わり。

これで問題がある場合(大抵は引数の存在チェックをしたい)、


  // 許可対象の一覧
  // XXX ここにおくかconfigにおくか別の場所におくかは、それもまた一つ重要な議論
  $checked_string_array = array(
    'test' => 1,
    'hoge' => 1,
    'foo' => 1,
    'bar' => 1,
    'hogebar' => 1,
    'boo' => 1,
    );

  // ほげほげな処理を行う
  if (true === isset($checked_string_array[$param1])) {
    $なんちゃら->メソッド($param1);
  } else {
    // XXX 元プログラムには存在していないエラー処理
  }

hashのkeyのほうにチェックしたい文字列を持ってきてるのは、検索しやすいようにするため。値にもっちゃうと線形探索なロジックになるので、おいちゃん的にお好みではない。


同一処理の連打:超深刻

似たようなもん。

実際にあって、まぁなんていうかどんびいたものでございます。


  $printData['test'] = htmlspecialchars($_GET['test']);
  $printData['hoge'] = htmlspecialchars($_GET['hoge']);
  $printData['foo'] = htmlspecialchars($_GET['foo']);

面倒だから3行しか書いてないけど、実際には十数行あったと記憶してる orz

これは(これも)loopを使って小さくまとめましょう。

  // えすけぷ処理対象の一覧
  // XXX ここにおくかconfigにおくか別の場所におくかは、それもまた一つ重要な議論
  $target_names = array(
    'test',
    'hoge',
    'foo',
  );

  // ぶん回して一気に処理
  foreach($target_names as $name) {
    // えすけーぷする
    $printData[$name] = htmlspecialchars($_GET[$name]);
  }

せめてこの程度は、最低でも。

上述の「頭痛が痛かった」実例。

$this->get_conv()->monoDic("customer_id", security::sanitize_html($customer_data["customer_id"]));
$this->get_conv()->monoDic("name1", security::sanitize_html($customer_data["name1"]));
$this->get_conv()->monoDic("name2", security::sanitize_html($customer_data["name2"]));
$this->get_conv()->monoDic("name1_kana", security::sanitize_html($customer_data["name1_kana"]));
$this->get_conv()->monoDic("name2_kana", security::sanitize_html($customer_data["name2_kana"]));
$this->get_conv()->monoDic("email", security::sanitize_html($customer_data["email"]));
$this->get_conv()->monoDic("uid", security::sanitize_html($customer_data["uid"]));
$this->get_conv()->monoDic("sex_name", security::sanitize_html($sex_name));
$this->get_conv()->monoDic("birthday", security::sanitize_html($customer_data["birthday"]));
$this->get_conv()->monoDic("birthday_year", security::sanitize_html($birthday_year));
$this->get_conv()->monoDic("birthday_month", security::sanitize_html($birthday_month));
$this->get_conv()->monoDic("birthday_day", security::sanitize_html($birthday_day));
$this->get_conv()->monoDic("zip1", security::sanitize_html($customer_data["zip1"]));
$this->get_conv()->monoDic("zip2", security::sanitize_html($customer_data["zip2"]));
$this->get_conv()->monoDic("prefecture_name", security::sanitize_html($prefecture_name));
$this->get_conv()->monoDic("city", security::sanitize_html($customer_data["city"]));
$this->get_conv()->monoDic("address", security::sanitize_html($customer_data["address"]));
$this->get_conv()->monoDic("building", security::sanitize_html($customer_data["building"]));
$this->get_conv()->monoDic("point", security::sanitize_html($customer_data["point"]));
$this->get_conv()->monoDic("temp_point", security::sanitize_html($customer_data["temp_point"]));
$this->get_conv()->monoDic("memo", security::sanitize_html($customer_data["memo"]));

こんなのもあった。…いやまぁこっちはそもそもとしてあちこち論外なのだけれども。…せめて「書かざるを得ない」んなら、loopにしようよ、と orz

if($_POST["a"]){$a = $_POST["a"];}elseif($_REQUEST["a"]){$a = $_REQUEST["a"];}elseif($_GET["a"]){$a = $_GET["a"];}
if($_POST["b"]){$b = $_POST["b"];}elseif($_REQUEST["b"]){$b = $_REQUEST["b"];}elseif($_GET["b"]){$b = $_GET["b"];}
if($_POST["c"]){$c = $_POST["c"];}elseif($_REQUEST["c"]){$c = $_REQUEST["c"];}elseif($_GET["c"]){$c = $_GET["c"];}
if($_POST["d"]){$d = $_POST["d"];}elseif($_REQUEST["d"]){$d = $_REQUEST["d"];}elseif($_GET["d"]){$d = $_GET["d"];}
if($_POST["e"]){$e = $_POST["e"];}elseif($_REQUEST["e"]){$e = $_REQUEST["e"];}elseif($_GET["e"]){$e = $_GET["e"];}
if($_POST["f"]){$f = $_POST["f"];}elseif($_REQUEST["f"]){$f = $_REQUEST["f"];}elseif($_GET["f"]){$f = $_GET["f"];}
if($_POST["g"]){$g = $_POST["g"];}elseif($_REQUEST["g"]){$g = $_REQUEST["g"];}elseif($_GET["g"]){$g = $_GET["g"];}
if($_POST["h"]){$h = $_POST["h"];}elseif($_REQUEST["h"]){$h = $_REQUEST["h"];}elseif($_GET["h"]){$h = $_GET["h"];}
if($_POST["i"]){$i = $_POST["i"];}elseif($_REQUEST["i"]){$i = $_REQUEST["i"];}elseif($_GET["i"]){$i = $_GET["i"];}
if($_POST["j"]){$j = $_POST["j"];}elseif($_REQUEST["j"]){$j = $_REQUEST["j"];}elseif($_GET["j"]){$j = $_GET["j"];}
if($_POST["k"]){$k = $_POST["k"];}elseif($_REQUEST["k"]){$k = $_REQUEST["k"];}elseif($_GET["k"]){$k = $_GET["k"];}
if($_POST["l"]){$l = $_POST["l"];}elseif($_REQUEST["l"]){$l = $_REQUEST["l"];}elseif($_GET["l"]){$l = $_GET["l"];}
if($_POST["m"]){$m = $_POST["m"];}elseif($_REQUEST["m"]){$m = $_REQUEST["m"];}elseif($_GET["m"]){$m = $_GET["m"];}
if($_POST["n"]){$n = $_POST["n"];}elseif($_REQUEST["n"]){$n = $_REQUEST["n"];}elseif($_GET["n"]){$n = $_GET["n"];}
if($_POST["o"]){$o = $_POST["o"];}elseif($_REQUEST["o"]){$o = $_REQUEST["o"];}elseif($_GET["o"]){$o = $_GET["o"];}
if($_POST["p"]){$p = $_POST["p"];}elseif($_REQUEST["p"]){$p = $_REQUEST["p"];}elseif($_GET["p"]){$p = $_GET["p"];}
if($_POST["q"]){$q = $_POST["q"];}elseif($_REQUEST["q"]){$q = $_REQUEST["q"];}elseif($_GET["q"]){$q = $_GET["q"];}
if($_POST["r"]){$r = $_POST["r"];}elseif($_REQUEST["r"]){$r = $_REQUEST["r"];}elseif($_GET["r"]){$r = $_GET["r"];}
if($_POST["s"]){$s = $_POST["s"];}elseif($_REQUEST["s"]){$s = $_REQUEST["s"];}elseif($_GET["s"]){$s = $_GET["s"];}
if($_POST["t"]){$t = $_POST["t"];}elseif($_REQUEST["t"]){$t = $_REQUEST["t"];}elseif($_GET["t"]){$t = $_GET["t"];}
if($_POST["u"]){$u = $_POST["u"];}elseif($_REQUEST["u"]){$u = $_REQUEST["u"];}elseif($_GET["u"]){$u = $_GET["u"];}
if($_POST["v"]){$v = $_POST["v"];}elseif($_REQUEST["v"]){$v = $_REQUEST["v"];}elseif($_GET["v"]){$v = $_GET["v"];}
if($_POST["w"]){$w = $_POST["w"];}elseif($_REQUEST["w"]){$w = $_REQUEST["w"];}elseif($_GET["w"]){$w = $_GET["w"];}
if($_POST["x"]){$x = $_POST["x"];}elseif($_REQUEST["x"]){$x = $_REQUEST["x"];}elseif($_GET["x"]){$x = $_GET["x"];}
if($_POST["y"]){$y = $_POST["y"];}elseif($_REQUEST["y"]){$y = $_REQUEST["y"];}elseif($_GET["y"]){$y = $_GET["y"];}
if($_POST["z"]){$z = $_POST["z"];}elseif($_REQUEST["z"]){$z = $_REQUEST["z"];}elseif($_GET["z"]){$z = $_GET["z"];}

ifでエラー処理の書式の連打:行飛びだから意外と気付かない:深刻

これは少し趣が違う。

エラー系処理なんかでよく見るかなぁ。


if(とあるエラー) {
  header('Location: ./error1.html');
  exit;
}
if(とあるエラー2) {
  header('Location: ./error2.html');
  exit;
}

if(別のエラー) {
  header('Location: ./error_hogehoge.html');
  exit;
}

if(びっくりするエラー) {
  header('Location: ./error_pamyupamyu.html');
  exit;
}

間に挟まる処理にもよるんだけど、とりあえず「エラーで突っ返すlocation」は一箇所のほうが、後々楽なので、まとめましょう。

他の処理がはさからない(或いはエラーに因らない処理しかはさまらない)場合は、こんな書式。


$error_location = '';
if(とあるエラー) {
  $error_location = './error1.html';
}
if(とあるエラー2) {
  $error_location = './error2.html';
}

if(別のエラー) {
  $error_location = './error_hogehoge.html';
}

if(びっくりするエラー) {
  $error_location = './error_pamyupamyu.html';
}

// エラー判定
if ('' !== $error_location) {
  // エラーらしいので終了しておく
  header('Location :' . $error_location);
  // XXX 使ってる状況FWにも因るけど、できるだけexitぢゃなくてreturnで処理をもどしましょう
  return ;
}

こんな感じ。

もし「細かくエラー判定をして成功への階段をいっぽづつ踏みしめる」系の場合、例外処理使うのが多分早い。

ソースは省略。リクエストがあったら捏造します(笑


「インタフェース/処理のわずかな違い」をコピペで量産:超深刻

以下の2つのプログラムには7つ未満の差異があります。どこでしょう?

protected function dice($num)
{
  //
  $ret = array();
  $ret['total'] = 0;
  $ret['malice'] = 0;
  $ret['dice'] = array();
  //
  for($i = 0; $i < $num; $i ++) {
    $d = mt_rand(1,6);
    $ret['total'] += $d;
    $ret['dice'][] = $d;
    //
    if (6 === $d) {
      $ret['malice'] ++;
    }
  }
  //
  return $ret;
}


protected function dice2($num)
{
  //
  $ret = array();
  $ret['total'] = 0;
  $ret['malice'] = 0;
  $ret['dice'] = array();
  //
  for($i = 0; $i < $num; $i ++) {
    $d = mt_rand(1,6);
    $ret['total'] += $d;
    $ret['dice'][] = $d;
    //
    if (1 === $d) {
      $ret['malice'] ++;
    }
  }
  //
  return $ret;
}

メソッド名が違うことを含めても、ぶっちゃけると「2箇所しか違いません」。

こーゆーことをやられると、なんていうか割と本気で「闇よりもなお暗きもの 夜よりもなお深き存在 混沌の海よ たゆたいしもの 金色なりし闇の王...」とかって詠唱を始めたくなってしまう。

上述は「判定値が一つ違う」のですが。他にも「引数が1つ違うだけ」とか「SQL1文だけ少し違う」とかもうちょっと酷いと「見にいくテーブルが違うけどカラム名は同じだからつまり"テーブル名が違うだけ"」とか。

ンなことをやるからソースが膨れ上がり、わけわかめになるのです。

メソッド名を含めて、考え直しましょう。上述はおおむね「dice2が後から追加された」のが見え見えなので。dice2が追加されるタイミングで、脊髄反射で「共通化」できるようにしておきましょう。


「オレカコイイ!」実装:深刻〜超深刻

出来るようになった楽しい気持ちはわかるのですが、業務ではやめておきましょう。

習作でやる分には全然OKだと思うんだけど。


複雑な正規表現

まぁそのまんま。「どこまでを複雑とするか」って議論はあるのだけれども、そこも含めて意識をしておくとベターです。


ネストしまくった三項演算子

割と悲鳴があがるケース(笑

三項演算子を使うのはよいのですが、ネストは控えるようにしたほうがいいかと思います。



流儀ドン無視:超深刻

別段、現場の流儀が「常に正しい」とはまったく思わないですし、場合によっては「神速の突っ込みをいれないとまずい」流儀も多々存在します。

でも「流儀に対して意見を申し述べる」のと「流儀を聞きもせずに横紙破りに破る/無視する」のは、別次元のお話です。


「俺の使いたいツールはすべて使えるべきだ!」:深刻

書いといてなんですが、結構難しい問題です(苦笑

ただとりあえず。現場さんに伺ったときは、まずすべてのツールについて「確認」は取ったほうがよいと思います。

使ってから「これ使ってるんで」で事後承諾にすると、あまりいい顔をされません。オプトアウトが嫌われるのは、spamに限らないものです。


現場のコーディング規約破り:超深刻

割と論外。

もちろんコーディング規約にもピンキリあるので、場合によっては突っ込むことも大切ですが。

聞きもせずに「オレオレコーディング」すると、大抵、とても嫌な顔をされます。

特にインデント。これを違えると、とても敏感に反応する人が多いので、要注意です。


フレームワークの流儀無視:超深刻

フレームワークは、大抵「導入理由」がありますし、またそこから、その現場が「どんなメリットを享受したいのか」という背景があって、その上に「その現場で、そのフレームワークを使う流儀」というのが成り立ちます。

なので、最低限「FW使用にあたって、に注意を払うべきか」という「意図」の部分は、当然ながらちゃんと聞かないといけませんし、不明点は質問をしなきゃいけません。

特に危ないのが「前の現場でも使ってた(或いはプライベートで使ってる)」フレームワーク

「同じフレームワーク」だから「同じような意図だろう」と妄想することは、極めて危険です。大きく外して「場外ファール」になって「リカバリ不能」な状態を作ってしまうその前に、きちんとコミュニケーションをとりましょう。


…実例かいてたら案外とでかくなった(笑

また報告発見があったら適宜追加します〜。

*1:とはいえ。実際に現場で「共通化/関数化は難しいからやらないでください」とかいう暴言があったとかいう話がじゃぁ皆無かというと、そんなことはなくて… orz

2010-02-02

[]Smarty用のセキュリティラッパ

ざっくりと雑なルーチンですが。

    /*
     * HTMLタグを直接assignで扱いたい時用
     * 
     * @access protected
     * @param  no
     * @return no
     */ 
    public function assign_unsecure_raw($tpl_var, $value = null)
    {
        //
        parent::assign($tpl_var, $value);
    }

    /*
     * assignのセキュアラッパー
     * 
     */ 
    public function assign($tpl_var, $value = null)
    {
        //
        parent::assign($this->_esc($tpl_var), $this->_esc($value));
    }
    /*
     * 内部処理用関数
     */
    protected function _esc($str){
      if(is_array($str)){
        $ret = array();
        foreach($str as $key => $val) {
          $ret[$key] = $this->_esc($val);
        }
        return $ret;
      }else{
        if (is_null($str)) {
          return null;
        }
        // else
        return htmlspecialchars($str,ENT_QUOTES);
      }
    }

2008-10-29

[][]入力時無毒化の弊害への考察:突っ込み

あえて。あえて「サニタイズ」書かずに「入力時無毒化」と書いてみます*1

先日もまぁドカドカと叩いたのですが、ちと色々と考察を。


元ネタとして、キーワード「入力時 サニタイズ」でググって色々なサイトを見に行ってます。

# つまらん事に「入力時のサニタイズは駄目よん」なPageが多くてネタ探しに苦労しましたw


端的に書くと。入力時に処理してしまうと

  • 適切なエスケープが出来ない(HTML出力用のエスケープ処理とSQL出力用のエスケープ処理とコマンドライン出力用のエスケープ処理はそれぞれ「全く異なる」処理…ってのは今更だよね?)
  • 二重エスケープ、エスケープ漏れ(後述)などの発生で面倒な配慮が必要

っていう問題があります。


とりあえず各記事に改めて丁重に突っ込みw

http://www.hotfix.jp/archives/word/2004/word04-17.html

一般的にサニタイジングは、入力データのチェック時に行うこと、とされている。

どんな一般よ? *2


http://www.intel.co.jp/jp/business/glossary/27072949.htm

ここも同じ事が書いてある。…コピペ?


http://maglog.jp/mcierror/index.php?module=Article&action=ReaderDetail&article_id=46518

入力の正規化というのは常に必要で、ウェブに限らず普通のWinアプリでも一緒なんだが、こう考えるとわかりやすいだろう。

と書くといかにも正論ちっくなのだが。実際に処理を書けという話をしてちゃんとうなずける処理が書けた状況を見た事がない。

「そんなまか不思議な処理はない」のだから当然なのだが。


http://www.oopstyle.net/doc/57.html

サニタイズとは?

このようなHTMLで意味のある記号をエスケープすること(HTMLを無効化)

えと…HTML出力限定?

HTMLを表示する時、プログラマー、もしくはデザイナーが責任もってサニタイズするべきだと思ってます。

デザイナーがやる仕事なの?

又、サニタイズが必要なのは文字列なので、数値や日付などはサニタイズの意味が無いでしょう。INT型なら(int)でキャストしてしまえばサニタイズの必要はありませんから。

日付だと例えば/とか入るけど。んで「ノーチェックでいいやとか思ったらそこからアタックされた」らどうするんだべさ?


http://slashdot.jp/comments.pl?sid=413123&threshold=-1&commentsort=3&mode=thread&pid=1396392

事件は入力段階で起こるんでないの?

入力しただけではなにも起きない。

入力データが「攻撃者が意図している出力先」に「(攻撃者にとって)適切に」出力された時に事件が起きる。

SQLにJavaScriptのアタック用のタグとかコードとか書いたの入れてもなにもしてくれないでしょ?

HTMLにinsert文とか書いてもDBにinsertは出来ないでしょ?


http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?mode=viewtopic&topic=28217&forum=7&start=16

はい、ただしXSSに対応したサニタイジングとSQLインジェクションに対応したサニタイジング

は別物ですので注意が必要です。通常は、SQLインジェクションへの対応は入力時、XSSへの

対応は出力時に行います。

は? なぜ?

いずれも「出力時に出力のTOPにあわせたエスケープが必要」なだけですが。


http://www1.mahoroba.ne.jp/~mitt/itmemo/webappsecurity/04.htm

1.入力値チェックの強化

 入力値に特殊な文字列が含まれている場合はエラー扱いにする。

2.サニタイジング(sanitizing)(無害化)

 有害となる文字そのものを無害な文字に変換する。

 ・<→&lt;

 ・>→&gt;

 ・&→&amp;

 ・"→&quot;

 ・'→&#39;

………すみませんそろそろ突っ込むの疲れてきました orz


http://blog.webcreativepark.net/2008/10/24-201629.html

foreach($_GET as $key => $value){

$_GET[$key] = htmlspecialchars(htmlspecialchars_decode($value,ENT_QUOTES),ENT_QUOTES);

}

foreach($_POST as $key => $value){

$_POST[$key] = htmlspecialchars(htmlspecialchars_decode($value,ENT_QUOTES),ENT_QUOTES);

}

今気づいたのですが。例えば、出力として

&lt;A href=&quot;test&amp;test&quot;&gt;

と出力したい時。上述ルーチンを通してしまうと、「サンプルコードで一度デコードを行ってからエスケープを行っているのは実体参照化された文字列に更に実体参照化していくと「&amp;amp;amp;」とえらい事になるからです。」という余計な処理(アイデア?)のおかげで、意図する出力にならなかったりします。


おいといて。


別に出力時のエスケープと併用しても問題ないかなと思いますし。

二重エスケープになって思いっきり問題有りすぎると思うのですが如何でしょうか?


番外編

http://www.softek.co.jp/Sec/mod_security3.html

クロスサイトスクリプティング対策

SecFilter "[\"]" deny または SecFilter "[']" deny

SecFilter "[<]" deny

SecFilter "[>]" deny

Webアプリケーションによっては上記の文字列を通常のリクエストに使用することも考えられます。その場合は、制限を緩める必要があるかもしれません。

…えと「その場合は、制限を緩める必要がある」場合どうするのさ?


続きますw

[][]入力時無毒化の弊害への考察:予想事例

とまぁざくざく突っ込みましたが。

んと…予想される面倒事をいくつか。


いち

「入力で<>(半角ね)をNGにしました」

「え〜例えば >>(くどいようだけど半角ね) とか使いたいし通せるようにしてよ」

「(なんでNGなのか前任者から引き継いでないしなぁ)わかりました〜」

終了 orz


えと…この出力情報、先にDBに入れたいんだよなぁ。デコードしてからDBに入れなきゃ。

$hogehoge = $_POST['hogera'];
$hogehoge = htmlspecialchars_decode($hogehoge, ENT_QUOTES);
$sql = printf("insert into a(hogera) values('%s');", mysql_real_escape_string($hogehoge));

で…出力して、っと。

echo $hogehoge;

終了 orz


…どうだろほかにもきっと奇想天外魔境な事例がありそうなのですが…幸か不幸かあまり知らない。

だってそも「なにもやってないソース」の方が多いんですもの orz

*1:んと…このへん微妙なのですが。厳密には、サニタイズはあくまで「無毒化」程度の意味しかないと私は思ってるのですが、大抵のところで「入力時に」と書かれています…なんでだろ? どこかに原典あるのかしらん?

*2:まぁその後に「しかし独立行政法人情報処理推進機構では、サニタイジングのタイミングをHTML生成時のタイミングで行うことを推奨している。」と書かれてるのですが