第2回 サニタイズ どこかで誰かが やっている? - セキュリティ講座
がる先生のセキュリティ講座
Lecutures on PHP
第2回 サニタイズ どこかで誰かが やっている? (その1)

今回は、俗にサニタイズとか無毒化とか呼称される「お作法」について学びます。
一応「セキュアなコーディング」というお話にはなるのですが、個人的にはセキュリティ云々というよりは「躾」とか「お作法」に属する知識だと思ってます。
いずれにしても、この躾が出来ていないと結果的に「セキュリティ上の危険を発生させる」のは間違いのない事実です。 ではまず「躾が必要な」状況を確認してみましょう。
特別な記号?
プログラムの中には特別な意味を持つ文字列とか記号とかいうものが、存在します。
昔々のお話をいたしますと、9999とかが特別な意味を持っていた、なんてことも、COBOLなどでは散見されたものですが。
最近を基準に考えると、英数字が「何か特別な意味を持つ」事は割合に稀です。
しかし一方で「特定の記号」が特別な意味を持つことは、これはあちこちでよくある状況です。
例えばHTMLにおいて「<」「>」は特別な意味を持ちますし、SQL文で「;」とか「'」はやはり特別な意味を持ちます。CSVですと「,」と改行コードが特別な意味を持ちますし、UNIX系でのディレクトリ名だと「../」、URIの後ろに付与するパラメタだと「=」や「&」、メールアドレスですと「@」が、それぞれ特別な意味を持ちます。
もちろん上述以外にも、色々な状況で、色々な「特別な意味を持つ記号や文字列」があります。
こういった特別な意味をもつ文字を「メタ文字」と呼称しますので覚えておきましょう。
メタ文字は、データそのものではなく、なにか別の意味を持って記述されます。
普通のデータとして使いたい
…で終われば話は楽なのですが。
実際には「メタ文字として用いられている特別な記号を、メタ文字としてではなく普通の文字(というか記号)として使いたい」シーンというものが、やはり存在します。
HTMLで「<」や「>」を「ただの文字として出力したい」事もあるでしょうし、SQLで、データとして「;」や「'」を扱いたいシーンもあるでしょう。CSVで「,」をデータとして用いたい、というお話はよく耳にしますし、URIのパラメタとして、値に「=」や「&」が入ることもないわけではありません。
たとえばHTMLに出力したい文章が「HTMLでリンクを張りたいときは、<A href="リンク先のURI">と記述します」といった内容になる時とか、ありませんか?

では、そんな時にはどんなことが現場で行われているのでしょうか。
一つ目は「メタ文字を普通のデータとして使いたい」という要望に対して「メタ文字を普通のデータとして使っちゃダメ」と言ってしまう手法です。HTMLにおいて半角の「<>」は認めず、SQLで用いる文字列の中から「;」や「'」は排除し、CSVのデータに「,」が入ることを認めない。
誰かに何かを言われたら「仕様です」で切り捨ててしまう。 手っ取り早くはあるのですが、禁止事項がどんどん増えていくと、非常に使いにくいシステムになってしまいます。
では使いにくいシステムにしない別手法は、存在するのでしょうか?
「メタ文字を普通のデータとして使いたい」という要望に対して「こうやればメタ文字を普通のデータとして使っていいよ」という手法は存在するのでしょうか?
答えは、Yes。
まずはそれぞれ個別に、その手段について把握してみましょう。
エスケープ処理
HTMLにおいて、もっとも厄介なのは「<」「>」の二つ。何はともあれ、この二つを「特別な意味を持つことなく」表示できる手法が必要になります。
具体的には、「<」「>」を「<」とか「>」とかに変換してあげることで、「<」「>」を「普通の文字として」正しく表示することが出来るようになります。
これは、HTMLが「"<"と書いてあるときは<と出力する」「">"と書いてあるときは>と出力する」というルールを持っているからです(これを実体参照と呼称します)。

SQLにおける最も顕著なメタ文字は「'」と「;」。この二つは、データ全体をシングルクォートで囲い、囲われたデータの中でその手前に「\」をつけてあげることで問題なく扱えるようになります(正しくは、「'」は「''」と記述するのですが、実際には「\'」でいけるDBMSも多々存在します)。
つまり、例えば Let's Go; というデータであれば 'Let\'s Go\;'(または、より正しくは'Let''s Go\;')としてあげると、問題なく取り扱うことが可能になります。
CSVにおいて、データに「,」を入れる場合、データをダブルクォートで囲うことで、データの中に「,」があっても正しく認識します。
例をあげますと、1つのレコードとして「テスト,データ」という文字列(「,」を含む文字列です)を扱いたい場合、"テスト,データ"とすることで、データの中に「,」があっても問題なく取り扱えるようになります。
このように、メタ文字にはそれを「通常の文字として扱うための方法」が必ず別途存在します。
見る角度を少しかえて上述を言い換えますと。
「データを扱うときには、その出力先に応じた処理を、データ(文字列)に対して行ってあげる」必要があります。
こういった処理を、一般的に「エスケープ処理」と呼称します。
エスケープ処理はいつどんな風に?
もうちょっと実装寄りの話をしてみましょう。
先に結論から書いてしまいますと、エスケープ処理は必ず
- 使う直前に
- 用途に沿った処理方法で
- 常に
行う必要があります。
理由を簡単に解説してみましょう。
入力のタイミングだと「用途に沿った処理」が出来ませんし、「二重にエスケープした」などの厄介なことも、起き得る可能性があります。

また、HTMLに用いるべき文字列をSQL用のエスケープ処理をしても無意味です。HTMLで用いる文字列は「HTML用の」エスケープ処理が必要なのは、言うまでもありません。
そうしてエスケープ処理を「常に」行っておけば「つい漏れてしまった」「仕様変更によって今まで問題なかったはずの部分で漏れが出てしまった」などということもありません。
ですので「使う直前に」「用途に合わせて」「常に」エスケープ処理をする癖をつけておけば、二重に行うこともなく、忘れることもなく、用途に沿わないエスケープで間違えることも(多分)ありません。
ちなみに、こういったエスケープ処理は「関数/クラス」にまとめておくと便利です。PHPの場合はすでにそういった関数が多々用意されていますので、それらをつかうとよいでしょう。
HTML用のエスケープ処理の場合はhtmlspecialcharsまたはhtmlentitiesを用いるとよいでしょう。
どちらかというとhtmlentitiesのほうがお勧めで、双方の関数ともに、第二引数にENT_QUOTESを設定するのは必須になります。また、可能な限り、内部処理の文字コードを第三引数で明示的に指定しておくほうがよいでしょう。
SQL文の場合はaddslashes、またはそれぞれのDBMSごとに用意された専用のエスケープ用関数を用います。たとえばMySQLであればmysql_real_escape_string(mysql_escape_stringは現在非推奨です)、PostgreSQLであれば文字列はpg_escape_string、bytea型ならpg_escape_byteaなどです。
ここでもう一つだけ、ヒント…というか、注意点を。bytea型
magic_quotes_gpc、というものがあります。php.iniファイルの設定項目の一つで、意味は「'(シングルクオート)、" (ダブルクオート)、\(バックスラッシュ) 、NULL には全て自動的に バックスラッシュでエスケープ処理が行われる」になります。
もうお気づきの方もいるかとは思いますが。「エスケープ処理は使う直前に用途に合わせて」という原則から外れています。
ですので、筆者はこれを「Offにする」ことを強くお勧めいたします。
ただ、環境上やむをえないケースというのも少なからずあるので。どうしてもphp.iniを変更できないなどの場合、エスケープされるものとされないものがある($_GET、$_POST、$_COOKIEの中の値はすべてエスケープされます)ことに留意した上で、適切な処理を行う必要があります。

具体的には
- もしmagic_quotes_gpcがOnになっていた場合(get_magic_quotes_gpc関数で判断できます)
- 「やむをえない場当たり的対応であることを理解しつつ」、FORMから入ってくるすべての値(より具体的には$_GET、$_POST、$_COOKIEのそれぞれの値)に対してstripslashes関数で「エスケープ処理を一旦無効化する」
という処理を加えましょう。
これによって「使う直前に必要なエスケープ処理」という原則を貫くことができます。
筆者は、$_GET、$_POST、$_COOKIEの値を直接は触らず、すべて「ラッパークラス(cgi_requestクラス)」経由で値を取得するようにしています。
そうすると、コーダーは意識しなくとも「magic_quotes_gpcによる無駄なエスケープがされていない値」を常に取得することが可能です。
ちなみにラッパークラスというのは「あるデータや処理を包み込んでおく(ラッピングする)」クラスです。cgi_requestクラスの場合、「$_GET、$_POST、$_COOKIEのそれぞれのハッシュ配列からの情報取得をラッピングしたクラス」になります。
- 1
- 2





ページのトップへ


kende様のご指摘通り、三項演算子を使用する際には、コードの複雑度などを考慮する必要がありますね。書きやすさと共に可読性も追求したいところですね。