このサイトについて

みんなのPython Webアプリ編 - セキュリティホールへの対処

みんなのPython Webアプリ編 - セキュリティホールへの対処

セキュリティホールへの対処

Webアプリケーションを使う上で、起こりうることをすべて想定してプログラムを作れば、原理的にはセキュリティホールはなくなるはずです。しかし、実際にそのようなプログラムを作るのはとても難しいか、ほとんどの場合は不可能といってよいでしょう。

そのため、セキュリティホールを出にくくするような定石をよく知り、活用する、という対策が取られます。セキュリティホールにはいくつかのパターンがあります。そのため、取るべき対策のパターンも限られています。

先ほど例に挙げたようなセキュリティホールが発生する原因として考えられるのは、Webアプリケーションで扱う文字列を適切に扱えていない、ということです。先ほどの申し込みフォームでは、フォームのアトリビュートの中に文字列を埋め込んでいました。アトリビュートの内部には書き込んではいけない文字列があります。ダブルクオーテーション(")や「>」のような文字列は書き込んではいけないことになっています。そのような文字列を直接書き込む代わりに、実体参照文字列と呼ばれる文字列を使って置換します。つ まり、この例で示したセキュリティホールが発生した原因は、そのような「約束事」をよく理解していなかったために起こったと言えるのです。

クロスサイトスクリプティング(XSS)

Webアプリケーションのセキュリティホールとして使われるクロスサイトスクリプティングには2つの意味があるようです。Webブラウザが表示するHTMLに任意の文字列を埋め込めるという不具合自体を指して、広い意味でクロスサイトスクリプティングと呼ぶ場合があります。また、この不具合を利用して悪意のあるスクリプトを埋め込むことを指して、より狭い意味でこの言葉を使うことがあります。

スクリプトを埋め込むとはどういうことでしょうか。たとえば、先ほどのお問い合わせフォームを起動中に、Webブラウザに以下のようなURLを打ち込んでみてください。

http://127.0.0.1:8000/?name=%22/%3E%3Cscript%3Ealert('Hello!')%3C/script%3E

このURLを使うと、HTMLの内部に"/><script>alert('Hello!')</script>という文字列を埋め込むことになります。結果としてHTMLにJavaScriptの実行コードが埋め込まれ、アラート表示のダイアログが表示されたはずです。

図02 アラートボックス

図02 アラートボックス

このような方法を使うと、より攻撃的で悪意のあるコードを埋め込むこともできます。たとえば、JavaScriptを使うとCookieの内容を取得することができます。フォーム認証では認証用のデータがCookieに保存されていることがある、ということを思い出してください。このセキュリティホールを使って、どのようなことが起こるかを想像してみてください。

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

クロスサイトスクリプティングを防ぐために取られる一般的な対策は、まずHTMLのエレメントのアトリビュート値をクオーテーションで囲むということです。また、動的に置き換える文字列をエスケープする、という対策も有効です。動的に埋め込まれるダブルクオーテーション(")や不等号がクロスサイトスクリプティングの引き金になります。これらの文字列を実体参照文字列で置き換えることで、JavaScriptなどが埋め込まれる危険性がほぼなくなります。実体参照文字列とは、HTMLやXMLで、特定の文字列を直接表記する代わりに利用する文字列のことです。たとえば「>」に対応する実体参照文字列は「<」、「"」は「"」というように対応が決まっています。

実体参照への変換は、基本的にHTML文字列に動的な文字列を埋め込む前に1回だけ行います。何回も変換を繰り返すと、実体参照文字列自体に含まれる「&」がさらに実体参照文字列に変換されてしまいます。そうなると、実体参照文字列から元の文字列への変換ができなくなってしまいます。

CGIのような手法でレスポンスを生成している場合は、レスポンスに埋め込む文字列の扱いによく注意する必要があります。必要な文字列を実体参照文字列に変換を行う関数を用意しておき、文字列の動的置き換えをする直前に、1回だけ変換を行うようにします。

クロスサイトスクリプティングとテンプレートエンジン

リクエスト文字列の生成にテンプレートエンジンを使う場合では、テンプレートエンジン自体が実体参照文字列への変換を行うべきです。テンプレートエンジンでは、動的な置換を行う場所に特別な記法で文字列を埋め込みます。テンプレートエンジンがこの記法を使って文字列を置換するときに、自動的に実体参照文字列への変換を行えば、クロスサイトスクリプティングは起こらずに済みます。

ただし、場合によってはHTMLのエレメント(タグ)を含む文字列を無変換で動的に置換したい場合もあります。たとえばウィジェットをテンプレートエンジンに埋め込むときにはHTMLのタグを含んだ文字列を動的に置換します。もしウィジェットの出力するHTMLまで実体参照に変換されてしまったら、ウィジェットのフォームが正しく表示されません。このようなことを避けるため、実体参照への変換を行う置換と、行わない置換の2種類を用意することが多いようです。

既存のテンプレートエンジンを使う際、文字列置換時に実体変換が行われない場合は、扱いに注意が必要です。

ところで、本書で作ったテンプレートエンジン(SimpleTemplate)では、文字列の置き換え時に実体参照への置換を行っていません。置換時に実体参照への変換を行うようにする方法については、後ほど議論することにします。

SQLインジェクション

クロスサイトスクリプティングは、HTMLの文字列としての特性をついたセキュリティホールでした。同じように、SQLの文字列としての特性をついたセキュリティホールがあります。それがSQLインジェクションと呼ばれているセキュリティホールです。

HTMLと同じく、データベースを操作するために利用するSQLも文字列から構成されています。多くのWebアプリケーションでは、プログラムを効率的に作るためにSQL文字列をプログラムで動的に組み立て、データベースとの通信を行っています。たとえば、SQLを使ってデータを検索する条件、フォームに入力されたデータをデータベースに反映するためのSQLなどが動的に作られています。

具体的な例を挙げて説明しましょう。たとえば、Webアプリケーションの利用者のデータがデータベースに登録されているとします。ユーザIDから、名前を調べるためには以下のようなSQLを使います。

:::sql
SELECT name FROM userdata WHERE userid='XXXX';

検索フォームを使って特定のユーザのデータを検索する場合を考えましょう。検索フォームにはユーザIDを入力します。フォームに入力した文字列を、「XXXX」の部分に埋め込んでSQLを作ります。

このとき、フォームに「'OR's'='s」という文字列を入力したらどうなるでしょうか。最終的に以下のようなSQLが生成されることになります。

:::sql
SELECT name FROM userdata WHERE userid='' OR 's'='s';

WHERE句以下は検索条件を指定している部分です。ここにuseridが''か、または's'='s'かという条件が指定されていることになります。この条件はすべてのデータに対して「真」となりますので、結果としてデータベース上にあるすべてのユーザの名前が取り出されることになります。

このように、SQLで使われる区切り文字列などを悪用して、意図されていな命令をデータベースに送ることができるのがSQLインジェクションと呼ばれるセキュリティホールの正体です。この例のように検索条件の変更だけでなく、場合によっては、パスワードのような情報を取り出したり、データを消してしまえることもあります。

SQLインジェクションの対策

たいていのデータベースエンジンは,クエリに埋め込まれる文字列を安全な文字列に置換するプレースホルダと呼ばれる機能を備えています。SQLインジェクションに対するベストプラクティスは,データベースエンジンの機能を使ってクエリを生成することです。動的に埋め込まれる文字列を適切にエスケープする機能をPythonで実装することは不可能ではありません。しかし,完璧なエスケープ処理を実装するのはとても大変です。信頼できる機能が出来合いの機能として存在するのなら,それを使うのが一番の方法と言えます。

たとえば,PythonのDBAPIにはプレースホルダという機能があり,この置換を自動的に行ってくれます。SQL文字列を動的に組み立てるときには、文字列フォーマットの機能などは使わず、極力プレースホルダを使うとよいでしょう。

また、ほとんどの既成のO/Rマッパーでは、動的に埋め込まれる文字列を適切に扱ってくれます。セキュリティに配慮して作られている既成のO/Rマッパーを使うことでも、SQLインジェクションのリスクを最小限度に抑えることができます。

クエリに関連するセキュリティホール

クロスサイトスクリプティングもSQLインジェクションも、対策を怠ると大きな被害をもたらしうるセキュリティホールといえます。ただし、テンプレートエンジンやO/Rマッパーといった開発手法を正しく使えば、適切に対処することができるセキュリティホールです。

その他、Webアプリケーションではクエリに関連してセキュリティホールが発生する場合があります。たとえば、フォームからのPOSTリクエストを受け付ける関数などがあるとします。フォームには性別を選ぶメニューがあり、「男性」、「女性」、「無回答」の3つの項目しかないとしましょう。

フォームでは他の値を選ぶことができないのですから、POSTリクエストを受ける関数では値のチェックが必要ないと思うかもしれません。しかし実際には、GETを使ってメニューにないクエリを作ることができますし、フォームから送信されたリクエストになりすましたリクエストを、Pythonを使って作り上げることもできます。

インターネットに公開し、不特定多数のユーザに使ってもらうことを前提として作るWebアプリケーションでは、クエリの値チェックを特に厳密に行う必要があります。必ずフォームからリクエストが送られてくることを前提にしたコードには、いろいろな危険性が潜んでいる可能性が高いと言えます。値のチェックを手軽に行いたいなら、バリデータのような仕組みを使うとよいでしょう。

2014-09-03 15:00