存储过程之外:SQL注入深入防御

几年以前,对开发者提及”SQL注入”或者要求采取一个”深入防御”的措施,你大概会遭白眼。如今,越来越多的人听过”SQL注入”攻击而且开始关注这些攻击出现带来的潜在危险,但是大多数开发者仍然欠缺如何防止SQL注入攻击的知识,而当问及他们的应用软件如何防御SQL注入时,他们通常回答:“很简单,只要使用存储过程”。我们可以预见的是,使用存储过程对于你的防御策略来说是非常好的开端,但是仅此一步却不够。你需要采用一个深入防御策略。

如果你不熟悉SQL注入攻击和它们对你的应用软件的潜在危险,请参考MSDN的文章 “SQLInjection” (http://msdn2.microsoft.com/en-us/library/ms161953.aspx)。

仅仅依赖存储过程而无法执行一个深入防御的问题在于你真的只指望存储过程开发者为你提供安全。类似于下面SQLServer代码用来鉴别一个用户的存储过程非常常见:

ALTER PROCEDURE LoginUser
(
@UserID [nvarchar](12),
@Password [nvarchar](12)
)
AS
SELECT * FROM Users WHERE UserID = @UserID AND Password = @Password
RETURN


这个存储过程看起来非常安全,但是考虑一下这个:

ALTER PROCEDURE LoginUser
(
@UserID [nvarchar](12),
@Password [nvarchar](12)
)
AS
EXECUTE (‘SELECT * FROM Users WHERE UserID = ‘’’ + @UserID + ‘’’
AND Password = ‘’’ + @Password + ‘’’’)
RETURN

通过创建一个特别的SQL声明,而且把它放到存储过程代码的EXECUTE函数中,我们真的可以生成一个存储过程SQL注入。当你使用控制代码来写存储过程的时候,这个更加容易,它在Microsoft SQL Server 2005中以新的方式支持:

[Microsoft.SqlServer.Server.SqlProcedure]
public static void LoginUser(SqlString userId, SqlString password)
{
using (SqlConnection conn = new SqlConnection(“context connection=true”))
{
SqlCommand selectUserCommand = new SqlCommand();
selectUserCommand.CommandText = “SELECT * FROM Users WHERE UserID = ‘”
+ userId.Value + “’ AND Password = ‘” + password.Value + “’”;
selectUserCommand.Connection = conn;

conn.Open();
SqlDataReader reader = selectUserCommand.ExecuteReader();
SqlContext.Pipe.Send(reader);
reader.Close();
conn.Close();
}
}


甚至就算你是写存储过程的那个人,你通常不能确定其他人会在你后面在应用软件配置以后改变代码。关于Web应用这尤其可能,而这也是为什么一个需要一个深入防御策略。

明显地,这个问题的解决是采用一个深入防御策略。你应该继续使用存储过程和参数化任何有必要的查询,但是你也应该设法建立你的深入防御策略来保证传给这些存储过程的的参数和查询的验证。在我们上面的用户鉴定例子中,“bobsmith”可能是一个有效的用户ID,但是“SELECT * FROM tblCreditCards”或许不是。对于验证用户输入使用你的深入防御策略的一个好的方式是对它采用规范化的表达规则。你能使用在System.Web.UI.WebControls名字空间中的RegularExpressionValidator控制寻找来验证Web表格数据,而且你可以使用在theSystem.Text.RegularExpressions 名字空间中Regex类寻找来验证任何类别的文字数据。这里有一个Web表格的例子,在把用户输入传递给数据库前对它进行验证。

protected void Page_Load(object sender, EventArgs e)
{
if (Page.IsPostBack)
{
// We allow only alphanumeric input
Regex allowRegex = new Regex(“^[a-zA-Z0-9]*$”);
if ((!allowRegex.IsMatch(textBoxUserId.Text))
|| (!allowRegex.IsMatch(textBoxPassword.Text)))
{
labelErrorMessage.Text = “Invalid user ID or password.”;
return;
}
else
{
// Call the login stored procedure

}
}
}

一个更加彻底的深入防御策略是使用一个允许输入联合模式(也被认为是“优选名单”)和拒绝输入模式(或者称为”黑名单”)。用户的输入必须匹配优选名单模式(或者说至少一个优选名单模式,如果不多于一个的话)而且不和黑名单模式匹配(或者说任何黑名单模式)。如果在你的允许输入表中允许类似省略号这样的非字符输入,在深度防御策略中,你应该明确的使用黑名单模式。

// We allow alpha characters, spaces, and apostrophes as input
Regex allowRegex = new Regex(@“^[a-zA-Z\s\’]*$”);
// But we disallow common SQL functions
Regex disallowRegex = new Regex(“(union|select|drop|delete)”);
if ((!allowRegex.IsMatch(textBoxLastName.Text)) || (disallowRegex.IsMatch(textBoxLastName.Text)))
{
labelErrorMessage.Text = “Invalid name.”;
return;
}


最后,我们必须论及加入损害控制到你的深入防御策略。如果一个黑客真的找到了一个对你的数据库执行SQL命令的途径,他可能会造成什么样的损害呢?如果你的应用程序作为一个管理员用户连接到数据库,象Microsoft SQL Server的”sa”用户,损害甚至会很严重。他不仅仅能看表中的数据,他也能增加他自己的新数据或者改变已经存在数据的值。设想一个在线购物站点,所有的商品项目价格标成低于一个美分。他能够增加新的用户或者删除已经存在用户。他能删除行,表或者甚至整个数据库。你能通过应用最少权限原则到你的深入防御策略减轻这个风险:使你的应用软件作为一个只有足够执行必需动作的许可,而没有其它更多的许可。如果你的应用程序只是从一个数据库中读数据,去掉数据库用户插入,更新和删除的许可。如果应用程序只需要对一个产品目录数据库进行存取(举例来说),确保用户不能存取定购历史数据库。永远不要指定”sa”或者任何管理员用户作为数据库用户。

通过采用一个深入防御策略,你可以避免大一个SQL注入攻击导致你应用程序的多数或者所有的损害。就很多原因包括提高安全来说,使用存储过程是一个很好的主意,但是一定不要依赖它们提供你所有的安全。总是验证用户输入,而且采用最少权限原则来减小一个成功的攻击能引起的损害。

关于作者

Bryan Sullivan是SPI Dynamics 一个Web应用软件安全产品公司的开发经理。Bryan管理DevInspect 和 QAInspect Web 安全产品,这些产品帮助程序员在整个开发和测试过程中维护应用程序的安全。他在Georgia Tech获得本科学位,而且有11年信息技术产业工作经验。他也对AVDL有所贡献,这已经成为应用软件安全行业的一个标志。

展开阅读全文

没有更多推荐了,返回首页