在C#中检查自定义SQL SELECT查询

时间:2014-01-05 21:48:54

标签: c# sql security

我需要验证运行时用户提供的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);

谢谢你的时间!

PS我只对改善&验证给定的代码 - 没有GUI,特定角色和其他建议目前是一个选项

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

6 个答案:

答案 0 :(得分:2)

Sqldatareader创建一个仅向前数据阅读器。选择是唯一可行的语句。

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

答案 1 :(得分:1)

虽然在某些情况下你看起来像它可能有用,但我会考虑进一步解决这个问题并将其解析为真实的。 Parsing SQL Server Database's Script指向可能感兴趣的一些项目。然后,您可以学习如何向语法树询问实际情况,并根据该问题做出决策。你所做的事情真的没有安全感。我可以想到一些聪明的人可以通过你的安全来解决问题。如果它是一个内部应用程序,你需要考虑努力是否值得。

答案 2 :(得分:1)

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

答案 3 :(得分:1)

jus add -

  

从(您的查询在此处)x

中选择*

仅运行选择查询,其他将发出错误。

答案 4 :(得分:0)

如何使用Builder Pattern以及允许用户构建查询的合适GUI?

答案 5 :(得分:0)

这可能会更好,这允许关键字出现,如果它是一个更大的字母数字字符串的一部分:

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
请参阅正则表达式备忘单:cheatsheet1cheatsheet2

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

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

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;
}

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