涵盖的主题
解决数据库安全漏洞
为了提高 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\[username]\Application Data\Adobe\Dreamweaver 9\Configuration
- Windows Vista
C:\Users\[username]\AppData\Roaming\Adobe\Dreamweaver 9\Configuration
- Mac OS 10.4.x
/Users/[username]/Library/Application\ Support/Adobe/Dreamweaver\ 9/Configuration