内容
データベースの脆弱性への対応
Dreamweaver 8.0.2 および CS3 のセキュリティを強化するため、標準レコードセット内で動的 SQL パラメーターを使用する機能が削除されました。元のコードには、データベースに対する SQL インジェクションの脆弱性が存在していました。ASP_VBS および ColdFusion サーバーモデルにプリペアドステートメントを使用することで、この脆弱性を解決しました。PHP_MySQL では、最初のレコードセットが適用されたときにページに関数を追加し、その関数をページ上のすべてのレコードセットから呼び出す必要がありました。この方法は ASP_VBS および ColdFusion のプリペアドステートメントに似ています。
この脆弱性を解決するため、機能の一部を廃止し、セキュリティを強化しました。ここでは、動的 SQL クエリを必要とする拡張機能を使用または開発する場合や、レコードセットをカスタマイズして動的パラメーターを利用する際に Dreamweaver 内でカスタムレコードセットを編集できるようにする方法を説明します。
SQL インジェクションとその防止策
SQL インジェクションは、アプリケーションのデータベースレイヤーに存在する脆弱性を悪用する手法です。この問題は、SQL ステートメントに埋め込まれた文字列のエスケープ処理が正しく行われていないか、予期しないユーザー入力が行われたときに発生します。これは、あるプログラミング言語やスクリプト言語が別の言語に埋め込まれると発生する可能性がある一般的な脆弱性の一つです。詳しくは、Wikipedia を参照してください。
標準的な SQL クエリの例:
SELECT * FROM company_com WHERE name_com = '$companyName'
上の例では、$companyName 変数が入力フォームのフィールドから読み取られるので、送信する値をユーザー側で制御することが可能です。たとえば、ユーザーが会社名として Adobe と入力した場合、この SQL クエリは次のようになります。
SELECT * FROM company_com WHERE name_com = 'Adobe'
しかし、この点を悪用して Web サイトに侵入して機密情報を盗んだり、データベースを破壊することも可能です。上記の例では、blabla' OR '1'='1 と入力するだけで、SQL インジェクションを簡単に実行できます。この文字列を入力すると、1 つの会社ではなく、すべての会社のデータにアクセスすることが可能になります。実際には、次のような SQL が実行されます。
SELECT * FROM company_com WHERE name_com = 'blabla' OR '1'='1'
また、x'; DROP TABLE company_com; -- という文字列を渡すだけで company_com テーブル全体を削除できます。この場合、実際には次の SQL クエリが実行されます。
SELECT * FROM company_com WHERE name_com = 'x'; DROP TABLE company_com; --'
SQL インジェクションから Web サイトを保護する方法としては、次のような対策があります。
- 入力可能な文字セットを最小限にし、所定の範囲に含まれていない文字を自動的に削除する。
- SQL クエリ内で問題を引き起こす可能性のある文字(一重引用符など)をすべてエスケープする。
- プリペアドステートメントを使用する。
- データベースのアクセス権を使用する。
- ストアドプロシージャを使用する。
この中でおすすめの対策はプリペアドステートメントの使用です。具体的な使い方は「プリペアドステートメントの使用」で説明します。Dreamweaver 8.0.2 および CS3 では、ASP_VBS、ASP_JavaScript、Cold Fusion、JSP サーバーモデルにプリペアドステートメントを使用できますが、PHP_MySQL サーバーモデルの場合は、ユーザーの入力をエスケープする必要があります。SQL インジェクション攻撃の詳細については、「SQL インジェクション攻撃の例」を参照してください。
プリペアドステートメントの使用
Dreamweaver 8.0.2 および CS ではプリペアドステートメントを使用しています。この方法では、実際の SQL パラメーターを所定の値(URL 経由で送信される可能性がある)に置き換える処理がページ上ではなく、サーバー上で実行されます。プリペアドステートメントでは、SQL に渡される値自体に適切なデータ型が設定され、適切なエスケープが行われます。たとえば、パラメーターが int 型と宣言されている場合、ユーザーは文字を送信できなくなります。また、text 型と宣言されている場合は、渡された文字列全体が適切にエスケープされるので、SQL インジェクションを回避することができます。
ASP_VBS および ASP_JS サーバーモデルのプリペアドステートメント
ASP_VBS と ASP_JSの場合、ADODB データベースレイヤーが必要です。次のコードは、動的 SQL パラメーターを使用して結果をフィルタリングし、シンプルな ASP_VBS レコードセットを表示します。
... <% Dim Recordset1__MMColParam Recordset1__MMColParam = "1" If (Request.QueryString("id_com") <> "") Then Recordset1__MMColParam = Request.QueryString("id_com") End If %> <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com WHERE id_com = ?" Recordset1_cmd.Prepared = true Recordset1_cmd.Parameters.Append Recordset1_cmd.CreateParameter("param1", 5, 1, -1, Recordset1__MMColParam) ' adDouble Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...ColdFusion サーバーモデルのプリペアドステートメント
ColdFusion には、プリペアドステートメントのサポートが組み込まれています。次のコードは、動的 SQL パラメーターを使用して結果をフィルタリングし、シンプルな ColdFusion レコードセットを表示します。
... <cfparam name="URL.id_com" default="1"> <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com WHERE id_com = <cfqueryparam value="#URL.id_com#" cfsqltype="cf_sql_numeric"> </cfquery> ...PHP_MySQL サーバーモデルのプリペアドステートメント
MySQL 4.1で PHP 用のプリペアドステートメントを使用できるようになりましたが、以前のバージョンの MySQL もサポートするため、実質的に同じ機能を持つカスタム関数を実装しました。次のコードは、動的 SQL パラメーターを使用して結果をフィルタリングし、シンプルな PHP レコードセットを表示します。
... <?php if (!function_exists("GetSQLValueString")) { function GetSQLValueString($theValue, $theType, $theDefinedValue = "", $theNotDefinedValue = "") { $theValue = get_magic_quotes_gpc() ? stripslashes($theValue) : $theValue; $theValue = function_exists("mysql_real_escape_string") ? mysql_real_escape_string($theValue) : mysql_escape_string($theValue); switch ($theType) { case "text": $theValue = ($theValue != "") ? "'" . $theValue . "'" : "NULL"; break; case "long": case "int": $theValue = ($theValue != "") ?intval($theValue) : "NULL"; break; case "double": $theValue = ($theValue != "") ? "'" .doubleval($theValue) . "'" : "NULL"; break; case "date": $theValue = ($theValue != "") ? "'" .$theValue ."'" : "NULL"; break; case "defined": $theValue = ($theValue != "") ?$theDefinedValue : $theNotDefinedValue; break; } return $theValue; } } $colname_Recordset1 = "-1"; if (isset($_GET['id_com'])) { $colname_Recordset1 = $_GET['id_com']; } mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = sprintf("SELECT * FROM company_com WHERE id_com = %s", GetSQLValueString($colname_Recordset1, "int")); $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...標準レコードセットでの動的パラメーターの使用
SQL インジェクションを防ぎながら、動的パラメーターで生成されたコードを Dreamweaver のサーバービヘイビアパネルで編集できるようにするには、どうすればよいでしょう。この問題は、動的パラメーターを SQL パラメーターとして渡す(パラメーターをエスケープするか、プリペアドステートメントを介して SQL に追加する)のではなく、SQL クエリ自体に挿入することで解決できます。
重要:Dreamweaver 8.0.2 または CS3 に付属のオブジェクトビヘイビアとサーバービヘイビアを使用して、このようなコードを生成することはできません(例:サーバービヘイビアパネルまたは挿入バーでレコードセットを挿入する)。この解決策では、ユーザーが手動でコードを編集するか、このようなコードを作成するサードパーティの拡張機能をインストールする必要があります。安全なコードを作成する責任は、ユーザーまたはサードパーティのソフトウェア開発者にあります。
この方法は、経験豊富な開発者が動的 SQL クエリを作成できるようにすることを目的としています。この手法を誤って使用すると、ハッカーに不要な特権を与えたり、Web ページやデータベースに侵入される可能性があります。「SQL インジェクションとその防止策」を参照して、SQL インジェクションのリスクを回避してください。
この解決策は主に次のような場合に使用されます。
- 動的パラメーターを受け入れる SQL クエリを手動で作成または更新する。
- 動的パラメーターを生成する拡張機能を作成またはアップグレードする。
これらの使用例については、以下で詳しく説明します。
Dreamweaver でのレコードセットの編集機能を維持しながら、動的パラメーターを受け入れる SQL クエリを手動で作成または更新する
次の例では、動的テーブルにソート機能を追加します。ファイルとソート順(昇順または降順)を URL で渡して、ページをリロードしています。最初の URL は次のようになります。
http://www.mydomain.com/index.php?sortCol=name_com&sortDir=ascサーバービヘイビアを使用して Dreamweaver でこのタスクを実行するには、ハンドコーディングが必要になります。次のサンプルコードで、各サーバーモデルで必要な変更を示します。
重要:以下の例では、SQL インジェクションに対する検証を行っていません。これらの例は、あくまで説明を目的とするものです。実際の環境では使用しないでください。説明を分かりやすくするため、保護対策を行っていません。SQL インジェクションを防ぐ対策を必ず行ってください。詳しくは、「SQL インジェクションとその防止策」を参照してください。
ASP_VBS
変更前:
... <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com" Recordset1_cmd.Prepared = true Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...変更後:
... <% Dim orderBy: orderBy = "" If (Request.QueryString("sortCol") <> "") Then orderBy = "ORDER BY " & Request.QueryString("id_com") If (Request.QueryString("sortDir") <> "") Then orderBy = orderBy & " " & Request.QueryString("sortDir") End If End If %> <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com " & orderBy & "" Recordset1_cmd.Prepared = true Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...ColdFusion
変更前:
... <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com </cfquery> ...変更後:
... <cfparam name="URL.sortCol" default=""> <cfparam name="URL.sortDir" default="ASC"> <cfset orderBy=""> <cfif (#URL.sortCol# NEQ "")> <cfset orderBy="ORDER BY #URL.sortCol# #URL.sortDir#"> </cfif> <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com #orderBy# </cfquery> ...PHP_MySQL
変更前:
... <?php mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = "SELECT * FROM company_com"; $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...変更後:
... <?php $orderBy = ""; if (isset($_GET['sortCol'])) { $orderBy = "ORDER BY " . $_GET['sortCol']; if (isset($_GET['sortDir'])) { $orderBy .= " " . $_GET['sortDir']; } } ?> <?php mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = "SELECT * FROM company_com " . $orderBy . ""; $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...Dreamweaver でのレコードセットの編集機能を維持しながら、動的パラメーターを生成する拡張機能を作成またはアップグレードする
上記と同じシナリオで考えてみましょう。この場合は、組み込みの機能を使用して手動で編集するのではなく、適切なコードを生成する拡張機能を作成します。新しい拡張機能では、ASP_VBS、ColdFusion、PHP_MySQL の各サーバーモデルに対応する正しいコードが生成されます。
重要:以下では、URL パラメーターの値を使用して動的 SQL クエリを作成するコードの生成方法を中心に説明します。Dreamweaver で認識され、標準の Dreamweaver インターフェイスで編集可能なコードを生成する方法について説明します。このチュートリアルでは、入力データの検証(URL パラメーターを使用)や、SQL インジェクションから最終的な SQL を保護する方法について説明しません。このようなコードは複雑になり、このチュートリアルで扱う範囲を超えているためです。SQL インジェクションを防ぐ対策は、開発者の責任で行う必要があります。
以下で説明する拡張機能のサンプルは、入力として接続とテーブルを使用し、すべてのレコードを表示する動的テーブルを生成します。また、前述の例のように、orderBy 変数を含む SQL を生成します。この拡張機能は、Dreamweaver 8.0.2 および CS3 用に設計されています。ここでは、動的パラメーターを含み、標準の Dreamweaver インターフェイスで編集可能なレコードセットを作成することを目的としているため、生成されるコードに保護対策は行われていません。拡張機能のソースコードは、ユーザーの構成フォルダーにあります。
以下の例では、URLパラメーター sortCol と sortDir を使用してソート機能を追加した部分を抜粋しています。
ASP_VBS
ASP_VBS サーバーモデルに適切な SQL クエリを生成するコードは、ユーザーの構成フォルダーの [ユーザーの構成フォルダー]/Commands/My Dynamic Table.js ファイルに含まれています。主な変更点は 130 行目です。
... 130: paramObj.encodedSQL = "SELECT * FROM " + paramObj.table + " \" & orderBy & \""; ...MyDynamicTable ASP_VBS サーバービヘイビアの関連ファイルには、ページに orderBy 変数定義を追加する関連項目が追加されています。
<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="connectionref_statement" /> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="recordset_main" /> <groupParticipant name="repeatedRegion_init2" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="recordset_close" /> </groupParticipants> </group>ColdFusion
ユーザーの構成フォルダーの同じ [ユーザーの構成フォルダー]/Commands/My Dynamic Table.js に、ColdFusion サーバーモデルのコードも含まれています。この変更は 138 行目にあります。
... 138: paramObj.SQLStatement = "SELECT * FROM " + paramObj.table + " #orderBy#"; ...MyDynamicTable ColdFusion サーバービヘイビアの関連ファイルには、ページに orderBy 変数定義を追加する関連項目が追加されています。
<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="Recordset_main" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="RepeatedRegion_pageNum" /> <groupParticipant name="RepeatedRegion_maxRows" /> <groupParticipant name="RepeatedRegion_startRow" /> <groupParticipant name="RepeatedRegion_endRow" /> <groupParticipant name="RepeatedRegion_totalPages" /> </groupParticipants> </group>PHP_MySQL
最後のサーバーモデルは PHP_MySQL です。ユーザーの構成フォルダーの [ユーザーの構成フォルダー]/Commands/My Dynamic Table.js ファイルの 122 行目に、関連する変更があります。
... 122: paramObj.SQLStatement = "SELECT * FROM " + paramObj.table+ " \" . $orderBy . \""; ...MyDynamicTable PHP_MySQL サーバービヘイビアの関連ファイルには、ページに orderBy 変数定義を追加する関連項目が追加されています。
<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="Connection_include" /> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="EditOps_SQLValueString" /> <groupParticipant name="Recordset_main" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="Recordset_close" /> </groupParticipants> </group>長所
ここで説明した方法には、次のような利点があります。
- 経験豊富な開発者は、SQL インジェクションの脆弱性を利用して、より複雑な SQL を動的に構築することができます。
- 生成されたレコードセットを引き続き Dreamweaver から編集できます。
- 動的 SQL パラメーターを含むレコードセットを生成する既存の Dreamweaver 拡張機能にも統合できます。
- Dreamweaver で生成されるデフォルトのコードは変更されません。動的クエリ(およびクライアント)を必要としない開発者は、Dreamweaver で安全なコードを作成できます。
短所
Dreamweaver は、この方法をデフォルトでサポートするように設計されていないため、次のような欠点もあります。
- ここで説明した方法で生成されるコードは、SQL インジェクションに対して脆弱です。別の方法で保護する必要があります。たとえば、次のような方法で保護します。
- エンドユーザーが Web ページやデータベースに SQL インジェクションで侵入できないようにします。
- 実際の SQL が処理される前に、引数をエスケープします。
- コードの挿入に成功しても、機密情報の収集やデータベースへの侵入ができないように対策を行います。
- 今後のバージョンの Dreamweaver で解決する予定の既知の問題が 2 つあります。
- ColdFusion:単純なレコードセット UI で編集後に再適用すると、変数名を囲むハッシュが SQL クエリから削除される。
- ColdFusion、ASP_VBS、ASP_JS:SQL クエリに変数名が含まれている場合、詳細レコードセット UI の「テスト」ボタンが機能しない。
ユーザーの構成フォルダーの場所
- Windows XP
C:\Documents and Settings\[ユーザー名]\Application Data\Adobe\Dreamweaver 9\Configuration
- Windows Vista
C:\Users\[ユーザー名]\AppData\Roaming\Adobe\Dreamweaver 9\Configuration
- Mac OS 10.4.x
/Users/[ユーザー名]/Library/Application\Support/Adobe/Dreamweaver\9/Configuration