SQL 注入攻击似乎有些歇斯底里。最近,这里
如果我在 Excel 中创建一个连接到 Access 数据库的宏,我真的需要担心 SQL 注入吗?它不在网络上,而是在我的办公室使用(你们还记得台式机吗?)。我不担心我的同事会破坏我。如果他们足够聪明,可以进行 SQL 注入,难道他们还不够聪明,可以破解我的插件密码并更改代码吗?
SQL 注入攻击似乎有些歇斯底里。最近,这里
如果我在 Excel 中创建一个连接到 Access 数据库的宏,我真的需要担心 SQL 注入吗?它不在网络上,而是在我的办公室使用(你们还记得台式机吗?)。我不担心我的同事会破坏我。如果他们足够聪明,可以进行 SQL 注入,难道他们还不够聪明,可以破解我的插件密码并更改代码吗?
如果您在宏中构建 SQL,它很容易受到 SQL 注入的影响。即使您信任将使用该东西的人,您也应该至少注意基础知识,例如尝试将单引号和分号字符放入数据库字段的人。在您的情况下,这不仅仅是数据验证的安全问题。
SQL 注入不仅是一种安全威胁,它也是一个非常真实的错误来源。
您确定您的任何记录都不会包含撇号 (') 吗?
INSERT INTO NAMES (FIRSTNAME, LASTNAME) VALUES('Jack', 'O'Neill')
在这种情况下,即使没有人想破解您的系统,您也会遇到错误。
我想扩展我在上面针对 onedaywhen 的帖子所做的评论,该帖子概述了如何利用 MS Access 中的 SELECT 语句。请记住,这些不是关于如何防止 SQL 注入的概括性评论,而是专门适用于 MS Access 中的编程。
我从来没有见过任何 Access 的示例代码,它会允许利用 onedaywhen 概述的那种 SELECT 漏洞。这样做的原因是,几乎从来没有这样的情况,您会使用如此简单的方法来收集标准,而无需在途中某处对输入进行一些验证,不是为了避免 SQL 注入,而是为了避免由无效 SQL 引起的错误。
这是实现此最简单版本的代码:
Public Sub TestSQLExploit()
Dim strSQL As String
strSQL = "SELECT tblInventory.* FROM tblInventory WHERE InventoryID = "
strSQL = strSQL & InputBox("Enter InventoryID")
Debug.Print strSQL
End Sub
因此,传递 "10036 or 'a' = 'a'" 会产生以下 SQL:
SELECT tblInventory.*
FROM tblInventory
WHERE InventoryID=10036 Or 'a'='a'
而且这绝对不好!
现在,我永远不会那样写我的代码,因为我总是希望允许多个值。相反,如果我使用 InputBox() 函数来收集用户输入(老实说,我从来没有这样做过,因为它太难以验证),我会使用 Application.BuildCriteria 来编写 WHERE 子句,因为这样我就可以处理多个标准值。这将导致以下代码:
Public Sub TestSQLExploit1()
Dim strSQL As String
Dim strWhere As String
strSQL = "SELECT tblInventory.* FROM tblInventory "
strWhere = "WHERE " & Application.BuildCriteria("tblInventory.InventoryID", _
dbLong, InputBox("Enter InventoryID"))
strSQL = strSQL & strWhere
Debug.Print strSQL
End Sub
老实说,我认为 Application.BuildCriteria 会对此抛出错误,但事实并非如此,并且当通过“10036 or 'a' = 'a'”时会产生完全相同的 SQL。正如您所说,由于 Jet 表达服务的工作方式,它将是完全开放的。
现在,我从来没有真正写过这样的动态 SQL,因为我只是不喜欢 InputBox() 函数,正是因为你必须编写一堆代码来验证输入。如果你像上面的代码那样使用它,你必须做很多事情来确保它是有效的。
我从未见过任何不建议使用参数化 SQL(这当然可以避免问题)或 Query-By-Form 接口的此类操作的 Access 代码示例。我通常不会在 Access 中使用保存的参数查询,因为我喜欢将保存的查询编写为在任何地方都可以使用。这意味着它们大多没有具有在运行时更改的标准的 WHERE 子句。当我使用这些保存的查询时,我会为适当的情况提供 WHERE 子句,无论是作为表单中的记录源还是作为列表框或下拉列表的行源。
现在,这里的重点是,在这些情况下,我不是要求用户输入,而是从 Access 对象(例如表单上的控件)中提取标准值。现在,在大多数情况下,这将是表单上的一个控件,它只有一个目的——为某种形式的过滤收集标准。该表单上不会有未经验证的自由文本字段——日期字段将具有输入掩码(这会将输入限制为有效日期),并且有效值数量有限的字段将具有将选择限制为有效的控制类型数据。通常这类似于下拉菜单或选项组。
这种设计的原因不一定是为了避免 SQL 注入(尽管它会阻止这种情况),而是为了确保用户不会因输入无效且不会产生任何结果的条件而感到沮丧。
现在,另一个考虑因素是,有时您确实希望使用一些纯文本字段,以便用户可以输入某种尚未受到限制的数据(例如查找姓名)。只是看看我的一些应用程序的名称查找例程和未经验证的文本字段,我发现我很好,因为在这些情况下我不使用 BuildCriteria,因为它旨在一次只收集一个标准(尽管用户可以输入“*”来检索多条记录)。
如果我有一个文本框,用户在其中输入“fent* or 'a' = 'a'”,并且我在 WHERE 子句中使用它:
WHERE tblDonor.LName Like "fent* or 'a' = 'a'"
结果是什么都找不到。如果用户输入“fent* 或 a = a”,它仍然不起作用,因为它是一个文本字段,我在它周围使用了双引号。如果用户输入:
fent* or "a" = "a"
这也会中断,因为当我的代码在它周围加上双引号时,WHERE 子句将无效。
现在,在只使用 use 输入并在其周围加上双引号的情况下,很明显输入以下内容:
" Or "fent*" or "a" = "a" Or "
会导致:
WHERE tblDonor.LName Like "" Or "fent*" or "a" = "a" Or ""
那将是非常糟糕的,因为它会返回所有内容。但是在我现有的应用程序中,我已经从用户输入中清除了双引号(因为双引号在 LName 字段中理论上是有效的),所以我的应用程序构造了这个 WHERE 子句:
WHERE tblDonor.LName Like "? Or ?fent*? or ?a? = ?a? Or ?*"
那不会返回任何行。
但它没有的原因不是因为我试图避免 SQL 注入,而是因为我希望用户能够查找其中嵌入了双引号的名称。
======
一些结论:
过滤数据时从不接受用户的自由格式输入——相反,使用预先验证输入的控件(例如,带有输入掩码的文本框、下拉列表、选项组)并将其限制为您知道有效的值。
当从没有限制的文本框中接受数据时,请避免使用 Application.BuildCriteria,它会以用户可以欺骗您的应用程序返回所有行的方式处理输入(尽管这是漏洞可以做的范围)。
这实际上意味着,如果要收集多个标准,则需要以用户只能从预选值中进行选择的方式进行。最简单的方法是使用多选列表框(或一对带有 ADD>> 和 <<REMOVE 命令按钮的列表框)。
当然,您是否需要担心这种 SELECT 漏洞利用取决于所检索数据的重要性和隐私级别,以及返回给用户的确切内容。当以不可编辑的形式(例如,报告)呈现数据时,冒着返回所有非敏感数据行的风险可能没有问题,而如果您以可编辑的形式呈现它并且有人更改了不应该的数据,则可能会出现问题。 t 被编辑。
但是对于非敏感数据,用户返回的数据是否过多通常根本无关紧要(性能问题除外,例如,服务器超载——但最好以其他方式处理)。
所以,我对这一切的看法:
永远不要使用 InputBox() 来收集标准(这个我已经避免了)。
始终使用尽可能限制性的控制类型来收集标准(这已经是我经常做的事情)。
如果使用文本框来收集字符串数据,无论用户输入什么,都将其视为单一标准。
这确实意味着我有一些应用程序,用户可以在其中输入“或'a'='a'”以及有效的标准并返回所有行,但在这些应用程序中,这根本不是问题,因为数据不敏感。
但这很好地提醒我不要自满。我原以为 Application.BuildCriteria 会保护我,但现在意识到 Jet 表达式服务在 WHERE 子句中接受的内容过于宽容。
2009/12/08 编辑:刚刚在 MS Access 中的 SQL 注入中找到了这些链接。所有这些都是针对 Web 注入的,因此不能直接适用于非 Web SQL 注入的讨论(其中许多在交互式 Access 中会浪费时间,因为您已经可以访问大量的蛮力信息 -例如,有关文件系统、路径、可执行文件等的信息),但许多技术也适用于 Access 应用程序。此外,从 Access 执行会打开许多无法从 ODBC/OLEDB 运行的功能。深思熟虑。
老实说,如果这是您正在谈论的现有应用程序,我不会去重写它。但是,如果您正在开发它,我认为使用参数化查询而不是替代方法很难。
作为开发人员,您有责任(如果不是全部的话)至少部分地对您的应用程序所持有的数据的安全性负责。
无论您的应用程序是在线的还是仅在您的办公室使用,都是如此。尽一切可能确保您的数据存储是密封的。
归根结底,您不想成为必须向老板解释去年销售数据去向的人。
物理安全始终是数据安全的第一道防线。如果您的应用程序只打算在办公室内分发,并且访问的数据价值不足以让某人去麻烦和窃取和破解的费用,那么您可以遵守比在外部使用更低的安全标准 -面向 Web 应用程序。
但是,真正的安全最终是关于可能发生的事情,而不是我们期望发生的事情。如果您的应用程序处理公众委托给您的数据(SSN、信用卡号等),或者如果它是对您的公司至关重要的唯一数据存储库,则您必须考虑潜在的恶意用户可能对您的代码执行哪些操作未来。今天快乐的员工就是明天心怀不满的反社会者。
一个好的经验法则是问问自己:如果我对这个产品有充分的了解,想用它来伤害我的公司,我能造成多大的损失?然后,建立足够的安全性以将该数字降低到可容忍的水平。
不。(是的。)是的。:)
很多时候,我看到开发人员在加固“前门”上浪费了宝贵的资源,却没有注意到后面的摆动纱门。这通常类似于将前端强化到不安全的后端,强化基本上对不同用户开放的应用程序等等......
就安全性做出一揽子声明固然很好,但它必须符合要求。
IMO 如果您的系统将暴露给可能希望造成伤害的人(例如在 Internet 上),那么您确实应该防止 SQL 注入。
另一方面,如果它是一个内部系统,任何可以访问 SQL 注入的恶意用户也可能以其他方式损害它,那么它真的没那么重要。
我自己编写了易受 SQL 注入攻击的代码,但唯一拥有这种访问权限的人是拥有 SQL 访问权限的同事。
尽管我们都希望应用程序不受任何和所有攻击的影响,但开发所有防弹功能所花费的时间必须与额外的好处一起权衡。如果您可以合理地预期安全要求不会很高,那么这可能是您可能想要传递的东西。如果您认为这可能是值得担心的事情,也许您现在应该采取措施防止这种可能性,而不必再担心了。
如果我在 Excel 中创建一个连接到 Access 数据库的宏,我真的需要担心 SQL 注入吗?
也许。这取决于,真的。我个人不会担心,但是您要存储什么样的数据,它的敏感性是多少?
如果他们足够聪明,可以进行 SQL 注入,难道他们还不够聪明,可以破解我的插件密码并更改代码吗?
也许。仅仅因为有人可以进行 sql 注入并不意味着他们足够聪明,可以破解您的加载项密码。另一方面,他们可能是。
有人可以使用 Jet 数据库作为后端发布用于 SQL 注入的上下文证明 Excel VBA 代码吗?或者在如何根据另一个字段中的查找值返回一个字段中的值(而不是仅仅破坏代码)中准确地演示哪些参数可以传递给代码?
鉴于 Jet 无法执行由“;”分隔的多个 SQL 语句,我很难想象任何带有 Jet 后端的 SQL 注入威胁。但也许那是因为我不像黑客那样富有想象力。
哦,一个免费的线索(关于传统 SQL 注入以外的危险主题):
Access 表达式服务无法通过 ODBC 获得。
它可以通过 DDE 获得,但我不知道您是否可以通过 DDE 将 SQL 传递给 Access(我在大约 10 年内没有将 DDE 与 Access 一起使用)。
如果您对 Access 和 Jet 表达式服务一无所知,那么您可能没有资格回答有关 Jet(和 Access)的问题。
迪克,这取决于你如何处理参数。这是一个如何不做事的 VBA 示例:
Friend Function DeleteAnAccount() As Boolean
Const SQL_DELETE_AN_ACCOUNT As String * 50 = _
"DELETE FROM Accounts WHERE account_owner_ID = '?';"
Dim sql As String
sql = Replace$(SQL_DELETE_AN_ACCOUNT, "?", txtAccountOwnerID.Text)
m_Connection.Execute sql
End Function
考虑一下,如果某些人不是在文本框 (txtAccountOwnerID) 中输入他们的帐户 ID,而是实际输入了以下内容:
dummy' OR 'a' = 'a
那么生成的 SQL 字符串将是这样的:
DELETE FROM Accounts WHERE account_owner_ID = 'dummy' OR 'a' = 'a';
不好,因为'a' = 'a'
谓词将解析为TRUE
并且所有帐户都将被删除。
更好的是使用使用参数对象的准备好的语句,例如 ADODB.Command 对象。
杰米。
--
三点:
使用参数化查询通常比逃避可能破坏 SQL 的方法(例如,O'Neill 先生)更少工作,这样您就可以直接将数据连接到查询字符串中。如果更强大的选项也可以减少实施工作,那么您为什么不想这样做呢?
我已经很久没有使用 Jet 了,所以我不知道它现在是否支持预先准备好的语句,但是,如果您要多次运行该语句,请使用参数化查询并重新运行它具有不同的参数将比每次构建新查询更快。
即使所有用户都是 100% 值得信赖的,并且永远不会因为不满而试图造成任何损害,但始终存在拼写错误或其他真正错误的可能性。防止用户错误通常被认为是一件好事。
因此,您绝对应该使用参数化查询,如 Spolsky 对另一个问题的回复所示,即使不是为了安全起见。它们不仅更安全,而且更耐错误,通常编写速度更快,并且对于重复查询具有更高的性能。
>使用参数化查询并使用不同的参数重新运行它会比每次构建新查询更快。
实际上,如果您谈论查询性能,它不会提高 jet 的性能。事实上,从 JET 白皮书《性能概述和优化技术》中,我们得到了这个宝石:
第 18 页
由于存储查询具有预编译的查询计划,因此在索引列上包含参数的参数化查询可能无法有效执行。由于查询引擎事先不知道要传入参数的值,因此只能猜测最有效的查询计划。根据我们检查过的客户性能场景,我们发现在某些情况下,可以通过将存储的参数化查询替换为临时查询来实现显着的性能提升。这意味着在代码中创建 SQL 字符串并将其传递给 DAO OpenRecordset 或 Database 对象的 Execute 方法
整洁哦?而且,我经历过以上!
请记住,查询计划的编译时间无论如何都在 1000 秒内。我的意思是,实际上,查询计划时间从 0.01 到 0.0001。当然,它快了 100 倍,但这只是为我们节省了 100 分之一秒。我们运行一个需要 2 秒的报告,因此查询计划时间甚至不是问题。
我们今天有 GOBS 的处理。磁盘驱动器、内存和网络 I/O 速度是瓶颈。我们也没有为提交给 JET 的每个新 sql 字符串浪费服务器 sql 查询缓存的问题。无论如何,这些内联 sql 查询计划都不会被缓存。而且,更重要的 JET 是基于客户端的引擎,因此当您的办公室局域网上有 10 个用户时,每台机器上都有 10 个 JET 副本在本地运行。查询计划缓存不是 sql server 的问题。
正如上面的 jet 白皮书所示(以及我的经验),通过强制重新编译不带参数的 sql 来获得更好的查询计划的好处超过了具有参数的预编译查询计划的好处。
但是,要保持正轨,必须同意大卫的观点。我不认为当您使用 odbc 时,或者在这种情况下使用 dao 对象模型 + jet,我无法想出任何方式来注入真正的 sql 语句。
也许可以通过上面的“蹩脚” InputBox() 示例输入可能产生意外结果的条件。正如所指出的,内置访问的应用程序并不经常以这种方式工作。
对于删除记录之类的事情,您正在查看一个表单,它将有一个自定义菜单栏(或现在的功能区),或者只是一个放置在表单上的删除按钮。因此,用户不能为这种类型的删除代码输入错误数据。
更重要的是,当我们经常接受用户在表单上的输入时,请记住我们的表单具有内置的数据掩码。毕竟,这正是 MS Access 的设计初衷。因此,如果我们要求输入电话号码,则用户不能为该输入掩码输入字母甚至任何非数字字符。该掩码甚至会在该电话号码的适当位置放置 () 和 – 以进行显示,但只有数字才会出现在用户的实际输入中。
对于大多数其他类型的提示,我们使用组合框、lisbox 和其他 UI 元素,这再次限制了用户将表单允许的内容以外的内容注入该文本框中的能力。
由于如此丰富的屏蔽和输入能力远远超出了大多数屏幕构建器,因此注入对于基于 MS 访问的应用程序来说是很少见的话题。
如果有人可以展示一个 JET 示例,其中用户可以通过注入执行 sql 语句,我全神贯注,因为我认为 dao + jet 不可能。
对于一个 MS-access 应用程序来说,这可能是可能的,但再次强调,在实际实践中,非常困难。