目录
使用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中查找注释
- 查找大于127的ASCII值
- 查找分号
- 提取字符串和注释,在SELECT语句中查找特定的可配置关键字清单,如DELETE,SYSOBJECTS,TRUNCATE,DROP,XP_CMDSHELL
如果您不想强制执行上面的任何规则,或者由于您有一个特殊的场景或除了SQL Server之外的DBMS而需要添加类似的规则,那么编写代码会很容易更改。
该代码使用正则表达式[^ \ u0000- \ u007F]拒绝包含任何非ASCII字符的SQL。这适用于我编写的应用程序,但可能需要更改以获取非美国英语支持。
该代码还使用正则表达式检查SQL语句中是否存在不需要的关键字。一个正则表达式用于SELECT语句,因此如果它们含有INSERT,UPDATE或者DELETE,则会阻塞它们。可能表明SQL注入尝试其他关键字也拒绝,该名单包括waitfor,xp_cmdshell和information_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注入检测过程基本上包括三个步骤。
首先,代码检查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中查找关键字,如“DROP”和“XP_CMDSHELL”,它们都在淘气列表中。如果找到这些关键字中的任何一个,则拒绝SQL。
SQLExtensions类中包含的格式化方法SQLExtensions类提供更多的方法来帮助你减少编码SQL注入的风险。这些方法可帮助编码人员在不带参数的情况下使用SQL格式化变量。这些方法中最有用的是FormatStringForSQL,可以使用它将字符串括在SQL引号中,并用两个单引号替换值中包含的任何单引号。
string sql = "select * from customers where firstname like " + nameValue.FormatStringForSQL();
使用这种方法的另一个好处是,如果发现需要进行更改,则可以轻松地在代码中的任何地方更改处理字符串格式的方式。例如,也许您决定将应用程序从SQL Server迁移到MySQL,因此除了单引号之外,还需要替换双引号。您可以在此方法中进行更改,而无需查看整个代码库以对每个SQL逐一进行更改。
我还提供了一个自定义异常,主要是为了展示实现自定义异常有多么容易,并且因为我认为它对于此扩展类很有用。这为您提供了更大的灵活性来处理异常。您可以捕获和处理由于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
实现和使用此代码的步骤
我建议您采取以下步骤来实现此类:
- 将SQLExtensions.cs类文件放入代码库中的项目中。您还将需要CustomExceptions.cs类文件。Program.cs只包含一个使用样例和一个UnitTest1.cs类。
- 注释掉除“return true”以外的所有ReallyValidateSQL中的行
- 查找并替换整个代码库以替换ExecuteReader为ExecuteSafeReader
- 编译和测试。此时,您的应用仍应完全相同。
- 查看“可自定义的验证属性”并确定要实现的属性,然后取消注释您在ReallyValidateSQL中注释的行
- 确定是否需要并希望使用提供的四种FormatSQL…扩展方法中的任何一种来替换应用程序中动态构造的SQL。
- 给我反馈
该代码具有MIT许可证,这意味着您可以免费在商业产品中使用此代码!
源代码示例的链接在这里:https : //github.com/RobKraft/SQLInjectionDetection