[转】SQL 脚本注入式攻击的剖析一例
SQL 脚本注入式攻击的剖析
当您在应用程序中接受未筛选的用户输入值(见上文)时,恶意用户可以使用转义符来添加他们自己的命令。
试考虑这样一个 SQL 查询,该查询希望用户的输入是一个社会保障号码,比如 172-32-xxxx,最后查询形式如下所示:
SELECT au_lname, au_fname FROM authors WHERE au_id = '172-32-xxxx'
恶意用户可以将以下文字输入您的应用程序输入字段(例如,文本框控件)中。
' ; INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100) -
此示例中注入了一个 INSERT 语句(但是用于连接到 SQL Server 的帐户所允许的任何语句都可以执行)。如果该帐户是 sysadmin 角色的成员(它允许执行使用 xp_cmdshell的 shell 命令),并且 SQL Server 使用的域帐户拥有其他网络资源的访问权限,则该代码的破坏性尤其大。
上述命令生成以下组合 SQL 字符串:
SELECT au_lname, au_fname FROM authors WHERE au_id = '' ; INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100) --
在这种情况下,恶意输入开头的 '(单引号)字符中止了您的 SQL 语句中的当前字符串。它仅在以下情况下才关闭当前语句:下面分析的标记的意思不是当前语句的继续标记,而是一个新语句的开始标记。
SELECT au_lname, au_fname FROM authors WHERE au_id = ' '
;(分号)字符告诉 SQL 您正在开始一个新语句,而紧跟在后面的就是恶意 SQL 代码:
; INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100)
注 分开 SQL 语句不一定需要分号。这与供应商/实施方法有关,但 SQL Server 不需要。例如,SQL Server 将以下语句分析为两个独立的语句:
SELECT * FROM MyTable DELETE FROM MyTable
最后,--(双短划线)字符序列是一个 SQL 注释符号,它告诉 SQL 忽略文本其余部分,在此例中就是忽略 '(单引号)这个结束字符(否则它会导致 SQL 分析器错误)。
作为上述语句的结果,SQL 执行的全部文本是:
SELECT au_lname, au_fname FROM authors WHERE au_id = '' ; INSERT INTO jobs (job_desc,min_lvl,max_lvl) VALUES ('Important Job',25,100) --'
解决方案
以下几种方法可用来从应用程序安全调用 SQL。
-
在构建 SQL 语句时使用 Parameters 集合。
SqlDataAdapter myCommand = new SqlDataAdapter( "SELECT au_lname, au_fname FROM Authors WHERE au_id= @au_id", myConnection); SqlParameter parm = myCommand.SelectCommand.Parameters.Add( "@au_id", SqlDbType.VarChar, 11); parm.Value= Login.Text;
-
在调用存储过程时使用 Parameters 集合。
// AuthorLogin is a stored procedure that accepts a parameter named Login SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", myConnection); myCommand.SelectCommand.CommandType = CommandType.StoredProcedure; SqlParameter parm = myCommand.SelectCommand.Parameters.Add( "@LoginId", SqlDbType.VarChar,11); parm.Value=Login.Text;
如果您使用 Parameters 集合,不管恶意用户在输入中包含什么内容,该输入都会被按文本处理。使用 Parameters 集合的另一好处是您可以执行类型和长度检查。超出范围的值触发异常。这是一个安全的纵深防御示例。
-
从用户输入中筛选 SQL 字符。下面的方法说明如何确保在简单 SQL 比较(等于、小于、大于)语句中使用的任何字符串都是安全的。它是通过确保在字符串中使用的任何撇号再附加一个撇号进行转义来做到这一点的。在 SQL 字符串中,两个连续的撇号被视为字符串内的撇号字符实例,而不是分隔符。
private string SafeSqlLiteral(string inputSQL) { return inputSQL.Replace("'", "''"); } … string safeSQL = SafeSqlLiteral(Login.Text); SqlDataAdapter myCommand = new SqlDataAdapter( "SELECT au_lname, au_fname FROM authors WHERE au_id = '" + safeSQL + "'", myConnection);
其他最佳做法
下面是用于减少安全漏洞以及用于将可能造成的损失限制在一定范围内的其他一些措施:
-
通过限制输入的大小和类型在关口(前端应用程序)防止输入非法内容。通过限制输入的大小和类型,可大大减小危害的可能性。例如,如果数据库查看字段是十一个字符长并且全部由数字组成,则强制实现这一规则。
-
用具有最少权限的帐户运行 SQL 代码。这可大大减小可能造成的损失。
例如,如果用户要注入 SQL 以删除数据库的表,但是 SQL 连接所使用的帐户没有相应的权限,则 SQL 代码将失败。这是不应将 sa 帐户、 sysadmin 或 db_owner 的成员用于应用程序的 SQL 连接的又一原因。
-
在 SQL 代码中出现异常错误时,不要向最终用户披露数据库引起的 SQL 错误。记录错误信息,并只显示用户友好信息。这可避免泄露可能对攻击者有所帮助的不必要的详细信息。
保护模式匹配语句
如果输入将在 LIKE子句的字符串中使用,则字符(撇号除外)还具有特殊的模式匹配含义。
例如,在 LIKE 子句中,% 字符表示“匹配零或多个字符”为将输入中的此类字符作为没有特殊含义的文字字符,它们也需要进行转义。如果不对它们进行特殊处理,则查询可能返回错误结果,在字符串开头或开头附近的非转义模式匹配字符可能也会破坏索引。
对于 SQL Server,应当使用以下方法来确保输入的内容有效:
private string SafeSqlLikeClauseLiteral(string inputSQL) { // Make the following replacements: // ' becomes '' // [ becomes [[] // % becomes [%] // _ becomes [_] string s = inputSQL; s = inputSQL.Replace("'", "''"); s = s.Replace("[", "[[]"); s = s.Replace("%", "[%]"); s = s.Replace("_", "[_]"); return s; }