IRONSOFTWAREHOME

什么是 SQL 注入,如何在 C# 中防止 SQL 注入?

什么是 SQL 注入以及如何在 C# 中防止它?

Tim Corey

34m 33s

SQL 注入是一种代码注入技术,攻击者可通过用户输入向数据库服务器发送恶意 SQL 代码。 在"What Is SQL Injection And How Do I Prevent It in C#?"视频中,Tim Corey 准确演示了 SQL 注入漏洞如何出现在真实代码中,展示了几个成功的 SQL 注入攻击示例(包括基于联合的攻击和破坏性攻击),并介绍了可以在 C# 中应用的实用 SQL 注入防范技术。 本文紧跟 Tim 的演练,因此您可以看到他展示的确切问题和修复方法。

演示应用程序及其重要性

Tim 从一个绑定到本地 InjectableDB (人和秘密表)的小型 WPF 演示应用程序开始。 该应用程序的网络表单式搜索框接受用户输入(姓氏)并建立 SQL 查询,以返回 ID、名和姓。 它"有效"——输入 Corey,您会得到 Tim Corey——但 Tim 强调的核心点是:"仅仅因为一个应用程序可以工作并不意味着它是安全的。"一个工作中的 web 应用程序仍然可能在插入用户提供的输入通过字符串连接或动态 SQL 插入到 SQL 语句中时存在 SQL 注入漏洞。

不安全代码--字符串连接和动态 SQL.

Tim 展示了许多开发人员使用的不安全模式:

var sql = $"SELECT * FROM People WHERE LastName = '{searchText}'";
var results = connection.Query<Person>(sql);

该原始查询使用字符串连接来创建 SQL 语句。 蒂姆警告:如果您看到将用户输入直接注入 SQL 查询的代码,请停止 - 这是一个 SQL 注入漏洞。 攻击者可以制作恶意输入,更改您的 SQL 命令结构,甚至运行额外的恶意 SQL 语句。

攻击者如何利用它 - UNION 和 DROP.

为了展示 SQL 注入攻击是如何进行的,Tim 在 SQL Server 中重现了查询,然后使用 UNION ALL 和 SQL 注释 (--) 隐藏尾部字符来制作注入程序。 Tim 演示的恶意有效载荷示例:

  • 基于联合的 SQL 注入以读取其他表:

    UNION ALL SELECT ID, Username AS FirstName, Password AS LastName FROM Secrets;
    Text

    这将把来自 Secrets 的结果混合到原始 SELECT 结果集中,从而暴露出用户名和密码等敏感数据。

  • 破坏性注入以删除表格:

    DROP TABLE DemoTable;
    Text

    运行第二条 SQL 语句(DROP TABLE)时,先用分号结束第一条语句,然后添加破坏性命令。 Tim 显示表格消失了--数据库已被恶意 SQL 修改。

Tim 的观点:攻击者不需要提前知道表或列名--他们可以从数据库服务器中枚举表或列名,或者简单地尝试盲目或基于定时的技术来发现行为。

修复 1 - 参数化查询

Tim 的首要任务是停止用用户数据构建 SQL 字符串。 用参数化查询替换动态 SQL:

string sql = "SELECT * FROM People WHERE LastName = @LastName";
var results = connection.Query<Person>(sql, new { LastName = searchText });

Tim 解释说参数化(准备好的语句风格用法)意味着数据库将用户提供的输入严格视为数据——恶意的 SQL 只是一个字符串值,不能改变 SQL 的结构。 这可以防止许多常见的 SQL 注入攻击,包括基于联合的有效载荷和附加的.NET、Java、Python 或 Node js; DROP TABLE 命令。

他还建议将参数化与最低限度的输入验证结合起来:对姓氏中不可能出现的字符(如分号或--注释标记)进行消毒或屏蔽,同时允许使用撇号等合法字符(O'Reilly)。 参数化查询 + 输入清洗可有效防止 SQL 注入攻击。

修复 2 - 存储过程

接下来,Tim 展示了两个存储过程:一个是不安全的存储过程,在存储过程中连接 SQL 然后执行;另一个是安全的存储过程,直接使用参数。

  • 不安全的存储过程从参数构建 @sql 字符串并对其执行 - 仍易被注入。

  • 安全存储过程执行 SELECT ... WHERE LastName = @LastName,并执行参数 - safe。

Tim 澄清道:如果您仍然在存储过程中构建动态 SQL,那么存储过程并不能自动治愈。 SQL语句的使用很简单,但如果使用得当(不使用动态SQL),存储过程有助于集中管理SQL语句,使参数化和审核查询变得更加容易。 存储过程还有助于简化应用程序中的 SQL 注入防范工作。

不要相信任何数据,甚至不要相信数据库数据

Tim 提出了一个经常被忽略的重要问题:不能盲目相信从自己的 SQL 数据库中获取的数据。 攻击者有时会在列中植入恶意有效载荷("定时炸弹"),这些有效载荷随后会被另一个进程连接到动态 SQL 中。 蒂姆坚持认为:无论数据来自网络表单、文件上传还是您自己的数据库,都要始终使用参数并在每个步骤中对数据进行消毒,这样恶意输入就不会成为注入的途径。

奖励提示 - 最低权限和限制数据库权限

除了代码修复外,Tim 还建议进行防御性配置:限制应用程序账户的数据库权限。 在他的演示中,连接通过集成安全功能使用了管理员账户--这很危险。 在翻译过程中,应尽量避免使用".NET"、"Java"、"Python "或 "Node js "等术语,而应使用 "最小特权 "原则:

  • 为应用程序创建一个数据库账户,只保留其所需的权限。

  • 如果您使用存储过程,请仅授予该账户对特定存储过程的 EXECUTE 权限,其他权限一概不授予。

  • 不要赋予应用程序账户广泛的管理员权限,以允许 DROP TABLE、列出所有表或读取其他数据库。

这样可以降低 SQL 注入攻击成功后的影响--即使有可能进行注入,攻击者也无法进行超出账户允许范围的操作。

Tim 还指出,Entity Framework(Entity Framework)使翻译变得更加复杂:EF 通常需要提升权限(迁移、模式更改)。 如果您在生产中使用 EF,请仔细规划其权限和部署。

Recap - 停止、参数化、消毒、限制

视频的最后,Tim 提供了一份清晰的清单,用于防止 C# 应用程序中的 SQL 注入:

1.停止使用字符串连接或包含用户输入的动态 SQL 来构建 SQL 语句。

2.使用参数化查询/预处理语句模式,使用户数据始终被视为数据。

3.在适当的地方对输入进行消毒(分号块、SQL 注释、意外字符)。

4.首选安全的存储过程(内部无动态 SQL),以便集中查询逻辑。

5.对数据库账户应用最低权限--限制应用程序的数据库用户可以做什么。

6.审核代码(尤其是动态构建 SQL 的地方)并测试 SQL 注入缺陷。

蒂姆的最后警告:马虎处理用户输入、动态 SQL 和过高的数据库权限可能导致严重的漏洞--敏感数据泄露、表格毁坏或长期未被发现的外泄。 将防止 SQL 注入作为核心安全要求,而不是可有可无的修饰。

Earn More by Sharing What You Love

Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Let's Stay in Touch!

Join our newsletter, you’ll get exclusive access on article updates. We value your privacy

Key in blue circle

立即获取免费的 30 天试用版密钥

Your trial license will be sent to your email address

无任何限制。100% 解锁。无需信用卡。

bullet_checked无需信用卡或创建账户无任何限制。100% 解锁。无需信用卡。
  • Logo Aetna
  • Logo NASA
  • Logo GE
  • Logo Porsche
  • Logo USDA
  • Logo Qatar
Join Millions of Engineers who’ve tried IronPDF
获取您的无义务咨询
填写下面的表格或通过sales@ironsoftware.com
您的资料将始终保密。
深受全球数百万工程师信赖
Iron Software 的客户徽标
立即获取您的免费30 天试用密钥
无需信用卡或创建账户