他社および他組織のWebサイトなどへのポートスキャンおよびデータの取得などの行為で得た情報を侵入などに悪用するか、または同じ目的を持つ第三者に提供した時点で違法となります。ご注意ください。
本稿の内容を検証する場合は、必ず影響を及ぼさない限られた環境下で行って下さい。
また、本稿を利用した行為による問題に関しましては、筆者および株式会社アットマーク・アイティは一切責任を負いかねます。ご了承ください。
前回までは主に検査する立場から脆弱性を見てきたが今回は少し趣向を変えて、安全なWebアプリケーションを開発する際、役に立つと思われる情報を提供しよう。
セッション管理の脆弱性は、セッション管理の仕組みを複雑にすることが原因の1つと考えられる。最初からシンプルにしておけば、安全性の確認も非常に楽であるし、攻撃する側からしても、アプリケーションが脆弱に見えないため、攻撃しない場合もある。
そのサイトだけを唯一の攻撃対象としているのであればこの限りではないが、攻撃者にとっては嫌な仕様である。
アプリケーションの重要性によって、推奨するセッション管理の仕組みは変わる。もちろん、堅牢であるに越したことはないのだが、セキュリティ強度を高くするあまり、使いづらくなってしまう場合もある。そのため、使いづらくならない程度にセキュリティ強度を高めるためのポイントについて説明していこう。
まずはセッション管理の方法の選択を行う。これが決まらないとそのほかの仕様も決まらない。一般的なアプリケーションのセッション管理の方法としては次の2つに分類される。
インターネットバンキングや企業間取引などの重要なアプリケーションでは(1)の方法が取られることが多い。CookieにセッションIDを入れるとクロスサイトスクリプティング(XSS)によってセッションIDを抜かれる危険性がある。重要なアプリケーションでは、これを根本的に回避するために(1)のような方法が取られる。しかし、画面遷移をすべてPOSTメソッドで行うことになるため、ブラウザの戻るボタンが使えなくなったりして不便になることがある。これは利便性を下げることになるため、セッション管理の方法としては(2)を選択することが一般的となる。
以降では、このセッション管理方法を使う場合を前提として話を進めることにする。
基本構造というと大げさかもしれないが、セッション管理の次にこれを決める必要がある。選択肢は、 以下の2つである。
前者は実装する機能ごとに別々のアプリケーションを作り、各フォームからそれぞれのアプリケーションにSubmitする仕様である。小さなアプリケーションではこれでもよいと思うが、サイトが大きくなると、処理の重複部分が多くなる(重複部分とは、以降で詳しく説明するが、パラメータの処理、画面遷移チェックなどのことである)。重複部分をまとめて1カ所で処理することは、アプリケーションが簡潔になり、バグが減り、セキュリティ的にも堅牢性が高くなる。そのため、本稿では後者の方法を採用することにして話を進める。
ユーザーから見えるアプリケーションは1つであるが、開発するアプリケーションが1つであるわけではない。基本的な処理を1つのアプリケーションで実行した後、おのおのの機能を実現するためのコードを呼び出して処理を続行する。
これを実現するには、機能を区別するためのパラメータが必要になる。このパラメータは、次のような仕様になるだろう。
例えば「001-01」というフォーマットにした場合、機能001の画面番号01の機能を実行することを意味する。アプリケーションがこのパラメータを受け取ると、その番号に対応したロジックで処理を進める。
このようにして実行する機能を区別することができる。この部分の説明は後ほど行うことにして、まずは、アプリケーションが呼び出されたときに、どのような処理を行う必要があるかについて説明しておこう。
基本処理としてはこのような処理が必要になるだろう。では順に説明する。
URLパラメータ、POSTデータからパラメータを受け取り、適当な配列に入れておく。すべてのリクエストをPOSTメソッドに制限している場合は、URLパラメータは無視してもよいし、不正リクエストとしてエラー画面を表示してもよい。バッファオーバーフロー対策として、最初にCONTENT_LENGTHだけを見て、長すぎる場合はエラーとするような処理を入れてもよいだろう。
動作パラメータが前述したようなフォーマットになっているかチェックを行う。正しくなければエラー画面を表示する。
Cookie内のセッションIDを受け取り、正しい値であるかチェックを行う。このチェックでは、
といった処理を行う。不正なセッションIDである場合は、そのIDを無視して、新しいセッションIDを発行してユーザーに返す。この時点では、セッションが存在するかのチェックまでで、認証状態などのチェックは、各機能の先頭で行う。
「ユーザーから見えるアプリケーションは1つ」ということで話を進めているが、もし、すべての機能を認証後に利用するようなアプリケーションである場合は、ログイン部分だけ別のアプリケーションにしてしまえば、残りの部分は「認証後」であることが前提となるので、この部分で認証チェックまで行ってしまうことができる。このような作りにしておけば、未認証ユーザーによる不正アクセスが完全にできなくなる。
ただし、ショッピングサイトのように、未認証でもサービスを利用できるような場合は、この方法は使えないので注意していただきたい。
セッションのタイムアウトについては、セッション変数を使えば簡単にチェックできる。アクセスごとにセッション変数内のあるパラメータにアクセス時刻を記録しておき、次のアクセス時にその変数の時刻と現在時刻を比較する。未認証状態で処理を行っているのであれば、この仕様は実装しなくてもセキュリティ的には問題はないだろうが、認証してからサービスを提供している場合には、タイムアウトチェックは必要だろう。
ここでは、直前のリクエストの存在をチェックする。これは以下のようにして行う(図1参照)。
(1)リクエスト送信
(2)リクエストで渡されたランダム値とセッション変数内のランダム値を比較
(3)新しいランダム値を生成する
(4)セッション変数内のランダム値を(3)で生成した値に書き換える
(5)(3)のランダム値をレスポンスに入れて返す
ポイントは上記シーケンスの(2)の部分である。リクエスト内のランダム値とセッション変数内のランダム値が等しければ、そのランダム値を生成した直前のリクエストが存在することが確認できる。
このチェックを行うことで、セッションIDが漏れても、ランダム値を知らないユーザーは、アクセスすることができなくなる。ただし、この場合でも、WebメールシステムにおけるXSS攻撃では、
Cookieとhidden内のランダム値の両方を抜き出すことができてしまうので、これだけでは完全な対策とはならないので注意が必要である。
また、このような仕様は、hiddenにセッションIDを入れてPOSTするセッション管理方法と似ており、戻るボタンで戻った場合や、新しいウィンドウを開いた場合に動作がおかしくなる場合がある。そのため、リクエスト内のランダム値とセッション変数内のランダム値に不整合が生じた場合は、不正操作として強制ログアウトするのではなく、いったんエラー画面を表示してトップページに戻すような仕様にしたほうがよいだろう。
その2では、画面間の厳密なチェックを行う。この部分はしっかりと実装しておく必要がある。1つのアプリケーションに対して動作パラメータを渡す仕様では、パラメータを書き換えることで、ある機能の画面シーケンスの途中の機能を呼び出すことができる。前述した「その1」のチェックでは、ユーザー自身による動作パラメータ改ざんを検知することができない。アプリケーションを正常に動作させるには、このような呼び出しを許可してはならない。
ある画面を呼び出す場合、その画面を呼び出すための前提条件をクリアしている必要がある。その前提条件とは、そのリクエストが正しい画面から送信されていること、である。前の画面に関する情報はサーバ側で管理しておけばいい。セッション変数の中にでも入れておけばよいだろう。
このチェックは以下の手順で行う(図2参照)。 基本的にチェック(その1)と同じである。
(1)リクエスト送信
(2)現在の画面IDと次の画面IDを比較
(3)セッション変数内の画面IDを次の画面IDに書き換える
(4)次の画面IDをhiddenに入れて返す
この段階で不正な入力はすべてはじいてしまうのがよい。個々の機能を実行するアプリケーションごとに不正入力チェックを埋め込む方法もあるが、これだとチェック漏れが発生する場合がある。
それぞれの機能ごとにチェックするパラメータが異なるが、開発する前にルールを決めておけば、1カ所でまとめて入力チェックを行うことができる。決めておく必要があるルールとは、パラメータの名前付けである。
チェックする必要があるのは、 以下のものが考えられる。
これらの情報をパラメータ名の中に埋め込んでしまえば、実行する機能の処理内容を知らなくても、入力チェックを行うことができる。埋め込み方としては、名前の先頭部分に文字の種類と長さを入れておくのが単純でよいのではないかと思う。
パラメータ名を書き換えれば、チェックを回避して、想定外のデータを送り込むことは可能だが、パラメータ名が違うとアプリケーションで無視されてしまうので問題はないだろう。
セッションIDのチェックや画面遷移のチェックなど、処理を進めるために必要な条件チェックが完了したら、ここでおのおのの機能に処理を分岐させる。1つのアプリケーションが肥大化しないよう、おのおのの機能は別のアプリケーションにしてしまおう。異なるファイルに分けたら、後はそれぞれのアプリケーションを呼び出すような仕組みを組み込む。本稿では特に開発言語は特定していないが、例えば、JSP/Servletを使用する場合は、ServletからJSPにディスパッチするようにすれば、この仕様は簡単に実現できると思う。
個々の機能を実行するアプリケーションで実装しなければならないのは、認証状態のチェック、その機能が受け取るパラメータのチェックである。この部分の詳細は、機能ごとに微妙に異なるが、商品を購入する処理を例として、どのような実装になるか見てみよう。
セッション内にトランザクションデータを持っており、このリクエストを送ることで、購入処理が確定する。
処理が正しく完了するためには次の1〜4までの条件を満していなければならない。
1の処理はセッションに対応するユーザー情報があるかどうかチェックするだけである。2については、リクエストを受け取ったときに処理を行っているので、ここで行う必要はないだろう。3、4のチェックは、個々のアプリケーションで処理が異なるのでここで行う必要がある。
アプリケーションに対する攻撃は、不正文字によるテクニカル系のものと、アプリケーション的に有効な入力を使ったロジック系のものがある。前者のチェックは終了しているので、後者のチェックをここで行えばよい。
ロジック系の検査は各機能に分散してしまうが、これは仕方がない。もっとうまい方法があれば、またそのときに説明しようと思う。
←「第9回」へ
「第11回」へ→
中村隆之(なかむらたかゆき)
三井物産セキュアディレクション勤務。 セキュリティコンサルタントとして主にWebアプリケーションのセキュリティ検査に従事しており、大手ポータルサイト、オンラインバンキングなどの数多くの 検査実績を持つ。また、セキュアネットワーク及び暗号関連の研究に携わり、大手製造、官公庁、金融機関へのセキュリティシステム導入など数多くの実績を持つ。
主に、不正アクセス監視サービス、セキュリティ検査、セキュリティポリシー策定支援などのサービス提供している。また、セキュリティに関する教育サービスも実施中。
Copyright © ITmedia, Inc. All Rights Reserved.