C#中自定义SQL SELECT查询的安全检查



我需要验证运行时用户提供的SQL查询是否仅用于SELECT数据,而不能执行其他操作(删除、更新、插入…)或更改数据库(更改、创建、删除、截断…)

我不是在寻找一个受限制的用户解决方案(可能稍后实现),而是在寻找C#查询"白名单"。

目前,这是我正在使用的代码:

private bool ValidateDatasourceQuery(String datasourceQuery)
{
bool result = false;
try
{
bool isValid = true;
String query = datasourceQuery.Trim().ToLower();
if (query.Substring(0, 6) != "select") { isValid = false; }
if (query.Contains("delete ") || query.Contains(" delete")) { isValid = false; }
if (query.Contains("exec ") || query.Contains(" exec")) { isValid = false; }
if (query.Contains("insert ") || query.Contains(" insert")) { isValid = false; }
if (query.Contains("update ") || query.Contains(" update")) { isValid = false; }
if (query.Contains("alter ") || query.Contains(" alter")) { isValid = false; }
if (query.Contains("create ") || query.Contains(" create")) { isValid = false; }
if (query.Contains("drop ") || query.Contains(" drop")) { isValid = false; }
if (query.Contains("truncate table ") || query.Contains(" truncate table")) { isValid = false; }
result = isValid;
}
catch (Exception exception) { GUC_Utilities.TraceError(exception); }
return result;
}

有什么想法吗?有没有办法通过这个检查并执行像DELETE这样的危险操作?您将如何改进此代码?

还有一个问题,ExecuteReader方法只能运行SELECT语句,还是也可以运行其他CRUD操作?类似于以下代码:

//execute command
SqlCommand sqlCommand = new SqlCommand(sql, sqlConnection);
SqlDataReader sqlDataReader = sqlCommand.ExecuteReader();
dataTable.Load(sqlDataReader);

谢谢你抽出时间!

附言:我只对改进感兴趣;验证给定的代码-没有GUI,特定的角色&其他建议目前是的一个选项

编辑(2014-01-16):经过进一步的研究和测试,我可以确认,没有可靠的方法可以防止黑客在SQL查询中注入破坏性语句(分号、字符注入、内置函数等)。保持数据完整性的唯一方法是创建一个具有有限特权的特定用户角色。其他任何东西都必须被视为潜在的不安全。另外,请注意,EXECUTREADER确实可以运行DELETE、UPDATE和INSERT语句。

Sqldatareader创建了一个仅向前的数据读取器。Selects是唯一有效的语句。

作为备用,具有任何类型逻辑的选择,特别是如果它们将被重用,则应将其转换为存储过程,以允许生成计划和缓存。

如何将其包装在事务中,读取数据,然后始终回滚事务。因此,如果存在恶意代码,它将永远不会被提交。

jus-add-

从(此处查询)x 中选择*

将只运行"选择查询",其他查询将给出错误。

虽然在某些情况下它可能会起作用,但我想更进一步,真正解析查询。正在分析SQL Server数据库';s脚本指向一些可能感兴趣的项目。然后,您可以学习询问语法树到底发生了什么,并在此基础上做出决策。你的所作所为真的没有安全感。我能想出几个办法让一个足够聪明的人通过你的安检。不过,如果这是一个内部应用程序,你需要考虑这些努力是否值得。

将Builder模式与允许用户构建查询的合适GUI一起使用怎么样?

这可能效果更好,如果关键字是较大字母数字字符串的一部分,则可以显示该关键字:

public static bool ValidateQuery(string query)
{
return !ValidateRegex("delete", query) && !ValidateRegex("exec", query) && !ValidateRegex("insert", query) && !ValidateRegex("alter", query) &&
!ValidateRegex("create", query) && !ValidateRegex("drop", query) && !ValidateRegex("truncate", query);
}
public static bool ValidateRegex(string term, string query)
{
// this regex finds all keywords {0} that are not leading or trailing by alphanumeric 
return new Regex(string.Format("([^0-9a-z]{0}[^0-9a-z])|(^{0}[^0-9a-z])", term), RegexOptions.IgnoreCase).IsMatch(query);
}

您可以在这里看到它的工作原理:regexstorm
请参阅regex备忘单:cheatsheet1,cheatsheet2

请注意,这并不完美,因为它可能会阻止将其中一个关键字作为引号的查询,但如果您编写查询并且这只是一种预防措施,那么这可能会奏效。

您也可以采取不同的方法,尝试查询,如果它影响数据库,则执行回滚:

public static bool IsDbAffected(string query, string conn, List<SqlParameter> parameters = null)
{
var response = false;
using (var sqlConnection = new SqlConnection(conn))
{
sqlConnection.Open();
using (var transaction = sqlConnection.BeginTransaction("Test Transaction"))
using (var command = new SqlCommand(query, sqlConnection, transaction))
{
command.Connection = sqlConnection;
command.CommandType = CommandType.Text;
command.CommandText = query;
if (parameters != null)
command.Parameters.AddRange(parameters.ToArray());
// ExecuteNonQuery() does not return data at all: only the number of rows affected by an insert, update, or delete.
if (command.ExecuteNonQuery() > 0)
{
transaction.Rollback("Test Transaction");
response = true;
}
transaction.Dispose();
command.Dispose();
}
}
return response;
}

你也可以把两者结合起来。

您可以添加在SQL注入的大多数情况下使用的注释:

if (query.Contains("--")) { isValid = false; }

最新更新