mb_language("uni"); mb_internal_encoding("utf-8"); mb_http_input("auto"); mb_http_output("utf-8"); ?>
前回と、前々回では住所録アプリケーションに認証機能を実装するまでを紹介しました。認証機能の追加により、第01回で紹介した画面遷移図に沿ったアプリケーションが完成しました。しかし、このアプリケーションには問題が残っています。前回までのサンプルコードを見て気付かれた読者もおられると思いますが、セキュリティの面で落とし穴があるのです。今回はそのような問題の原因と対処法を紹介します。
Webアプリケーションにおけるインジェクション問題とは、悪意のあるユーザが入力値に特別なコマンドを含めることにより、サーバ側でそのコマンドが実行されてしまうセキュリティ上の問題のことです。Webサーバで入力値のチェックを行っていないことがインジェクションの原因、脆弱性となります。
代表的なものとしてはSQLインジェクションというものがあります。これはRDBに対して行われる攻撃です。ユーザが入力した値を利用してSQLを作成するときに、悪意のある入力によってSQLが改ざんされてしまい、RDBに対して本来意図されていない問い合わせが行われ、登録されているデータの改ざんや本来ユーザに公開したくない情報が漏れてしまう危険があります。
XML DBを利用する場合も同じ手口の攻撃があります。1つはXMLインジェクション、もう1つはXPathインジェクションといいます。XMLインジェクションは、格納するXMLを改ざんする攻撃で、XPathインジェクションは、XML DBに問い合わせるXPathを改ざんする攻撃です。どちらの攻撃に対してもXML DBを利用したアプリケーションでは対策を行う必要があります。
現在の住所録アプリケーションで実際に起きている問題を確認してみます。
● XMLインジェクションの現象確認
住所アイテム入力画面で、アイテムの名前や住所を入力する際に「<」と「>」で囲まれた文字列を格納すると、現在の実装方法では、name要素やaddress要素の下に子要素を持たせる形になってしまい、アプリケーションが意図していないXMLの構造が生まれてしまいます。
たとえば、ユーザがアイテム編集画面にて、
と入力したとします。このとき、「<」や「>」に対して何も処理をしなければ<株>というタグが閉じていないと判断され、登録を実行しようとすると下記のようなエラーを返し、登録することができません。
<Error> <Name> Malformed XML </Name> <Message> Could not parse XML input </Message> <Exception-Number> 6 </Exception-Number> </Error> |
また、
と入力した場合は、name要素の下にcomという子要素が入ることで、プログラムでは意図しない構造になってしまい、閲覧画面で正しく表示されないという問題が発生します。これらの問題はアプリケーションが正常に動作しない原因となりますので対策が必要といえます。
● XPathインジェクションの現象確認
現在、ログイン画面でログインを行う際には、ユーザが入力するユーザ名とパスワードを受け取り
/ND/user_info/user[name = {ユーザ名} and password = {パスワード}] |
というXPathを問い合わせることで、ユーザが登録済みか確認し、パスワード認証を行っています。もし悪意をもったユーザがユーザ名として
" or 1=1 or ""="
という値を入力した場合(パスワードはなんでもよい)、XPathは
/ND/user_info/user[name = "" or 1=1 or ""="" and password = ""] |
と改ざんされてしまいます。このXPathはXprioriに登録されたユーザを全て取得する問い合わせなので、現在の実装では、取得したユーザの中で一番上のユーザ、つまり最初に登録した管理者ユーザとしてログインが行われてしまいます。権限を持たないユーザに管理者ログインを許可してしまいユーザ操作が行われてしまうので、非常に危険な問題といえます。
▲XPathインジェクション
これらのインジェクション攻撃への対応方法を以下に示します。
● XMLインジェクションへの対応
XMLインジェクションは、「<」、「>」、「&」といったXMLにおいて特別な意味を持つ文字をエンコードしていないのが原因です。Xprioriにパラメータから受け取った値を登録する場合は、APIのXMLEncoderクラスのxmlEncode関数によって登録する値をあらかじめエンコードすることで対応できます。
先ほどの名前に入力した値の場合は
<株>プリオリカンパニー → <株>プリオリカンパニー
<com>プリオリカンパニー</com> → <com>プリオリカンパニー</com>
とエンコードされます。
住所録アプリケーションでの修正箇所は、住所アイテム情報を扱うItemBeanクラスのgetItemXML関数と、ユーザ情報を扱うUserBeanクラスのgetUserXML関数です。それぞれxmlEncode関数を利用して下記のように修正します。
public String getItemXML() { String itemXML = "<item id=\"" + XMLEncoder.xmlEncode(this.getID()) + "\">" + "<name>" + XMLEncoder.xmlEncode(this.getName()) + "</name>" + "<zip>" + XMLEncoder.xmlEncode(this.getZip()) + "</zip>" + "<address>" + XMLEncoder.xmlEncode(this.getAddress()) + "</address>" + "</item>"; return itemXML; } |
public String getUserXML() { String userXML = "<user id=\"" + XMLEncoder.xmlEncode(this.getID()) + "\">" + "<name>" + XMLEncoder.xmlEncode(this.getName()) + "</name>" + "<password>" + XMLEncoder.xmlEncode(this.getPassword()) + "</password>" + "</user>"; return userXML; } |
また、この修正に伴い幾つかの対応が必要になります。1つは検索の際の脆弱性を避けるため、検索キーワードもエンコードする必要があるという点です。また、Xprioriに格納した情報を取得するときも、APIのXMLEncoderクラスのxmlUnencode関数を利用してデコードした値を取得する必要があります。
● XPathインジェクションの対応
SQLインジェクションの場合は、SQLに含まれる文字列のシングルクォート(')やダブルクォート(")の頭にバックスラッシュ(\)をつけ、エスケープすることで対応できますが、XPathの場合はバックスラッシュをつけても、その文字は「\」と「"」をつなげたものとして扱われてしまうため、別の方法での対応が必要です。
先ほどの「" or 1=1 or ""="」という値について考えて見ましょう。この値での問題点は、この値に含まれているダブルクォートが単なる文字ではなく、XPathの一部として解釈され、結果としてその後の文字列が条件文として解釈されてしまう、という点です。ですから、ダブルクォートを文字として扱うように、以下のように、値をシングルクォートで囲んでみましょう。XPathは下記のようになります。
/ND/user_info/user[name = '" or 1=1 or ""="'] |
このXPathを実行すると、「" or 1=1 or ""="」は文字列と判断され、name要素に一致する値がないため空を返します。このような方法でダブルクォートによるXPathインジェクションへの対応ができました。しかし、この対応では「' or 1=1 or ""='」のように、値がシングルクォートで囲まれた場合に同様の問題が発生します。そこでXPathのconcat関数を利用して、下記のような対応をとることにします。
・ 文字列にシングルクォートが含まれている場合、シングルクォートで区切って文字列を分割する
・ 分割した文字列はダブルクォートで囲む('{シングルクォートを含まない文字列}')
・ シングルクォートはそれだけをダブルクォートで囲む("'")
・ 各クォートで囲んだ値をconcat関数の引数に渡す
ここで利用するconcat関数とは、引数に与えた文字列を結合する関数です。例えば、
/ND/user_info/user[name = "test"] |
と
/ND/user_info/user[name = concat("te",'st')] |
は同じ結果を返します。concat関数の引数に与える文字列の囲む文字にはシングルクォート、ダブルクォート両方利用できます。
ですから「' or 1=1 or ""='」の場合は
という3つの文字列に分割し、それらをconcat関数で連結させることで、Xprioriには
/ND/user_info/user[name = concat("'",' or 1=1 or ""=',"'")] |
というXPathを実行します。これで、ダブルクォート、シングルクォートのどちらで囲まれた場合も、XPathインジェクションへの対応ができました。上記のロジックでconcat関数を生成する関数は以下のようになります。この関数は汎用クラスであるAddressbookUtilsクラスに追加します。
public static String getXpathConcat(String query) { // 分割対象文字列 String splied_query = query; // concat関数の中身 String concat_in = ""; //queryの最後がシングルクォートかどうかチェックする if (query.lastIndexOf("'") == query.length() - 1) { // A:最後に半角スペースを追加する splied_query = splied_query + " "; } // シングルクォート(')で分割する String[] split_sq = splied_query.split("'"); if (query.lastIndexOf("'") == query.length() - 1) { // Aで追加した半角スペースを空にする split_sq[split_sq.length - 1] = ""; } // concatの中身を作成する for (int i = 0; i < split_sq.length; i++) { concat_in += "'" + split_sq[i] + "'"; if (split_sq.length != i + 1) { concat_in += ",\"'\","; } } // XPathのconcat関数を返す return "concat( " + concat_in + " )"; } |
XMLインジェクション対策とXPathインジェクション対策を下記のクラスに対して行いました。文字列比較を行うXPathを問い合わせている部分には、比較対象の文字列に対してxmlEncode関数によるエンコードを行います。さらにgetXpathConcat関数によって文字列をconcat関数に変換し、文字列比較を行います。また、Xprioriに格納したアイテムの情報を取得する際は、xmlUnencode関数を利用してエンコードされた文字列をデコードします。修正の詳細は下記のクラスをご覧ください。
修正を行ったクラス
・ ItemBeanクラス
・ UserBeanクラス
・ SearchAddressbookBeanクラス
・ SearchUserInfoBeanクラス
・ UpdateAddressbookBeanクラス
・ UpdateUserInfoBeanクラス
クロスサイトスクリプティング(以下XSS)とは、ユーザが入力した住所を表示する住所録アプリケーションのような、ユーザからの入力をページに表示するWebアプリケーションにおいて発生するセキュリティ上の問題です。XML DB独特の問題ではありませんがWebアプリケーションには必須のセキュリティ対応といえます。
ユーザが入力したHTMLタグなどがそのまま表示されるような場合、通常のHTMLタグであれば問題ないのですが、悪意のあるユーザが入力したスクリプトタグをそのまま表示してしまうと、表示したユーザのブラウザでスクリプトが実行されてしまいます。スクリプトの実行によってフィッシング詐欺のWebページに転送されたり、クッキーの漏洩により個人情報が漏れてしまう恐れがある問題です。JSPでXSS対策を実装する方法を紹介します。
● XSSの現象確認
では、現状のアプリケーションの入力フォームがある画面でXSSの現象を確認してみましょう。アイテム一覧画面では、入力した検索ワードを検索フォームに表示するため、以下のようなHTMLが出力されます。
<input type="text" name="sword" value="入力した検索ワード">
そこで、下記のような検索ワードを入力してみましょう。
-"><script>alert('danger!!')</script>
検索実行後の検索フォームは以下のようになります。
<input type="text" name="sword" value="-"><script>alert('danger!!')</script>">
検索ワード内の最初に出てくるダブルクォート「"」がinputタグのvalue属性を終了させ、そこから新たにJavaScriptを記述したscriptタグを作成しています。このページをブラウザで表示すると、JavaScriptのalert関数が実行され、下記の画面のように「danger!!」というダイアログが表示されます。
▲XSS現象
alertでメッセージを表示するだけのJavaScriptでは実害はありませんが、JavaScriptの内容によっては、クッキー情報の漏洩といった問題も発生しますので対応が必要です。
● XSSへの対応
XSSの発生はHTML内で表示する情報に対してHTMLエスケープをしていないのが原因です。HTMLエスケープとはHTMLの文法上特別な意味を持つ文字を変換することであり、具体的には「<」、「>」、「"」、「'」、「&」といった文字を「<」、「>」、「"」、「'」、「&」に変換します。このように変換することで、先ほどの
-"><script>alert('danger!!')</script>
という文字列をinputタグに入れた場合は、
<input type="text" name="sword" value="-"><script>alert('danger!!')</script>">
となり、scriptタグが無効化され、JavaScriptは実行されません。
そこで、文字列に対してHTMLエスケープを行うhtmlEncode関数を作成しました。関数内では引数で渡した文字列に対してエスケープ対象の文字がある場合に置換処理を行います。この関数は汎用クラスであるAddressbookUtilsクラスに追加します。なお、ここで注意したいのがXMLインジェクションへの対応で利用したxmlEncode関数はHTMLエスケープには利用できないということです。xmlEncode関数ではダブルクォート「"」などはHTMLエスケープと同様に「"」と変換しますが、シングルクォート「'」を「'」と変換するためHTMLでは正しく「'」が表示できません。xmlEncode関数はXML専用の関数であると思ってください。
public static String htmlEncode(String unescape){ //&→& unescape = unescape.replaceAll("&","&"); //"→" unescape = unescape.replaceAll("\"","""); //<→< unescape = unescape.replaceAll("<","<"); //>→> unescape = unescape.replaceAll(">",">"); //'→' unescape = unescape.replaceAll("'","'"); return unescape; } |
この関数を利用して、アイテム一覧画面(itemlist.jsp)の修正した箇所を以下に示します。
<%@page import="addressbook.AddressbookUtils" %> ・・・ <%-- A.検索ワードにhtmlエスケープ処理を行う--%> <table border="0" cellspacing="0" cellpadding="3"> <tr><td>名前: <input type="text" name="sword" value="<%= AddressbookUtils.htmlEncode(param.getSword()) %>"> <input type="submit" value="検索"> </td> ・・・ <%-- B.アイテム情報にhtmlエスケープ処理を行う--%> <td width="100" align="center"><%= AddressbookUtils.htmlEncode(itemlist[i].getID()) %></td> <td width="250" align="center"><%= AddressbookUtils.htmlEncode(itemlist[i].getName()) %></td> <td width="50" align="center"> <a href="/address_book/itemdetail.jsp?id=<%= AddressbookUtils.htmlEncode(itemlist[i].getID()) %>&sword=<%= param.getEncSword() %>">詳細</a></td> |
itemlist.jspでは、AddressbookUtilsクラスをインポートし、検索ワードとユーザが入力した値によって登録されるアイテム情報に対してHTMLエスケープ処理を行いました。ページ番号など他にも表示している変数はありますが、ユーザが故意に変更できない変数ですのでHTMLエスケープ処理は行っていません。
itemlist.jsp以外のjspに対してもHTMLエスケープ処理を行う必要があります。修正の詳細は下記のjspをご覧ください。
修正を行ったjsp
・ confirmitem.jsp
・ inputitem.jsp
・ inputuser.jsp
・ itemdetail.jsp
・ itemlist.jsp
・ userlist.jsp
今回のセキュリティ対応によって、住所録アプリケーションは完成です。XML DBには独特のセキュリティ対応が必要ですので、読者の皆様がXprioriを利用した別のアプリケーションを作られるときはご注意ください。次回はXprioriの特徴である「やわらかいデータベース」の利点を紹介します。
今回作成したjavaクラス・jspファイルはこちらからダウンロードすることができます。
・ jspファイルはWebアプリケーションフォルダのaddress_book/WebContentに配置してください。
・ javaファイルはWebアプリケーションフォルダのaddress_book/WebContent/WEB-INF/srcに配置してください。
また、ご自分でクラスを新規作成する場合はEclipseのメニューバーより、[ファイル]→[新規]→[クラス]を選択し、[次へ]を押してください。
次の画面で[ソースフォルダ]に「address_book/src」、[パッケージ]に「addressbook」を入力し、[名前]に適切なクラス名を入力し、[終了]を押すことで、新規のクラスを作成することができます。
▲このページのTOPへ