在 Dreamweaver 8.0.2 和 CS3 的標準資料錄集內使用動態 SQL 參數

涵蓋內容

解決資料庫安全性漏洞

為了提高 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'

但是,有時候使用者可能會想要入侵網站並竊取敏感資訊和/或破壞資料庫。考量到上方的範例,輸入「blabla' OR '1'='1」字串 (不包含前後括號) 將能夠輕鬆完成 SQL 插入。透過提供此字元序列,使用者將能夠擁有對所有公司的存取權限,而不是單一公司。所執行的實際 SQL 為:

SELECT * FROM company_com WHERE name_com = 'blabla' OR '1'='1'

此外,如果使用者想要刪除整個 company_com 表,只要傳遞「x'; DROP TABLE company_com; --」字串 (不包含前後括號) 即可輕鬆完成。以下是完整的 SQL 查詢:

SELECT * FROM company_com WHERE name_com = 'x'; DROP TABLE company_com; --'

使用以下其中一個措施可避免網站遭到 SQL 插入:

  • 自動移除所有其他不在指定範圍內的字元,將輸入限制為最小的允許字元組。
  • 在 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 伺服器模型的模擬已備妥陳述式

適用於 PHP 的已備妥陳述式是透過 MySQL 4.1 提供,但因為我們也希望支援先前版本的 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 查詢。不當使用此解決方案會讓駭客有機會獲得不必要的權限和/或入侵網頁及資料庫。請參閱 SQL 插入和防止方法來減少 SQL 插入的風險。

我們發現可使用此解決方案的主要案例為:

  • 手動建立/更新 SQL 查詢以接受動態參數。
  • 建立/升級現有擴充功能以產生動態參數。

我們將在接下來的段落中詳細說明這兩個使用案例。

手動建立/更新 SQL 查詢以接受動態參數,同時將資料錄集維持為可從 Dreamweaver 內部編輯

在接下來的範例中,使用者想要新增排序功能到動態表中。他決定重新載入頁面,同時在 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,使其免於受到 SQL 插入的攻擊。

我們提供了一個示範擴充功能,能夠將連線和表做為輸入,並產生顯示所有記錄的動態表。它也會更新所產生的 SQL,使其含有 orderBy 變數,如上方範例所示。此擴充功能是專為 Dreamweaver 8.0.2 和 CS3 所設計。所產生的程式碼不具備萬無一失的安全性,因為目標是使用標準 Dreamweaver 介面來建立含有維持可編輯之動態參數的資料錄集。擴充功能來源程式碼位於使用者的設定檔案夾中。

在下方所有程式碼範例中,都新增了特別標示的區段來啟用透過 URL 參數 sortColsortDir 排序的功能。

ASP_VBS

為 ASP_VBS 伺服器模型產生適當 SQL 查詢的程式碼位在使用者設定檔案夾的「[USER_CONFIGURATION_FOLDER]/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

使用者設定檔案夾中的相同「USER_CONFIGURATION_FOLDER]/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。使用者設定檔案夾中「[USER_CONFIGURATION_FOLDER]/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 插入攻擊,因此需要透過其他方式加以保護。此類保護方法的清單包括但不限於:
    • 確保一般使用者無法使用 SQL 插入方法來入侵網頁和/或資料庫。
    • 在引數達到實際 SQL 之前加以逸出。
    • 即便使用者得以入侵您的程式碼,也請確保他們無法竊取敏感資訊和/或無法入侵資料庫。
  • 有兩個已知問題必須直到發行下一個版本的 Dreamweaver 時才能修正:
    • ColdFusion:在編輯並重新套用時,簡易資料錄集使用者介面會將變數名稱前後的雜湊從 SQL 查詢中移除
    • ColdFusion、ASP_VBS、ASP_JS:SQL 查詢含有變數名稱時,進階資料錄集使用者介面的「測試」按鈕無法使用

尋找使用者的設定檔案夾

  • 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

更快、更輕鬆地獲得協助

新的使用者?