本稿は『日経オープンシステム 2001年6月(no. 99)』に掲載された記事「オープンセミナー 実践!セキュアなWebプログラミング 第3回 ASP(Active Server Pages)編 Part2」をHTML化したものです。内容は雑誌掲載時とほぼ同じですが,誤記訂正や小さな改善を施してあります。(2001年11月)



セキュアWebプログラミング

ASP(Active Server Pages)編 Part 2

佐名木智貴 著
元セントラル・コンピュータ・サービス株式会社 インターネットビジネス部
セキュリティ担当エンジニア




Webアプリケーションでは,複数の HTTP リクエストを関連付ける「セッション管理」の仕組みが必要不可欠である。そこで利用するセッションIDがクラッカに入手されたり偽造されたりすれば,ユーザーになりすまされてしまう。セッションIDの漏えいに備え,複数の方法でクライアントを認証するなどの対策を講じておきたい。また,重要なファイルが読み出されないように注意する,運用ミスを想定した設定を行うことも大切だ。



Internet Infomation Server(IIS)+Active Server Pages(ASP)*0を題材にして解説する。今回のテーマは,ユーザーまたはクライアントの認証と,運用である。ASP固有の問題も取り上げるが,以下で説明する問題や対策の多くは他の開発環境でも当てはまる。


*0
【ASP】Active Server Pages
MicrosoftのWWWサーバーであるInternet Information Serverが備える,サーバー上でスクリプトを実行する機能。HTML中にスクリプトを埋め込んだASPファイルにWWWブラウザがアクセスすると,スクリプトが実行され,実行結果がWWWブラウザに出力される。スクリプトとしてVBScriptなどが実行できる。





セッション情報をクラッカに取得され
ユーザーになりすまされる
 HTTP*1によるアクセスでは,クライアントからのリクエストに応じてサーバーからページが送信されると,そこでコネクションは切断される*2。つまり,複数ページにわたるリクエストが同じクライアントからのものかどうかを,HTTPだけでは管理することができない。そのためWebアプリケーションを構築するには,複数のリクエストを関連付けて1つのセッションとして認識するための何らかの仕組みが必要になる。
 この「セッション管理」の最も一般的な方法は,セッションIDを利用するものである。ユーザーがログインした際にサーバーがクライアントに対してセッションIDを発行し,以後クライアントはリクエストにセッションIDを含める(図1)。
 ここで注意しなければならないのは,万が一クラッカに何らかの方法でセッションIDを入手されてしまうと,ユーザーになりすまされ,ユーザーの個人情報を入手されたり,送信データを改ざんされたりする恐れがあるということだ。セッションIDとしてユーザIDそのものや容易に推定できるデータを使用していると,より被害が大きくなる可能性がある。そのような事態を防ぐには,セッションIDや認証がASPでどのように実装され,どんな場合に弱点が存在するのかを知っておく必要がある。

図1●Webアプリケーションのユーザー認証とセッション管理
HTTPによるアクセスは1ページごとに切れるため,複数のアクセスを1つのセッションとしてサーバーが認識するためには,何らかの仕組みが必要になる。ログインした際にサーバーがセッションIDを発行し,以後クライアントはリクエストにセッションIDを含める方法が一般的だ。しかし,クラッカに何らかの方法でセッションIDを入手されたり偽造されたりすると,ユーザーになりすまされてしまう

*1
【HTTP】HyperText Transfer Protocol
WWWサーバーに格納されているHTMLなどの情報をブラウザに転送する,TCP/IPの上位プロトコル。HTTPプロトコル自体はコネクションレス型の通信を行い,一度の接続で1つのリクエストをサーバー投げ,応答を得た後にコネクションを切断する。
*2
ここでいうコネクションとは,TCP/IPのコネクションを指す。HTTPはTCP/IPの上で動作する。TCP/IPは通信の際にコネクションを確立するが,デフォルトでは1回のHTTPリクエストに対する応答がクライアントに返されると,そこでTCP/IPのコネクションが切断される。HTTPの「Keep-Alive」というオプションを使用すると,TCPレベルでのコネクションを維持することができる。しかし,その場合でも,本セミナーで解説しているアプリケーション・レベルのセッション管理には他の方法が必要である。
ちなみに,HTTP1.1では「Keep-Alive」がデフォルトである。










Cookieは漏れることを前提に利用する。
 セッションIDをやり取りするための仕組みとしては,Cookieを利用することが多い。Cookieは,クライアントのWWWブラウザが特定のページにアクセスするとWWWサーバーから発行されるデータである。以後,クライアントが発行元のWWWサーバにアクセスする際に,Cookie情報も送るようになる。
 ASPでは,Sessionオブジェクトを使うことで1つのセッションの間サーバーのメモリー上に変数を保持し,同じセッションを構成するWWWページ間で共有できる。つまり,セッション管理が行える。そして,このSessionオブジェクトは,Cookieを使用している(図2)。Cookieを発行し,同じセッションに属するリクエストかどうかを判断するためにCookie情報を参照する。
 さて,このCookie情報がクラッカに漏れてしまうことがあるのだろうか。まず,ネットワーク・モニターなどでネットワークを盗聴すれば,入手できる。盗聴するためには通信経路となるセグメントに直接ネットワーク・モニターを接続しなければならないが,ありえないことではない。さらに前回解説した「クロスサイト・スクリプティング*3」によって,Cookie情報が漏れる可能性がある。盗聴はSSL*4などで暗号化すれば防げるが,クロスサイト・スクリプティングを利用された場合,暗号化されていてもCookie情報の漏えいを防ぐことはできない。
 このような状況なので,Cookie情報は他者に入手されることを前提にした使い方をしなければならない。まず,ユーザIDなど意味のある情報をCookie情報として使用してはいけない。ランダムな文字列などが望ましい。また,Cookie情報は1セッション限りとする。すなわち,1つのセッションが終了したらCookie情報は無効になるようにする。ASPのSessionオブジェクトでは,上記の2つの条件を満たしたCookie情報が使用されている。
 ユーザーが,ログアウト操作を行わずに終了してしまうこともある。ASPのSessionオブジェクトではこの点も考慮してあり,デフォルトでは最後のアクセス後20分でCookieが無効になる。
 さらに高い安全性が求められる場合,タイムアウトまでの時間を短縮する。各ASPファイルの先頭でSession.Timeoutの値を設定することで,Webアプリケーションごとにタイムアウト値を設定できる(図3上)。この方法では1分以下の指定はできないが,実用上十分であろう。
 Session.Timeoutの時間を極力小さくし,クライアント側のページが定期的にリロードされるようにするという方法もある。これにはHTMLのHEAD内のMETAタグを利用する(図3下)。アクセスが増えるためにネットワークの負荷は高くなるが,ユーザーが別のサイトに移動したという情報をサーバー側は速やかに知ることができ,Sessionオブジェクトを破棄することができる。


図2●ASPのSessionオブジェクトの仕組み
ASPのSessionオブジェクトを使えば,セッションの間サーバーのメモリー上に変数を保持できる。Sessionオブジェクトでは,セッション管理にCookieを使用する。
図3●Cookieの安全性を高める
ログアウトしなかった場合でも,なるべく早くCookieが無効になるようにする。
有効期限を設定する
Session.Timeout = 10
デフォルトは 20 分。

HTMLのMETAタグで定期的にRefreshをかけるように指定する
<HEAD>
<META HTTP-EQUIV="Refresh" CONTENT="40">
</HEAD>
この例では、40 秒に指定。

*3
【クロスサイト・スクリプティング】Cross-site Scripting
クラッカがWebサイトに入力した不正なスクリプトを,ユーザーに実行させてしまう不正アクセスの方法。外部からの入力データをそのままWWW画面に表示するようなWebアプリケーションで起こる。スクリプトが,Webサイトからのものとして実行されてしまうため,Webサイトが発行したCookie情報をクラッカのサイトに送るスクリプトなどを実行されてしまう恐れがある。
*4 【SSL】Secure Sockets Layer
インターネットで安全に通信を行うための暗号通信プロトコル。HTTP,SMTP,POP3などのアプリケーション層のプロトコルと組み合わせて用い,それぞれHTTPS,SSMTP,SPOP3と呼ぶ。





URLのクエリー文字列は改ざんが容易
 Cookieを使わずとも,セッションIDをやり取りできる。多く使われているものに,URLのクエリー文字列にセッションIDを埋め込む方法がある。クエリー文字列は,URLのドメイン名,ファイル名のあと,「?」記号に続いて「変数名=値」として記述される(図4)。サーバー側のASPプログラムではRequestオブジェクトで「Request.QueryString(変数名)」という書式で受け取ることができる。
 クエリー文字列を使うメリットは,クライアントを選ばないことだ。Cookieや後述する電子証明書は,PC上のWWWブラウザであれば,まず使うことができる。しかし,ユーザーによってはWWWブラウザの設定でCookieを無効にしていることがある。また,WWWブラウザ搭載の携帯電話といったPC以外のクライアントでは,Cookieや電子証明書に対応していないものもある。一方のクエリー文字列は単にURLに記述するだけなので,使用できないクライアントはほとんどない。
 ただしクエリー文字列を使用する際には注意が必要だ。改ざんが非常に容易だからである。WWWブラウザでURLを打ち込むだけで,どのようなクエリー文字列も送ることができる。クエリー文字列にセッションIDやユーザーIDを埋め込むには,主に次の2つの方法が使われる。
 @次のページをリンクで呼び出して,リンク先URLのクエリー文字列にセッションIDを含める。 AフォームのHiddenフィールドにセッションIDを埋め込み,GETメソッドでサーバーに送信する。いずれの場合でも,いったんクライアント側にデータを預けてしまうので,そのデータはクライアント側で改ざん可能である。
 例えば図5のような,クエリー文字列でユーザーIDを受け取り,ユーザーIDに対応するユーザー情報を表示するアプリケーションを考えてみよう。このアプリケーションでは何のチェックも行っていないため,クエリー文字列中のユーザーIDを偽造されると,そのユーザーの個人情報を表示してしまう。偽造方法は簡単だ。単にWWWブラウザのURLを打ち込むフィールドに,クエリー文字列を手で入力すればよい(図6)。URLがユーザーに見えないように,フレーム化したサイトもあると聞いたことがある。しかし,WWWブラウザでHTMLのソースコードを表示すれば,URLは簡単に見えてしまう(図7)。
 埋め込んだデータが改ざんされていないかどうかを検査するには,クエリー文字列の中にデータのチェック・サム値を一緒に埋め込み,クライアントから送信されてきたデータとチェック・サム値を比較すればよい。リンクに埋め込む場合は新たにもう一つの変数として,Hiddenフィールドに埋め込む場合はもう一つのHiddenフィールドとしてチェック・サム値を付加する。


図4●クエリー文字列
URLの最後「?」の後に続く部分。サーバーのASPプログラムではRequestオブジェクトで受け取ることができる。
サーバー
Request.QueryString("変数名")

URL
http://sample/sample.asp?変数名=値
赤字部分がクエリー文字列


図5●危険なアプリケーションの例 クエリー文字列でユーザーIDを受け取り,ユーザー情報を表示するアプリケーションで,ユーザーIDを偽造されると,他のユーザーの個人情報を取得されてしまう

01 <%
02  Option Explicit
03 %>
04 <HTML>
05  <HEAD>
06   <TITLE>クエリー・ストリングの問題</TITLE>
07  </HEAD>
08  <BODY>
09   <%
10    Dim SQLStr
11   Dim DBCon
12    Dim RS
13    Dim i
14    Set DBCon = Server.CreateObject("ADODB.Connection")
15    DBCon.Open "Sample1","User","123"
16
17    SQLStr = "SELECT * FROM UserTBL WHERE UserID=" & 
Request.QueryString("UserID")
18 
19   Set RS = DBCon.Execute(SQLStr)
20 
21    Response.Write("<TABLE BORDER=""1"">")
22    For i=0 To RS.Fields.Count-1
23     Response.Write("<TR><TD>" & RS(i).Name 
& "</TD><TD>" & RS(i).Value & "</TD></TR>")
24    Next
25    RS.MoveNext
26    Response.Write("</TABLE>")
27    RS.Close
28    Set RS = Nothing
29    DBCon.Close
30    Set DBCon = Nothing
31   %>
32  </BODY>
33 </HTML>


図6●IDを改ざんされた場合
他のユーザの個人情報を表示してしまう


図7●フレーム化してもクエリー文字列は隠せない
フレームにしてもHTMLのソース・コードを参照すれば,クエリー文字列を見ることができる

表示(V)->ソース(C)
<HTML>
 <HEAD>
  <TITLE>フレーム化しても URL を隠す事はできない</TITLE>
  <FRAMESET ROWS="20%,*">
   <FRAME SRC="top.htm">
   <FRAME SRC="sample.asp?UserID=1">
  </FRAMESET>
 </HEAD>
</HTML>









サイト中のリンクでクエリー文字列が漏えい
 このようにして改ざんをチェックしても,クエリー文字列にはもう一つ問題がある。リンクをクリックすると,クエリー文字列を含むURLがリンク先に送信されてしまうことだ。
 例えばページAの中にページBへのリンクがあるとする。そのリンクをクリックすると,ページBへのリクエストのヘッダーには Referer情報としてページAのURLが記述される。このURLのクエリー文字列にセッションIDが含まれていると,リンク先のサイトはそのセッションIDを入手できることになる。リンク先サイトの開設者が入手したセッションIDを悪用し,他のユーザーになりすます恐れがある。
 「悪用するようなサイトにリンクなどしない」と思われるかもしれないが,電子掲示板やWWWメール・サービスなどで問題が発生する可能性がある。HTMLタグを書き込める電子掲示板で,悪意のあるクラッカが自分のサイトへのリンクを書き込む。別のユーザーがリンクをクリックすると,ユーザーが電子掲示板をアクセスしていた際のURLがクラッカのサイトに送られる。セッションIDやユーザーIDがこのURLに含まれていると,それらのデータがクラッカに入手されてしまう(図8)。HTMLタグを直接書き込めなくとも,テキスト中のURLをリンクに自動変換する機能を備える電子掲示板であれば,同じ問題が起こり得る。
 WWWブラウザでメールを送受信できる,いわゆるWWWメール・サービスでも,実際に同じような問題があったことが多数報告されている。クラッカが自分のサイトへのリンクを埋め込んだメールをユーザーに送る。ユーザーがメールを開いてリンクをクリックすれば,クラッカはURLを入手できる。電子掲示板と同様,WWWメールがHTMLメールを表示できる場合と,メール中のURLをリンクに自動変換する機能を持った場合に問題が発生する。
 このような,リンクによるセッションID漏えいの可能性があるアプリケーションでは,クエリー文字列を使うべきではない。例えば,HiddenフィールドにセッションIDを埋め込んでいる場合ならば,GETメソッドではなく,POSTメソッドでデータを送信するようにする。GETメソッドでは送信データはURL中のクエリー文字列として送られるが,POSTメソッドで送れば,データはHTTPリクエストのボディ部として送信されるため,URLには含まれない(図9)。


図8●リンクによるクエリー文字列の漏洩
リンクをクリックすると,直前に見ていたURLがReferer情報としてリンク先に送られる。そのため,クエリー文字列に含まれる情報も,リンク先に送られる。クエリー文字列にセッションIDが含まれていると,悪用され,リンク先サイトの所有者にユーザーになりすまされる恐れがある。

図9●GETメソッドとPOSTメソッドの違い
POSTメソッドで送れば,データはURLに含まれない。リンクによる漏えいへの対策になる。
GET メソッドの場合
Web アプリケーションへのリクエスト
http://www.test/test.cgi?User=nikkei
HTML
<FORM METHOD="GET" ACTION="http://www.test/test.cgi">
ユーザ名:<INPUT TYPE="text" NAME="User">
<INPUT TYPE="submit" VALUE="送信">
</FORM>


POST メソッドの場合
Web アプリケーションへのリクエスト
http://www.test/test.cgi
HTML
<FORM METHOD="POST" ACTION="http://www.test/test.cgi">
ユーザ名:<INPUT TYPE="text" NAME="User">
<INPUT TYPE="submit" VALUE="送信">
</FORM>



セッション管理に複数の方法を併用する
 これまで見てきたセッション管理の問題は,セッションIDという単一の方法でクライアントを認証していることが原因の一つだとも言える。他の認証方法と併用すれば,セキュリティ・レベルを向上させることができる。
 例えば,認証時のクライアントIPアドレスを記録し,クライアントからのページ要求ごとにIPアドレスをチェックするという方法がある*5
 クライアントが見ていた前画面をRefererによってチェックするという方法もある。ただし,前回のセミナーで述べたように,ちょっとした知識やツールがあれば,HTTPリクエストのヘッダーやボディ部は偽造できる。Refererによるチェックは絶対に安全とはいえない。この方法を単独で使用するのではなく,他の認証方法の補完手段として使用することを推奨する。



*5
ただし,インターネット接続プロバイダーによってはプロキシ(ゲートウェイ)・サーバーを介してインターネットにアクセスする。また携帯電話によるインターネット・アクセスの場合,携帯電話事業者のゲートウェイ・マシンを介してのアクセスになる。これらのケースでは,アクセスするたびにクライアントのIPアドレスが変わることが多い。複数のゲートウェイ・マシンがあり,アクセスするたびに別のゲートウェイ・マシンが使われることが多いためだ。その場合,ある範囲のIPアドレスであれば同じクライアントと見なさざるを得ず,完全にクライアントを特定できない。そのため,IPアドレスによる認証は単独で使用するのではなく,他の認証方法の補完手段としての利用をお勧めする。





最も安全な電子証明書
 最後に,最も安全性が高い認証方法を紹介しよう。電子証明書である(写真1)。電子証明書とは,SSLを使用して暗号化したHTTPであるHTTPSで使用できる認証方法だ。運用がやや煩雑ではあるが,セキュリティ・レベルが高いユーザー認証およびセッション管理機能が備わっている。
 ASPには,ClientCertificate コレクションという,電子証明書にアクセスするためのオブジェクトが用意されている。例えば,図10のようなファイルをすべてのASPファイルの先頭部分にインクルードすることで,すべてのASPファイルで電子証明書による認証を行うことができる。この場合,データベースのUserTBLに認証するユーザーの電子メール・アドレスが登録されており,電子証明書から電子メール・アドレスを取得して比較している。
 電子証明書は暗号化された上でやり取りされるため,電子証明書を各ASPファイルで毎回チェックするようにすれば,クロスサイト・スクリプティングのような問題やセッションの乗っ取りなどの問題は起きないので,非常に堅牢なWebアプリケーションを構築することができる。ただし,例えば,ログインのページでのみ電子証明書の認証を行っている場合はその限りではない。すべてのASPファイルにて電子証明書による認証を行うことを推奨する。
 なお,図10のプログラムでは説明のため,データベース接続用のデータベース名,ユーザー名,パスワードをソース・コード中にじかに書いているが,これはセキュリティ上望ましくない。その理由と安全な記述方法は,次項で説明しよう。


写真1●電子証明書
電子証明書には写真のような情報が含まれており,Cookieなどに比べて偽造は極めて難しい。

図10●電子証明書を使用する認証プログラムの例