がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

「冗長な言葉で」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サービス、とっとと滅びればいいのに

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

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