参数化查询如何帮助防止SQL注入攻击?

70
在查询1和2中,文本框中的文本被插入到数据库中。参数化查询在这里有什么重要性?
  1. txtTagNumber作为查询参数传递

SqlCommand cmd = new SqlCommand("INSERT INTO dbo.Cars " +"VALUES(@TagNbr);" , conn);
cmd.Parameters.Add("@TagNbr", SqlDbType.Int);
cmd.Parameters["@TagNbr"].Value = txtTagNumber.Text;
  • 在构建查询之前将 txtTagNumber 转换为整数

  • int tagnumber = txtTagNumber.Text.ToInt16(); /* EDITED */
    INSERT into Cars values(tagnumber.Text); /* then is it the same? */
    

    此外,我会使用正则表达式验证来阻止插入非法字符。


    8
    当参数化查询已经解决问题时,为什么还要编写自己的验证? - OJ.
    在自由文本输入中进行加法验证很困难,参数化查询可以正确地进行“转义”(客户端直接将数据传递给服务器)。 - dvhh
    @dvhh:你能否举个转义的例子? - sqlchild
    一个无效的转义示例是String.replace("'","''")。参数化执行命令使用另一种编码形式,你几乎不需要知道。 - dvhh
    6个回答

    60

    参数化查询在运行SQL查询之前对参数进行适当的替换。这完全消除了“脏”输入改变查询含义的可能性。也就是说,如果输入包含SQL代码,它不能成为执行语句的一部分,因为SQL代码永远不会被注入到生成的语句中。


    1
    @sqlchild:表格、列或过程名称(通常标识符)不能被参数化,只能使用“值”或“参数”(正如OJ所说)。例如在WHERE或JOIN/IN子句中使用的参数,函数或过程的参数等。因此称为“参数化”查询。 - Christian.K
    2
    @Christian.K:先生,我的意思是,如果我在文本框中传递DROP table Cars到一个插入语句中,insert into cars values(@carname); cmd.Parameters.Add(@carname,SQLDBTYPE varchar),那么参数化查询会做什么? - sqlchild
    4
    抱歉,我误解了。它只会在相应的列/字段中插入字符串“DROP table Cars”。 - Christian.K
    2
    @sqlchild:这不是所谓的“转义”,而是数据库提供的机制,使转义变得不必要。我认为你需要阅读更多关于SQL注入的背景知识(例如在这里http://en.wikipedia.org/wiki/SQL_injection),这超出了评论或对你原始问题的回答。 - Christian.K
    3
    @sqlchild:您已经开始改变话题,并且已经接受了原始问题的答案。请考虑发布关于其他您需要了解的内容的新问题。这是SO应该运作的方式,可以更好地发现问题并使每个人受益。 - Christian.K
    显示剩余6条评论

    42

    Imagine a dynamic SQL query

    sqlQuery='SELECT * FROM custTable WHERE User=' + Username + ' AND
    Pass=' + password
    

    so a simple sql injection would be just to put the Username in as ' OR 1=1-- This would effectively make the sql query:

    sqlQuery='SELECT * FROM custTable WHERE User='' OR 1=1-- ' AND PASS='
    + password
    

    This says select all customers where they're username is blank ('') or 1=1, which is a boolean, equating to true. Then it uses -- to comment out the rest of the query. So this will just print out all the customer table, or do whatever you want with it, if logging in, it will log in with the first user's privileges, which can often be the administrator.

    Now parameterized queries do it differently, with code like:

    sqlQuery='SELECT * FROM custTable WHERE User=? AND Pass=?'
    

    parameters.add("User", username) parameters.add("Pass", password)

    where username and password are variables pointing to the associated inputted username and password

    Now at this point, you may be thinking, this doesn't change anything at all. Surely you could still just put into the username field something like Nobody OR 1=1'--, effectively making the query:

    sqlQuery='SELECT * FROM custTable WHERE User=Nobody OR 1=1'-- AND
    Pass=?'
    

    And this would seem like a valid argument. But, you would be wrong.

    The way parameterized queries work, is that the sqlQuery is sent as a query, and the database knows exactly what this query will do, and only then will it insert the username and passwords merely as values. This means they cannot effect the query, because the database already knows what the query will do. So in this case it would look for a username of "Nobody OR 1=1'--" and a blank password, which should come up false.

    来源:lavamunky.com; 2011年11月


    24

    SQL注入是指当一个可能包含SQL语句的参数没有被正确处理时发生的情况。

    例如:

    var sqlquerywithoutcommand = "select * from mytable where rowname =  '" + condition+''";
    

    条件是来自请求中用户提供的字符串。如果条件是恶意的,则会如下所示:

    var sqlquerywithoutcommand = "select * from mytable where rowname =  '" + "a' ;drop table  mytable where '1=1"+"'";
    

    如果不使用参数,您可能最终会运行恶意脚本。

    然而,使用参数可以清除任何可能转义字符串字符的输入,这意味着无论输入什么,都可以确保它无法运行注入脚本。

    使用带有参数的命令对象,实际执行的 SQL 如下所示:

    select * from mytable where rowname = 'a'';drop table mytable where 1=1'''
    

    本质上,它将查找一行 rowname = a';drop table mytable where 1=1' 并且不运行剩余的脚本。


    2
    但是当 SQL 从参数中获取值时,它是如何读取它的呢?我的意思是它会像直接从字符串中读取一样读取它,也就是说它会将其读取为 "drop table mytable",还是我错了? - sqlchild
    @sqlchild: 不是这样的...它会将所有的 ' 转义为 '',使整个字符串成为参数...让我把这个加入到答案中。 - Whimsical

    5

    参数化查询处理一切 - 为什么要费这个劲呢?

    使用参数化查询,除了一般的注入问题外,您还可以处理所有数据类型,包括数字(int和float),字符串(带有嵌入式引号),日期和时间(没有格式问题或本地化问题,即使在不使用不变文化区域设置调用.ToString()并且您的客户端移动到具有意外日期格式的计算机上)。


    他们肯定能够处理SQL注入,但不能处理其他类型的注入(主要是Web端注入,如JavaScript)。 - dvhh

    2
    很容易理解为什么会有这样的感觉。
    sqlQuery = "select * from users where username='+username+';"
    

    vs

    sqlQuery = "select * from users where username=@username;"
    

    以上两个查询看起来似乎做的是同样的事情,但实际上并不相同。

    前者使用输入作为查询条件,后者仅在执行查询时将输入内容替换到查询语句中。

    更明确地说,参数值存储在变量内存所在的堆栈的某个位置,并在需要时用于搜索。

    因此,如果我们在用户名字段中输入 ' OR '1'='1 ,前者会动态构建一个新的查询作为 SQL 查询字符串sqlQuery 的一部分,然后执行该查询。

    而后者则会在查询字符串sqlQuery中静态指定的查询中,在users表的username字段中搜索' OR '1'='1'

    总之,这就是如何使用参数进行查询:

    SqlCommand command = new SqlCommand(sqlQuery,yourSqlConnection);
    
    SqlParameter parameter = new SqlParameter();
    parameter.ParameterName = "@username";
    parameter.Value = "xyz";
    
    command.Parameters.Add(parameter);
    

    2

    参数化查询允许客户端将数据与查询文本分开传递。 在大多数自由文本中,您需要进行验证+转义。 当然,参数化不能防止其他类型的注入攻击,但由于参数是单独传递的,它们不被用作执行文本查询。

    一个很好的类比是“最近”的执行位,它与大多数现代处理器和操作系统一起使用,以保护免受缓冲区溢出的影响。它仍然允许缓冲区溢出,但可以防止注入数据的执行。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接