涵盖的主题
解决数据库安全漏洞
为了提高 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 使用预备语句,因为此解决方案可保证在服务器(而非页面)上使用给定值(可能通过 URL 发送)替换实际的 SQL 参数。预备语句还保证传递给 SQL 本身的值具有适当的数据类型,并使用相应的转义(如果参数的类型为整型,则用户将无法提交字母,或者如果参数的类型为文本,则整个传递的字符串将正确转义,并且用户将没有机会执行 SQL 注入)。
适用于 ASP_VBS 和 ASP_JS 服务器模型的预备语句
对于 ASP_VBS 和 ASP_JS,我们依赖 ADODB 数据库层。以下示例显示了一个简单的 ASP_VBS 记录集,该记录集使用动态 SQL 参数来过滤结果:
... <% 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 为预备语句提供内置支持。以下示例显示了一个简单的 ColdFusion 记录集,该记录集使用动态 SQL 参数来过滤结果:
... <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 的早期版本,因此我们决定实施一个几乎执行相同操作的自定义函数。以下示例显示了一个简单的 PHP 记录集,该记录集使用动态 SQL 参数来过滤结果:
... <?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); ?> ...允许在标准记录集内使用动态参数
那么,在仍允许使用动态参数并保持可通过 Dreamweaver 中的“服务器行为”面板编辑生成的代码的同时,我们如何防止 SQL 注入?解决方案是将动态参数插入 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 参数 sortCol 和 sortDir 启用排序功能。
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 中编辑。
- 可轻松地集成到现有 Dreamweaver 扩展中,以生成包含动态 SQL 参数的记录集。
- 由 Dreamweaver 生成的默认代码保持不变;无需动态查询(及其客户端)的开发人员可以确保 Dreamweaver 编写的代码是安全的。
缺点
由于默认情况下 Dreamweaver 不支持此方法,因此它也有一些缺点,包括:
- 使用本文档中描述的方法生成的代码易受 SQL 注入攻击,因此需要采用其他方式进行保护。此类保护方法的列表包括但不限于:
- 确保最终用户不能使用 SQL 注入方法来破坏网页和/或数据库;
- 在参数到达实际 SQL 之前转义参数;
- 确保即使用户成功破解了您的代码,也无法窃取敏感信息和/或破坏数据库。
- 在发布 Dreamweaver 的下一版本之前,无法修复以下两个已知问题:
- ColdFusion:编辑 SQL 查询后重新应用时,简单记录集 UI 会从 SQL 查询中删除变量名称周围的哈希
- ColdFusion、ASP_VBS、ASP_JS:当 SQL 查询包含变量名称时,高级记录集 UI 中的“测试”按钮不起作用
查找用户配置文件夹
- Windows XP
C:\Documents and Settings\[用户名]\Application Data\Adobe\Dreamweaver 9\Configuration
- Windows Vista
C:\用户\[用户名]\AppData\Roaming\Adobe\Dreamweaver 9\Configuration
- Mac OS 10.4.x
/用户/[用户名]/资源库/Application Support/Adobe/Dreamweaver 9/Configuration