防止SQL注入的好方法是什么?[复制]

What are good ways to prevent SQL injection? [duplicate]

提问人:LeonidasFett 提问时间:1/17/2013 最后编辑:Nondeterministic narwhalLeonidasFett 更新时间:5/17/2015 访问量:162184

问:

我必须为我的在职培训公司编写一个应用程序管理系统。前端将使用 C# 完成,后端将使用 SQL 完成。

现在我以前从未做过这种规模的项目;在学校里,我们只有关于SQL的基本课程。不知何故,我们的老师完全没有讨论SQL注入,我现在只是通过在网上阅读它才接触到的。

所以无论如何,我的问题是:如何防止 C# 中的 SQL 注入?我隐约认为可以通过正确屏蔽应用程序的文本字段来完成,以便它只接受指定格式的输入。例如:电子邮件文本框的格式应为“[email protected]”。这种方法就足够了吗?或者 .NET 是否具有处理此类内容的预定义方法?是否可以将筛选器应用于文本框,使其仅接受电子邮件地址格式或名称文本框,使其不接受特殊字符?

C# 安全性 SQL 注入

评论

3赞 CodeCaster 2/3/2016
另一个问题后来发布,但更详尽。

答:

17赞 Øyvind Bråthen 1/17/2013 #1

我的答案很简单:

使用 Entity Framework 在 C# 和 SQL 数据库之间进行通信。这将使参数化的 SQL 字符串不容易受到 SQL 注入的影响。

作为奖励,它也很容易使用。

评论

2赞 saluce 12/18/2015
但是,当您不知道返回的数据会是什么样子时,这并不总是一个选项。
7赞 Rez.Net 7/22/2019
我不喜欢英孚。我更喜欢像 Dapper 和 Insight.Database 这样的轻量级 ORMS。使用 ORM 时,您可以查看/编写要执行的确切查询。
3赞 Liam 12/10/2020
不利的是,它增加了大量的性能开销
6赞 Jon 1/17/2013 #2

不应通过尝试验证输入来阻止 SQL 注入;相反,在将该输入传递到数据库之前,应正确转义该输入。

如何转义输入完全取决于你使用什么技术来与数据库进行交互。在大多数情况下,除非您正在编写裸 SQL(您应该尽可能避免这种情况),否则框架会自动处理它,因此您可以免费获得防弹保护。

在你决定了你的接口技术之后,你应该进一步探索这个问题。

评论

0赞 LeonidasFett 1/17/2013
对不起,但你说的“编写裸SQL”是什么意思?
1赞 Jon 1/17/2013
@LeonidasFett:通常,您通过编写类似代码而不是类似 的代码来使用数据库。第二种是编写裸SQL。User.Name = "Joe"; User.Save();Database.ExecuteQuery("UPDATE users SET name = 'Joe' WHERE id = 1");
0赞 LeonidasFett 1/17/2013
啊,好吧。现在我明白了:)所以基本上我应该通过框架而不是 SQL 与数据库交互?
0赞 Basic 1/17/2013
你在这里提倡的是一个 ORM - 就像实体框架一样。我自己使用它并喜欢它,但它确实有一些缺点 - 比如批量操作真的很慢。另一种方法是使用参数化命令 - 您为变量编写带有占位符的 SQL,然后将变量传递给不同的方法,该方法负责将它们合并到查询中(实际上,它们分别传递给处理其余部分的 Db 服务器)。有关详细信息,请参阅此处
2赞 LeonidasFett 1/17/2013
截至目前,现有数据库中大约有 3000 条记录,但今年将大幅扩展。我将列出两个选项(EF 和参数化命令)以及每个选项的优缺点,并让我的项目主管决定:D
13赞 nerdybeardo 1/17/2013 #3

SQL注入可能是一个棘手的问题,但有一些方法可以解决它。只需使用 Linq2Entities、Linq2SQL、NHibrenate 等 ORM 即可降低风险。但是,即使使用它们,您也可能会遇到 SQL 注入问题。

SQL注入的主要内容是用户控制的输入(与XSS一样)。在最简单的例子中,如果你有一个登录表单(我希望你永远不会有一个只这样做的登录表单),它需要用户名和密码。

SELECT * FROM Users WHERE Username = '" + username + "' AND password = '" + password + "'"

如果用户为用户名 Admin' 输入以下内容,则在对数据库执行时,SQL 语句将如下所示。

SELECT * FROM Users WHERE Username = 'Admin' --' AND password = ''

在这个简单的情况下,使用参数化查询(这是 ORM 所做的)可以消除您的风险。您还遇到了一个鲜为人知的 SQL 注入攻击媒介的问题,那就是存储过程。在这种情况下,即使您使用参数化查询或 ORM,您仍然会遇到 SQL 注入问题。存储过程可以包含执行命令,而这些命令本身可能容易受到 SQL 注入攻击。

CREATE PROCEDURE SP_GetLogin @username varchar(100), @password varchar(100) AS
DECLARE @sql nvarchar(4000)
SELECT @sql = ' SELECT * FROM users' +
              ' FROM Product Where username = ''' + @username + ''' AND password = '''+@password+''''

EXECUTE sp_executesql @sql

因此,即使您使用参数化查询或 ORM,此示例也会遇到与上一个示例相同的 SQL 注入问题。虽然这个例子看起来很傻,但你会惊讶于这样的东西被写成的频率。

我的建议是使用 ORM 立即减少出现 SQL 注入问题的机会,然后学会发现可能存在问题的代码和存储过程并努力修复它们。我不建议直接使用 ADO.NET(SqlClient、SqlCommand 等),除非必须这样做,不是因为将它与参数一起使用在某种程度上不安全,而是因为更容易变得懒惰,只是开始使用字符串编写 SQL 查询并忽略参数。ORMS在强制你使用参数方面做得很好,因为这正是它们所做的。

接下来:访问 SQL 注入 https://www.owasp.org/index.php/SQL_Injection 上的 OWASP 站点,并使用 SQL 注入备忘单,以确保您可以发现并消除代码中出现的任何问题。https://www.owasp.org/index.php/SQL_Injection_Prevention_Cheat_Sheet 最后,我想说的是,在你和你公司的其他开发人员之间建立良好的代码审查,在那里你可以审查彼此的代码,比如SQL注入和XSS。很多时候,程序员错过了这些东西,因为他们试图匆忙推出一些功能,而不是花太多时间审查他们的代码。

评论

1赞 Basic 1/17/2013
我不知道 EF/类似 ORM 有任何 SQL 注入风险,因为它们在内部使用参数化查询 - 你能提供一个链接吗?谢谢
2赞 nerdybeardo 1/17/2013
我上面给出的存储过程的示例仍然容易受到 SQL 注入攻击,即使您使用像 Entity Framework 这样的 ORM 来调用该过程,因为漏洞存在于过程本身中。因此,我想传达的是,你不能简单地使用ORM,并认为你已经覆盖了100%的SQL注入攻击案例。以下是详细信息的链接:troyhunt.com/2012/12/stored-procedures-and-orms-wont-save.html
1赞 Basic 1/17/2013
如果你使用的是 ORM,为什么要使用 sproc?但是,是的,我接受你的观点,如果你想做一些手动的事情,那么你可能会引入漏洞
2赞 nerdybeardo 1/17/2013
虽然我同意最近由于 ORM 的兴起,存储过程的使用率有所下降,但仍然有许多业务情况确实需要使用它们。如果没有充分的理由,那么也许你有一个数据架构师,出于某些流程或性能原因需要使用它们。即使在今天,我也不知道有多少数据库没有存储过程,因此降低它们在 SDLC 中的重要性可能会导致应用程序中的 SQL 注入缺陷。我们开发人员有时生活在一个我们“想要”的世界里,而不是商业现实是什么。
75赞 Oliver 1/17/2013 #4

通过使用 SqlCommand 及其子参数集合,检查 sql 注入的所有痛苦都将从您身上消失,并将由这些类处理。

下面是一个示例,摘自上面的一篇文章:

private static void UpdateDemographics(Int32 customerID,
    string demoXml, string connectionString)
{
    // Update the demographics for a store, which is stored  
    // in an xml column.  
    string commandText = "UPDATE Sales.Store SET Demographics = @demographics "
        + "WHERE CustomerID = @ID;";

    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        SqlCommand command = new SqlCommand(commandText, connection);
        command.Parameters.Add("@ID", SqlDbType.Int);
        command.Parameters["@ID"].Value = customerID;

        // Use AddWithValue to assign Demographics. 
        // SQL Server will implicitly convert strings into XML.
        command.Parameters.AddWithValue("@demographics", demoXml);

        try
        {
            connection.Open();
            Int32 rowsAffected = command.ExecuteNonQuery();
            Console.WriteLine("RowsAffected: {0}", rowsAffected);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

评论

4赞 rollsch 12/6/2016
如果我想主动识别 sql 注入的尝试怎么办。是否有任何好的库可以与这种方法结合使用,以便我可以识别和记录攻击?
1赞 Oliver 12/6/2016
SQL 注入的第一种也是最简单的方法是以单引号或双引号开头,后跟大括号和分号来结束当前字符串和语句。因此,通过检查给定的输入,如果它以这些开头,这将是一个很好的提示,也许可以通过像 .这不是唯一的方法,而是最常见的方法。^\s*['"]\s*\)\s*;
4赞 Richardissimo 1/4/2019
请注意,SqlCommand 也是 IDisposable 的,因此应该位于块中。您可能想阅读我们可以停止使用 AddWithValue 吗using
0赞 karezza 9/5/2019
请注意,我实现了这段代码,它在遇到带有“~”的“n”时崩溃了。在那之前,这就像一个奇迹,现在我不确定其他语言......我希望这会安全。