C#SQL注入检测——特别是对于旧版.NET代码

目录

使用Decorator模式提供添加SQL注入检测的位置

SQL注入检测代码

究竟如何检测到SQL注入?

SQLExtensions类中包含的格式化方法

自定义.NET异常类

用于检测SQL注入的规则是可配置的

实现和使用此代码的步骤

MIT免费使用许可


我在本文中建议的向应用程序中添加SQL注入检测的主要技术是停止使用.ExecuteReader.ExecuteNonQuery方法。相反,使用Decorator模式创建自己的方法来代替这两个方法,其将包含用于执行某些SQL注入检测的代码。

当您拥有一个充满SQL语句的现有.NET代码库,并且希望减少代码中存在SQL注入风险的机会时,您可以决定对每个SQL语句进行一次检查,以确认它们都是编码正确;或者您可以雇用其他公司为您执行此操作。但是这种方法的一个问题是,从评审结束到人们开始再次修改和添加代码之间,代码只是SQL注入

您应该努力确保在运行之前和将来的所有SQL语句都经过SQL注入风险测试。这就是此示例代码为您提供的。如果您遵循此处描述的模式,我相信您可以大大降低代码中存在导致SQL注入的错误的风险,并且这种情况将一直保持下去。

使用Decorator模式提供添加SQL注入检测的位置

我在本文中建议的用于向应用程序中添加SQL注入检测的主要技术是停止使用.ExecuteReaderand .ExecuteNonQuery方法。而是使用Decorator模式创建自己的方法来代替这两个方法,其将包含用于执行某些SQL注入检测的代码。

更换:

SqlDataReader reader = command.ExecuteReader();

SqlDataReader reader = command.ExecuteSafeReader(); //provided in sample code

提供的示例代码的行为类似于Proxy模式,因为它将在没有SQL注入风险的情况下对数据库进行实际调用。这种方法的好处是,你可以再定期扫描的使用你的整个代码库,以便使用.ExecuteReader,并.ExecuteNonQuery,因为您知道除了您期望的异常情况之外,这些方法不应该出现任何情况。因此,您可以确保大部分代码都通过SQL 注入检测器运行。

使用Decorator模式实现SQL注入检测的另一个好处是,您还可以轻松添加其他功能,例如:

  • 记录执行的每个SQL
  • 记录和阻止存在SQL注入风险的每个SQL
  • 即时更改每个SQL。一种可能有用的方案是,如果您在数据库中重命名了一个表,但是有很多需要更改的SQL。您可以在运行中向每个SQL添加查找/替换以更改表名,从而使您有更多时间查找和更正带有旧表名的所有存储的SQL片段。
public static SqlDataReader ExecuteSafeReader(this SqlCommand sqlcommand)
{
    if (!sqlcommand.CommandType.Equals(CommandType.StoredProcedure))
    {
        var sql = sqlcommand.CommandText;
        //Options: You could Add logging of the SQL here to track every query ran
        //Options: You could edit SQL - for example if you had renamed a table in the database
        if (!ValidateSQL(sql, SelectRegex))
            return null;
    }

    return sqlcommand.ExecuteReader();
}

SQL注入检测代码

警告!这不会检测所有形式的SQL注入,但会检测其中的大多数形式。以下是导致类引发异常的原因:

  • 查找没有匹配单撇号(单引号)的单撇号(单引号)
  • 查找没有匹配双引号的双引号。仅当SQL Server具有SET QUOTED_IDENTIFIER OFF时才需要。但是,如果您的数据库是MySQL或其他DBMS,则可能还需要使用此功能。
  • SQL中查找注释
  • 查找大于127ASCII
  • 查找分号
  • 提取字符串和注释,在SELECT语句中查找特定的可配置关键字清单,如DELETESYSOBJECTSTRUNCATEDROPXP_CMDSHELL

如果您不想强制执行上面的任何规则,或者由于您有一个特殊的场景或除了SQL Server之外的DBMS而需要添加类似的规则,那么编写代码会很容易更改。

该代码使用正则表达式[^ \ u0000- \ u007F]拒绝包含任何非ASCII字符的SQL。这适用于我编写的应用程序,但可能需要更改以获取非美国英语支持。

该代码还使用正则表达式检查SQL语句中是否存在不需要的关键字。一个正则表达式用于SELECT语句,因此如果它们含有INSERTUPDATE或者DELETE,则会阻塞它们。可能表明SQL注入尝试其他关键字也拒绝,该名单包括waitforxp_cmdshellinformation_schema。注意,我也在列表中包括了UNION。因此,如果您使用UNION关键字,则需要将其从列表中删除。尝试执行SQL注入的黑客经常使用UNION

private static void LoadFromConfig()
{

	_asciiPattern = "[^\u0000-\u007F]";
	_selectpattern = @"\b(union|information_schema|insert|update|delete|truncate|drop|reconfigure|sysobjects|waitfor|xp_cmdshell)\b|(;)";
	_modifypattern = @"\b(union|information_schema|truncate|drop|reconfigure|sysobjects|waitfor|xp_cmdshell)\b|(;)";
	_rejectIfCommentFound = true;
	_commentTagSets = new string[2, 2] { { "--", "" }, { "/*", "*/" } };
}

SQL Server支持两种在SQL语句中注释掉SQL代码的技术,两个破折号和将注释括在/ * * /中。由于开发人员不太可能编写包含注释的SQL,因此我的默认选择是拒绝包含这些值的任何SQL

究竟如何检测到SQL注入?

SQL注入检测过程基本上包括三个步骤。

首先,代码检查127以上的任何ASCII值,如果找到,则拒绝SQL

其次,该代码删除所有带有字符串和注释的代码。因此,一个SQL看起来像这样:

select * from table where x = ‘ss”d’ and r = ‘asdf’ /* test */ DROP TABLE NAME1 order by 5

变成这个:

select * from table where x = and r = t DROP TABLE NAME1 order by 5

第三,代码在修改后的SQL中查找关键字,如DROPXP_CMDSHELL,它们都在淘气列表中。如果找到这些关键字中的任何一个,则拒绝SQL

SQLExtensions类中包含的格式化方法

SQLExtensions类提供更多的方法来帮助你减少编码SQL注入的风险。这些方法可帮助编码人员在不带参数的情况下使用SQL格式化变量。这些方法中最有用的是FormatStringForSQL,可以使用它将字符串括在SQL引号中,并用两个单引号替换值中包含的任何单引号。

string sql = "select * from customers where firstname like " + nameValue.FormatStringForSQL();

使用这种方法的另一个好处是,如果发现需要进行更改,则可以轻松地在代码中的任何地方更改处理字符串格式的方式。例如,也许您决定将应用程序从SQL Server迁移到MySQL,因此除了单引号之外,还需要替换双引号。您可以在此方法中进行更改,而无需查看整个代码库以对每个SQL逐一进行更改。

自定义.NET异常类

我还提供了一个自定义异常,主要是为了展示实现自定义异常有多么容易,并且因为我认为它对于此扩展类很有用。这为您提供了更大的灵活性来处理异常。您可以捕获和处理由于SQL注入风险而导致的异常,这些异常不同于从数据库返回的基础ADO.NET代码引发的异常。

[Serializable]
public class SQLFormattingException : Exception
{
	public SQLFormattingException() {}

	public SQLFormattingException(string message): base(message) {}
}

用于检测SQL注入的规则是可配置的

我使启用/禁用SQL注入检测的配置易于更改,以便您可以根据需要在运行时导入这些规则,以便不同的应用程序可以具有不同的规则。也许您的一个应用程序需要在SQL中允许使用分号,而其他应用程序则不允许。在所有可能的地方都执行最严格的规则是一个好习惯。不要在各处都实现弱的SQL注入检测规则,因为代码中的单个位置需要较弱的规则。规则在需要时先延迟加载,然后进行缓存,以支持在应用程序运行时通过调用提供的InvalidateCache方法来更改规则的能力。

以下是其中一个规则的示例。您可以将代码配置为拒绝包含SQL Server注释的SQL

#region RejectComments Flag
private static bool? _rejectIfCommentFound = null;
public static bool RejectIfCommentFound
{
	get
	{
		if (_rejectIfCommentFound == null)
		{
			LoadFromConfig();
		}
		return (bool)_rejectIfCommentFound;
	}
}
#endregion

实现和使用此代码的步骤

我建议您采取以下步骤来实现此类:

  1. SQLExtensions.cs类文件放入代码库中的项目中。您还将需要CustomExceptions.cs类文件。Program.cs只包含一个使用样例和一个UnitTest1.cs类。
  2. 注释掉除“return true”以外的所有ReallyValidateSQL中的行
  3. 查找并替换整个代码库以替换ExecuteReaderExecuteSafeReader
  4. 编译和测试。此时,您的应用仍应完全相同。
  5. 查看可自定义的验证属性并确定要实现的属性,然后取消注释您在ReallyValidateSQL中注释的行 
  6. 确定是否需要并希望使用提供的四种FormatSQL…扩展方法中的任何一种来替换应用程序中动态构造的SQL
  7. 给我反馈

MIT免费使用许可

该代码具有MIT许可证,这意味着您可以免费在商业产品中使用此代码!

源代码示例的链接在这里:https : //github.com/RobKraft/SQLInjectionDetection

发布了69 篇原创文章 · 获赞 146 · 访问量 49万+

猜你喜欢

转载自blog.csdn.net/mzl87/article/details/105005732