前回に引き続き患者Aさんの話をしましょう。前回のプログラムの要約をもう一度見てみましょう。
<cfform> アーティストIDを入力(数字): <cfinput type="text" name="AID"> <cfinput type="submit" name="submit" value="作品検索"> </cfform> <cfif IsDefined("Form.submit")> <cfquery datasource="cfartgallery" name="qArt"> SELECT * FROM ART WHERE ARTISTID = #Form.AID# </cfquery> <cfoutput query="qArt"> #qArt.ARTNAME#(#qArt.DESCRIPTION#)<br> </cfoutput> </cfif>
今回はSUBMITした後の処理、つまり <cfif>〜</cfif>で囲まれたデータベース処理を見て行きます。前回のページで入力をチェックするプログラムを追加したからそれで十分じゃないか?と思うかもしれません。
しかしそれは正しい判断ではありません。なぜならユーザーは、フォームページからSUBMITする方法以外でデータを送信してくる可能性もあるからです。
加工(偽装)した値を送りつける方法は色々あります。例えば下記は、FireFoxのアドオンツールを利用して、2つのフォーム変数(Form.AID=2、Form.submit=作品検索)をPOSTで送信した例です。
▲指定したURIに対して、パラメーターと適切なヘッダーを付ける事によってPOSTを行っています。その結果は、[Server Response]のところに戻されています。
その他にも、例えばColdFusionで <cfhttp method="post"> タグを使ったページを作成してそのページを実行ことでも指定したページにPOSTを行うことができます。色々な方法でそのページにデータを送りつけることができるのです。
それでは改めてデータベース処理が行われる箇所を確認してみましょう。<cfif>タグによるForm変数のsubmitの存在チェックはしていますが、それ以外の変数の存在や受け取った値のチェックが行われていません。受け取った値をそのままデータベース処理に利用していて、渡されてくる値の中身に対する備えが行われていません。
そこで今回は、適切なSQLを実行させるための備えについてを考えていきましょう。
現在のSQLで一番注意する点は、ユーザーから受け取った値をダイレクトにSQLの条件文に指定している点です。プログラムでは、ユーザーが入力した「アーティストのID」を条件にForm変数のAIDという値を使用していますが、例えば、上記のツール等を利用して、Form.AIDの値を数字ではなく、“1 or 0=0”を送りつけた場合、どういった処理になるでしょうか?
今のプログラムの書き方では、下記のようなSQL文が実行される事になります。
SELECT * FROM ART WHERE ARTISTID = 1 or 0 = 0
0 = 0 は常に一致する条件です。これが OR 条件に指定されているという事で、アーティストIDを絞り込んだ結果だけを取得するはずが、全てのレコードを取得する SQL 文として実行されてしまう事になります。これはSQLインジェクションと呼ばれる攻撃につながり、対策を怠ると予期しないSQLの実行によって、データの改変や漏えいにつながる懸念が生じます。
渡されてきた値をそのまま使うのではなく「渡されてきた値が適切かどうかをチェックする」ことと、「SQL文にダイレクトに値を渡さないようにする」ことを意識します。
まず、渡されてきた値が適切かどうかをチェックするには、ColdFusionのタグ<cfif>と目的に合った関数を組み合わせて使うと良いでしょう。今回の場合は、
が条件です。これに対応する関数には、IsDefined()関数 や IsNumeric()もしくはIsValid()関数(整数かどうかのチェックしたい場合)があります。
例えば、
のプログラムの箇所に条件を追加します。
とする事で、AIDというForm変数が存在し、かつ整数である事という条件を追加することができます。ColdFusionにはIs〜という結果がTrue(Yes)かFalse(No)で返ってくる決定関数と呼ばれるものや、YesNoFormat関数、FileExists、DirectoryExists、StructKeyExists関数など<cfif>タグと組み合わせて条件式に使用することができる関数が数多くありますので、目的に応じて使用下さい。
※<cfif>タグで条件式を記載する際、True/Falseで帰ってくる値の場合は、式の部分を省略することができますので、例えば <cfif IsDefined("Form.submit") is True AND ...>と書かなくても、<cfif IsDefined("Form.submit") AND ...>のように is True を省略できます。
次に、SQL文にダイレクトに値を渡さないようにするについてです。SQL文の書きかたの一つにプリペアードステートメントと呼ばれる記述方法があります。値を指定する箇所(今回の場合 Form.AIDを参照している所)に?というパラメーターを設定し、“?”に対応する値をSQL文とは分けて指定する方法です。SQL文を事前に固定化(プリペアード)し、割り当てる値(Form.AID)を指定する方法によって、上記のようにForm.AIDの値にSQLの式が含まれていてもただの文字列として扱うことができ、SQLインジェクションのリスクを軽減できます。
ColdFusionでは<cfquery>内のSQL文でパラメータ化したい箇所に<cfqueryparam>タグを利用します。<cfqueryparam>タグには、データの検証を行う属性(cfsqltype)があり、value属性に指定された値のチェックとデータベースのデータ型の種類に応じた処理を行います。
接続先のデータベースにこの今回のSQL文を変更した例を紹介します。
<cfquery datasource="cfartgallery" name="qArt"> SELECT * FROM ART WHERE ARTISTID = <cfqueryparam value = "#Form.AID#" cfsqltype="cf_sql_integer"> </cfquery>
上記のSQLを実行した際のColdFusionデバッグ情報は下記のようになります(Apache Derbyデータベースに接続しています)。
ColdFusion 11からはQueryExecute()関数でも同様の記述が可能です。以下がその例です。
<cfscript> qArt=QueryExecute("SELECT * FROM ART WHERE ARTISTID = ?", [{value=#Form.AID#, CFSQLType='cf_sql_integer'}], {datasource="cfartgallery"}); </cfscript>
今回は SQLのエラーを防ぐことを目的に、ベーシックなやり方を紹介していますが、Webサイトの安全性として考えた場合には、さらなる対策を検討します。
例えば、ColdFusionには、割符のようなチェック機能があります。それを使ってForm画面からデータをSUBMITして来ているかなどをチェックする事も可能です。さらに、データベースから取り出したデータをダイレクトに画面に表示している箇所も、データに特殊文字等が含まれていて、正しく表示されない場合やセキュリティの向上を目的に、データをHTMLエンコードしてから表示するといった事も必要となるかもしれません。
Webサイトの安全性については、今後、セキュリティに関する説明で紹介する予定です。
どこまでセキュリティを意識するかについては、運用される場所やシステムを利用する人にも依存するかと思います。ただ、プログラムの品質を高めるという点で、今回のような備えは常に意識したプログラミングを心がけると良いでしょう。
最後に、これまでの内容をまとめたプログラムを下記にご紹介します。入力側のチェック、処理側のチェックそれぞれを参考にして下さい。
<cfform> アーティストIDを入力(数字): <cfinput type="text" name="AID" required="true" validate="integer" message="アーティストIDは数字で入力して下さい"> <cfinput type="submit" name="submit" value="作品検索"> </cfform> <cfif IsDefined("Form.submit") AND IsDefined("Form.AID") AND IsValid("integer",Form.AID)> <cfquery datasource="cfartgallery" name="qArt"> SELECT * FROM ART WHERE ARTISTID = <cfqueryparam value = "#Form.AID#" cfsqltype="cf_sql_integer"> </cfquery> <cfoutput query="qArt"> #qArt.ARTNAME#(#qArt.DESCRIPTION#)<br> </cfoutput> <cfelse> アーティストIDを入力後「作品検索」ボタンを押して下さい<br> </cfif>