SQL注入:深度防御

尽管SQL Injection给数据安全带来了威胁,但许多程序员和DBA还是不知道它,或者不知道如何适当地防止它。这部分是因为在正规教育中很少谈论SQL注入及其防止方法:我经历了两门关于数据库理论的课程,几本关于SQL Server的书,以及一个MCDBA,然后才通过“ “动态SQL的诅咒和祝福 ”,作者Erland Sommarskog。

因此,SQL注入仍然是常见且有效的攻击。在最近的一个重要案例中,即使是一家致力于安全的公司,也至少部分受到了SQL注入攻击的危害(请参阅“ 匿名发言:HBGary hack的内幕”),向业界提供了有益的经验教训,以解决可能出现的问题。

关于防止SQL Server注入SQL Server的文章已经很多。但是,很少有人强调指出,针对此类攻击的最佳防御是纵深防御,并采取各种预防措施。这些文章中的许多文章几乎都集中在参数化SQL作为防御SQL注入的防御上。尽管参数化是抵御SQL注入的最佳方法,但它并不是唯一的方法。因此,我决定再添加一个清单,以检查各种防御层并使用python作为示例。

什么是SQL注入?

SQL注入攻击是通过传递特殊格式的字符串作为输入来进行的。在成功的攻击中,那些特殊的字符串将被传递到数据库以执行任意代码或导致服务器返回意外结果。例如,如果我们有一个使用pyodbc的python程序,它将用户输入连接到这样的SQL查询中:

  userInput = "'; Drop Table Test; --"

 

 

conn = pyodbc.connect(connString)

curs = conn.cursor()

 

sql = ("Select City, State from dbo.ZipCodes where zipcode = '"

        + userInput +"'")

 

curs.execute(sql)

conn.commit()

然后,精心格式化邮政编码条目的恶意用户可以执行意外的SQL命令。例如,如果用户提供了:

  '; Drop Table Test; --

然后,探查器将显示服务器将收到:

  Select City, State from dbo.ZipCodes where zipcode = ''; Drop Table Test; --'

假设程序具有适当的权限,则服务器会乖乖地删除测试表。

防止SQL注入的基本技术 参数化所有查询

抵御SQL注入的最好的第一道防线是在代码中参数化所有SQL查询。如果先前使用pyodbc的示例已被参数化,则它可能类似于:

  userInput = "'; Drop Table Test; --"

 

 

conn = pyodbc.connect(connString)

curs = conn.cursor()

 

sql = "Select City, State from dbo.ZipCodes where zipcode = ?"

 

curs.execute(sql, (userInput,))

conn.commit()

这使分析器接收到很多消息,但是关键部分是:

  exec sp_prepexec @p1 output,N'@P1 varchar(22)',N'Select City, State from dbo.ZipCodes where zipcode = @P1','''; Drop Table Test; --'

由于服务器将恶意代码作为变量接收,因此服务器只需在表中查找值并返回空白结果集。永远不会执行恶意字符串,因此永远不会删除测试表。

类似地,大多数ORM(例如SQLAlchemy)在正常情况下会自动对所有SQL语句进行参数化。因此,它们为SQL注入提供了良好的初始防御。

仅使用存储过程

尽管可以将存储过程适当地用作更全面的防御的一部分,但使用存储过程本身并不能提供针对SQL注入的直接保护。要了解为什么存储过程本身不能防止SQL注入,请考虑以下查询以检索邮政编码的城市和州:

  create procedure dbo.GetCityState

 

@zipcode varchar(15)

as

 

select city, State

from dbo.ZipCodes

where zipcode = @zipcode

然后,如果有执行的python程序:

  sql = "exec dbo.GetCityState '" + userInput + "'"

 

 

curs.execute(sql)

conn.commit()

然后,攻击者可能会提供:

  '; Drop Table Test; --

作为输入,它将发送...

  exec dbo.GetCityState ''; Drop Table Test; --'

…到服务器。再次假定具有适当的权限,它将删除Test表,就像没有存储过程时一样。

当然,可以像标准select语句一样,通过对输入进行参数设置来防止此类攻击。但是,如果存储过程本身使用通过合并创建的动态SQL,则即使调用程序正确设置了参数,存储过程也可能执行恶意命令。可以通过使用sp_executesql在存储过程中对动态SQL进行参数化来防止这种情况,这在“ 动态SQL的诅咒和祝福 ”中进行了讨论。

使用存储过程来防止SQL注入的最大价值在于,DBA可以为应用程序帐户设置权限,以便与SQL Server进行交互的唯一方法是通过存储过程。(请参阅SQL Server安全性工作台第1部分)这意味着即使调用程序未参数化,大多数SQL注入攻击也会由于缺少权限而失败。当然,这仍然保留了在存储过程内部通过动态SQL进行SQL注入的可能性,但是可以为存储过程提供“ execute as”子句,该子句将其权限限制为仅过程所需的权限。通常,验证所有存储过程均已编写以防止SQL注入更加容易,然后检查应用程序与SQL Server交互的每个位置。

限制权限

这自然导致一种非常有效的方法,可以防止某些攻击并限制SQL注入攻击的损害,即使用具有最低权限的帐户进行作业。如果所使用的帐户无权删除表,则即使命令滑至SQL Server,也不会删除该表。同样,如果帐户仅具有读访问权限,则攻击者可能会获得一些信息,这肯定会引起问题,但是攻击者将无法修改或破坏数据,这通常会更糟。在SQL Server中甚至可以严格限制读取权限,以限制可以查看哪些表。如果应用程序仅需要从表中选择列,则可以授予对视图的读取权限,而不是整个表。

验证输入

用户输入应始终谨慎处理,有很多原因可在进一步处理之前验证所有用户输入。验证代码还可以通过限制不会返回有用结果的请求来帮助避免浪费服务器资源,并且它们可以向用户提供比SQL错误消息或空结果集可能提供的有用得多的消息。它们还可以通过直接拒绝可用于执行SQL注入的任何形式的输入来帮助停止SQL注入。

由于其许多优点,验证用户输入始终很重要,但是当用户输入传递给其他例程以进行进一步处理时,或者在无法完全参数化用户参数的极少数情况下,这一点尤其重要。输入。例如,如果您遇到的罕见情况是要求用户为DDL语句提供表名,则该表名不能作为参数传递,而必须在某些时候进行连接。在这种情况下,验证输入是抵御注入攻击的关键防御措施。同样,如果将输入传递给存储过程,则即使程序正确地将过程调用参数化,存储过程也可能会使用它通过串联生成动态SQL。有了验证带来的好处,

隐藏错误信息

注入攻击通常取决于攻击者至少具有一些有关数据库架构的信息。他经常通过反复试验获得此信息,并且错误消息将使攻击者对模式有很多了解。SQL Server和python通常都提供清晰,信息丰富的错误消息,这些错误消息对程序员非常有帮助,但也可以向恶意用户提供信息。特别是Pyodbc,通常会引发pyodbc.ProgrammingError异常,该异常有助于包含SQL Server错误消息。

在try / except块中包含对SQL Server的python调用将使程序可以向最终用户提供更用户友好的错误消息,其中不包含对攻击者有用的信息。如果与sys.exc_info之类的东西和日志记录包一起使用,except块可以记录所有错误以供以后分析,同时向最终用户显示用户友好的消息。一个非常基本的示例如下所示:

  import logging

 

import sys

 

logFile = 'test.log'

logging.basicConfig(filename=logFile,level=logging.DEBUG)

 

#establish connection, create faulty SQL, removed for brevity

 

try:

    curs.execute(sql)

    conn.commit()

except:

    print 'User Friendly Error'

    logging.debug(sys.exc_info())

当然,要确保没有未经过滤的消息通过,可以覆盖标准异常钩子,如下所示:

  import sys

 

 

def new_exceptionhandler(type, value, tb):

    #put in custom logging code here

    print 'There was a general error.'

   

sys.excepthook = new_exceptionhandler

 

print aVariable #NameError since aVariable is not defined

这不会对异常处理代码产生任何影响,但是它将阻止标准错误消息到达用户未处理的异常。

极限伤害

除了采取措施防止诸如SQL注入之类的攻击外,还可以采取其他常规安全措施来限制损害。已经提到了限制所使用帐户的权限,因为它可以完全阻止许多攻击,但是它也将限制成功攻击可能造成的损害。但是还有其他方法可以帮助减轻攻击造成的损害。

在适当的地方使用加密/哈希函数

如果对数据进行了正确的加密,那么对于没有加密密钥的人来说,它的价值将很小。自SQL Server 2005以来,单元级加密可以帮助防止对敏感数据的未经授权访问。SQLServer自SQL Server 2005开始就支持它。透明数据加密(TDE)虽然对于防止数据库遭受其他形式的攻击很有用,但对SQL注入的价值却很小。 。

特别是密码不应以明文形式存储,哈希通常比加密更好,因为它使恢复原始明文更加困难。当然,如果处理不当,即使是哈希也只能提供有限的安全性。例如,存在用于MD5算法的彩虹表(http://en.wikipedia.org/wiki/Rainbow_table)。它们使从MD5的一次迭代中确定原始明文而不加盐变得相对实用。在python中,有一些库可以使散列和添加密码相对容易,包括hashlib。

隔离数据

根据所需的安全级别将数据隔离到不同的系统中,可以帮助限制攻击的范围。确保真正敏感的数据以无法从外部网络访问的方式存储甚至常常是有意义的。这有助于确保即使攻击者破坏了系统,也不会立即导致攻击者破坏所有系统。当然,有必要确保隔离的系统之间不共享相同的登录凭据,否则,如果一组登录凭据以某种方式受到破坏,则可能直接导致损害其他系统。

审核和记录

审核和日志记录将永远不会帮助防止SQL注入或任何其他攻击。但是,它可能有助于检测攻击,并有助于从中恢复。SQL Server中有许多工具,例如“ 更改数据捕获”和“ SQL Server审核”。自定义的书面触发器也可以用于监视和记录更改。还有许多外部工具可以提供更多选项,例如SQL Server比较和SQL Server数据比较。日志记录库和其他类似的库使从python应用程序进行日志记录也相对容易。

结论

SQL注入是对系统的一种更常见,更有效的攻击形式。通过遵循安全软件设计的原则(例如,对数据库的输入进行参数设置,对用户输入进行清理和验证,以及将授予所有帐户的权限限制在所需的最低限度内),可能使SQL注入攻击成功极为困难。此外,通过遵循诸如加密敏感数据,分离数据和维护日志之类的基本安全实践,可以限制即使成功的攻击也可以造成的破坏程度。

版权申明:本站文章均来自网络,如有侵权,请联系01056159998 邮箱:itboby@foxmail.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

猜你还会喜欢下面的内容

    无相关信息

中国领先的互联网域名及云服务提供商

为您提供域名,比特币,P2P,大数据,云计算,虚拟主机,域名交易最新资讯报道

域名注册云服务器