【问题标题】:What is the best way to save string data to SQL Server that contains single quote [duplicate]将字符串数据保存到包含单引号的 SQL Server 的最佳方法是什么[重复]
【发布时间】:2017-05-18 21:02:24
【问题描述】:

我有一个需要处理数据并将其存储在 SQL Server 中的 C# 程序。我遇到的问题是一些合法保存的数据包含一个单引号。保存数据时,我需要找到其中包含单引号的项目,并主动将单引号替换为两个单引号,这样我就不会得到截断的字符串。

目前,无论我在哪里添加可能包含单引号的数据,我都会通过以下例程传递它,该例程位于名为 FSQ(修复单引号)的静态模块中,这是例程:

/// <summary>
/// Fix Single Quote - Used to remove Double quotes from strings that would confuse Access database by replacing with Single Quotes.
/// </summary>
/// <param name="s">String text to be fixed by removing double quotes.</param>
/// <returns>The original string with any double-quotes removed and replaced with single quotes. If an error occurs will return an empty string.</returns>
public static string FSQ(string s)
{
    string tmp ="";

    try
    {
        if (s == null)
            return "";

        s = s.Trim();

        if (s == "")
            return s;

        if(s.Contains("'"))
        {
            if(!s.Contains("''"))//Already been fixed previously so skip here
                tmp = s.Replace("'", "''");

                s = tmp;
        }

        return s;


    }
    catch (Exception ex)
    {
        PEH("FDQ", "Common Module", ex.Message);
        return "";
    }
} //public static String FDQ(String s)

这行得通,我的字符串可以正常保存到 SQL,但由于对这个例程有很多调用,所以性能很差,因为程序在处理过程中循环了数千行数据。

是否有更有效的例程可以消除调用此函数的需要?大多数情况下,我只是在构建包含这些项目的更新或插入查询。

任何帮助表示赞赏。

【问题讨论】:

  • 啊。 SqlCommand + SqlParamater 将通过自动处理转义来解决这个问题,这就是为什么你应该总是使用它们。
  • 还要注意如果我设法传入''',它会跳过执行替换,仍然允许不匹配的引号字符通过。
  • @InnovaITveSolutions 不,这不应该用作重复项,因为 OP 是从 C# 执行的。在这种情况下,在字符串中转义单引号是 SQL 注入的邀请。
  • 没有办法修复FSQ,因为这个方法本身就是bug。 不要 试图逃避,只是避免字符串连接,你就不需要引用或任何其他类似的技巧。参数化查询可以传递您想要的任何内容,并且编写起来更简单
  • @Panagiotis Kanavos:你不能被说服留下答案吗?我有点担心其他寻找答案的人看不到你的评论,这比现在的两个要好得多。

标签: c# sql-server string


【解决方案1】:

永远不要尝试在您的代码中构建 SQL 语句。永远不要尝试自己逃避事情。您将获得 100% 的可攻击代码(sql 注入)。

根据您使用的数据库适配器,您可以使用参数化查询。

这是一个使用 ado.net 的示例:

var Query = "select * from customers where city = @city";
var cmd = new SqlCommand(Query);
cmd.Parameters.AddWithValue("@city", txtCity);

查询中的@city 将是占位符,稍后会在驱动程序级别替换。

【讨论】:

  • 请问如果有人尝试执行SQL注入攻击会发生什么?如果一切正常,它会在 try catch "cmd" 中“爆炸”吗?
  • 驱动级别没有被替换。当命令到达 SQL Server 时,命令文本和参数仍然是不同的概念。这就是我们想要的。 SQL Server知道参数是纯粹的数据,没有机会将它们误解为命令的(部分)。
  • 是的,你是对的。我用这个词是为了让初学者更清楚。
  • @Damien 感谢阅读这里的所有内容后,我非常确信使用参数并摆脱尝试去除单引号是最好的计划。
【解决方案2】:

如果您将其作为 varchar/nvarchar 参数传递给 sql server,则不会有任何问题。您不需要将 ' 替换为 ''。

【讨论】:

  • 正在处理的数据是 nvarchar 并且存储为 nvarchar 因为数据可能包含非 ascii 字符。
  • 那么你不必替换任何字符。按原样发送字符串。
【解决方案3】:

在处理字符串数据时,由于 SQL 注入攻击,您应该非常小心如何在 SQL Server 中处理它。 SQL 注入攻击是指攻击者输入格式错误的文本,从而导致在您的数据库上恶意执行 SQL 语句的攻击。

假设您有一个 products 表,下面是您可能编写的要处理的查询

var query = "SELECT * FROM Products WHERE ProductDesc = " + txtProductDesc;

作为攻击者,我可以输入“Smart Phones OR 1=1”,您可以看到查询最终会列出 products 表中的所有产品。

您可以遵循各种清理技术来避免此类攻击,您可能希望应用这些技术来处理此类攻击

SQL Injection Prevention Cheat Sheet

【讨论】:

  • 很高兴知道。非常感谢解释
  • 我可以问一下ORM,比如实体框架或类似的东西吗?它们安全吗?还是我应该采取措施防止 SQL 注入攻击?
  • 危险示例,因为即使您解释 SQL 注入,人们也倾向于复制和粘贴代码。然后他们忘记过滤。使用参数更省钱。
  • @BerndOtt - 我理解这个问题,但是复制代码的人应该先阅读文本然后继续
  • 点很好——正如在别处提到的那样,我肯定会沿着参数线走 a)它解决了这个问题(不是我的代码在任何地方都面向公众)并且 b)也让我不必有我最初的问题中的例程导致处理时间大大减慢。
【解决方案4】:

您需要做的第一件事是使用依赖注入将处理代码与持久性分离。
之后,您处理过的数据将作为 System.String 公开,并由任何 DBMS 通过适当的接口使用。
然后,假设您确实想要持久化到 SQL Server。您可能会使用实体框架,它会反过来处理转义字符,因为它特别知道它将存储该数据(字符串类型)的位置......我想至少。这就是我会做的!如果比我更有经验的人认为这是错误的方法,请随时纠正我!

【讨论】:

    【解决方案5】:

    以下是关于使用参数而不是尝试编写自己的方法来处理操作中的撇号的先前答复,无论是 INSERT、DELETE 还是 UPDATE。下面很简单,通过参数添加新记录并返回新的主键。

    第一个插入有一个撇号,第二个插入三个。下面的类在类项目中,而表单的第二个代码块在引用类项目的表单项目中。

    using System;
    using System.Data.OleDb;
    using System.IO;
    
    namespace DataLib
    {
        public class Operations1
        {
            public Exception InsertException { get; set; }
            private OleDbConnectionStringBuilder Builder = new OleDbConnectionStringBuilder
            {
                Provider = "Microsoft.ACE.OLEDB.12.0",
                DataSource = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Database1.accdb")
            };
            public Operations1()
            {
                if (!(File.Exists(Builder.DataSource)))
                {
                    throw new FileNotFoundException("Failed to find application's database");
                }
            }
            public bool AddNewRow(string CompanyName, string ContactName, ref int Identfier)
            {
    
                bool success = true;
                int affected = 0;
    
                try
                {
                    using (OleDbConnection cn = new OleDbConnection { ConnectionString = Builder.ConnectionString })
                    {
                        using (OleDbCommand cmd = new OleDbCommand { Connection = cn })
                        {
                            cmd.CommandText = @"INSERT INTO Customer (CompanyName,ContactName) 
                                VALUES (@CompanyName, @ContactName)";
    
                            cmd.Parameters.AddWithValue("@CompanyName", CompanyName);
                            cmd.Parameters.AddWithValue("@ContactName", ContactName);
    
                            cn.Open();
    
                            affected = cmd.ExecuteNonQuery();
                            if (affected == 1)
                            {
                                cmd.CommandText = "Select @@Identity";
                                Identfier = Convert.ToInt32(cmd.ExecuteScalar());
                                success = true;
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    InsertException = ex;
                    success = false;
                }
                return success;
            }
        }
    }
    

    表单代码(模拟/静态数据)

    using DataLib;
    using System;
    using System.Windows.Forms;
    
    namespace DataLibDemo
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
            private void button1_Click(object sender, EventArgs e)
            {
                Operations1 ops = new Operations1();
                int newIdentifier = 0;
                if (ops.AddNewRow("O'brien and company", "Mary O'brien", ref newIdentifier))
                {
                    MessageBox.Show($"New Id for Mary {newIdentifier}");
                }
                else
                {
                    MessageBox.Show($"Insert failed: {ops.InsertException.Message}");
                }
    
                if (ops.AddNewRow("O'''brien and company", "Mary O'brien", ref newIdentifier))
                {
                    MessageBox.Show($"New Id for Mary {newIdentifier}");
                }
                else
                {
                    MessageBox.Show($"Insert failed: {ops.InsertException.Message}");
                }
            }
        }
    }
    

    【讨论】:

    • 感谢您的评论,在这种情况下,我使用的是 SQL Server,但您的代码强调了如何以参数方式执行操作,在阅读完此处的所有评论后,这是解决我的问题的方式。
    • 您好,我本来想给您 SQL-Server,但在您最初的帖子中,我使用 MS-Access 为您提供了一条评论“混淆 Access 数据库”。请参阅下面的代码示例,这是我所做的更大的 MSDN 代码示例的一部分。 code.msdn.microsoft.com/Adding-new-records-into-bff5eaaf/…
    【解决方案6】:

    这是您可以实现的最快方式。

    public static string FSQ(string s)
    {
           try
        {
            s = s.Trim();
            return s = (s != null && s != "")?(s.Contains("'") && !s.Contains("''"))?s.Replace("'", "''"): s:"";
    
        }
        catch (Exception ex)
        {
            PEH("FDQ", "Common Module", ex.Message);
            return "";
        }
    } 
    

    【讨论】:

    • 仍然包含原始的许多缺陷之一 - 如果字符串包含 '' 它会跳过替换,因此它允许 ''' 未经修改地通过,因此你仍然有一个 un-此字符串中的转义引号。
    • 谢谢你,虽然不是我在这里发帖时将使用的技术,但它可能在我有用的编码 sn-ps 工具包中的其他地方有用,我认为总体共识是使用 SQL 参数,因为它回答了安全方面和去除单引号的需要。性能是我主要关心的问题,因为我目前的操作方式会占用大量时间。
    猜你喜欢
    • 2010-10-16
    • 2010-10-25
    • 1970-01-01
    • 2011-10-29
    • 1970-01-01
    • 1970-01-01
    • 2010-09-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多