【问题标题】:SQL Injection prevention for concatenated string inside IN clauseIN 子句中连接字符串的 SQL 注入预防
【发布时间】:2019-02-16 18:13:57
【问题描述】:

我有一个变量,它是一个字符串数组。我想传递变量的所有值,将它的所有元素连接成一个字符串。

但我不确定这是否会带来 SQL 注入风险。 我的代码:

private string concatenateStrings(string[] sa)
{
    StringBuilder sb = new StringBuilder();

    foreach (string s in sa)
    {
        if (sb.Length > 0)
        {
            sb.Append(",");
        }
        sb.Append("'");
        sb.Append(s);
        sb.Append("'");
    }
    return sb.ToString();
}

public void UpdateClaimSts(string[] ids)
{
    string query = @"UPDATE MYTABLE
                    SET STATUS = 'X'
                    WHERE TABLEID in (" + concatenateStrings(ids) + ")";

    OracleCommand dbCommand = (OracleCommand)this.Database.GetSqlStringCommand(query) as OracleCommand;
    this.Database.ExecuteNonQuery(dbCommand, this.Transaction);
}

我尝试更改查询以使用参数化查询:

string query = @"UPDATE MYTABLE
                SET STATUS = 'X'
                WHERE TABLEID in (:ids)";

OracleCommand dbCommand = (OracleCommand)this.Database.GetSqlStringCommand(query) as OracleCommand;

dbCommand.Parameters.Add(":ids", OracleType.VarChar).Value = concatenateStrings(ids);
this.Database.ExecuteNonQuery(dbCommand, this.Transaction);

但它不起作用。有什么想法吗?

【问题讨论】:

  • dbCommand.Parameters.AddWithValue("@mydata", ids)
  • 在你的第一个例子中,如果我的第一个id1');DROP TABLE MYTABLE;--(例如)那么你会有一些麻烦
  • @TrishSiquian 它不起作用。基本上,您所做的与我在第二个示例中尝试做的相同。
  • @Rafalon 这就是为什么我要求关于如何防止 SQL 注入的建议。
  • @Rafalon 与某些数据库不同,Oracle 不允许在一条语句中使用两个命令,因此这种 SQL 注入攻击类型会引发错误。还有其他攻击方法,例如1) OR 1=1 绕过过滤器并更新每一行或1) AND EXISTS( SELECT 1 FROM password_table WHERE user = 'Admin' AND hash = '0123456' ) 以查找其他表和敏感数据的存在。

标签: c# sql oracle sql-injection


【解决方案1】:

像这样创建一个 PL/SQL 过程(在 PL/SQL 包中):

TYPE TArrayOfVarchar2 IS TABLE OF MYTABLE.TABLEID%TYPE INDEX BY PLS_INTEGER;

PROCEDURE UPDATE_MYTABLE(TABLEIDs IN TArrayOfVarchar2) IS
BEGIN

    FORALL i IN INDICES OF TABLEIDs
    UPDATE MYTABLE SET STATUS = 'X' WHERE TABLEID = TABLEIDs(i);

END;

然后像这样拨打电话:

using (OracleCommand cmd = new OracleCommand("BEGIN UPDATE_MYTABLE(:tableId); END;"), con))
{
  cmd.CommandType = CommandType.Text;
  // or
  // OracleCommand cmd = new OracleCommand("UPDATE_MYTABLE"), con);
  // cmd.CommandType = CommandType.StoredProcedure;
  var par = cmd.Parameters.Add("tableId", OracleDbType.Varchar2, ParameterDirection.Input);
  par.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
  par.Value = sa;
  par.Size = sa.Length;

  cmd.ExecuteNonQuery();
}

【讨论】:

  • 不幸的是,由于某种原因,我可能无法在数据库中创建过程,因此更喜欢可以在 C# 中完成的解决方案。
【解决方案2】:

作为一种快速且部分(我们假设TABLEID 字段的类型为NUMBER)解决方案,您可以验证sa 中的每个项目 是有效整数:

private string concatenateStrings(string[] sa) {
   return string.Join(", ", sa
     .Where(item => Regex.IsMatch(item, @"^\-?[0-9]+$"))); 
} 

public void UpdateClaimSts(string[] ids) {
  string query = string.Format(
    @"UPDATE MYTABLE
         SET STATUS = 'X'
       WHERE TABLEID IN ({0})", concatenateStrings(ids));
      ...

一般情况下,您可以尝试使用绑定变量(请注意复数:我们必须创建许多个):

public void UpdateClaimSts(string[] ids) {  
  // :id_0, :id_1, ..., :id_N   
  string bindVariables = string.Join(", ", ids
    .Select((id, index) => ":id_" + index.ToString()));

  string query = string.Format(
    @"UPDATE MYTABLE
         SET STATUS = 'X'
       WHERE TABLEID IN ({0})", bindVariables);

  // Do not forget to wrap IDisposable into "using"
  using (OracleCommand dbCommand = ...) {
    ...
    // Each item of the ids should be assigned to its bind variable
    for (int i = 0; i < ids.Length; ++i)
      dbCommand.Parameters.Add(":id_" + i.ToString(), OracleType.VarChar).Value = ids[i];

   ...

【讨论】:

  • "您可以验证 sa 中的每个项目都是有效整数" -- 如果所有内容都应该是整数,则 OP 可以将参数更新为 int[] 而不是 string[] 和避免使用正则表达式。不过,您的第二个建议对我来说看起来不错。
  • @hvd: 没必要 - RDMS Number 和 .Net int (long) 很可能是完全不同的类型(例如 Number(20) 超过 long),这就是为什么 @987654333 @ 而不是 string[] 是一个危险的决定。但是我们经常使用Number(N) 作为键,在这种情况下,我的第一个部分解决方案将起作用
  • 然后阅读 decimal[] 而不是 int[]。我的观点是,您认为一切都必须是整数的假设只是一个假设。没有什么可以阻止 TABLEID 成为文本列,并且 OP 没有 使用 int[]long[]decimal[] 的事实至少是一个微弱的迹象,表明它不是一个整数列。
  • 不使用插值字符串是否可行?这样它也可以支持旧的 .net 框架?
  • @rcs:当然,内插字符串可以更改为它们的 string.Format 等效项
【解决方案3】:

C# 有一个 OracleCollectionType.PLSQLAssociativeArray 类型,用于将数组传递给 PL/SQL 关联数组数据类型,但这不能用于 SQL 查询,因为它只是一个 PL/SQL 数据结构。

不幸的是,它不支持将数组传递给 SQL 集合数据类型(可用于 SQL 查询)。

解决此问题的一种方法是让您的 DBA 创建一个简单的函数来将 PL/SQL 关联数组转换为 SQL 集合,然后将其用作查询中的中间步骤:

CREATE TYPE varchar2s_array_type IS TABLE OF VARCHAR2(100)
/

CREATE PACKAGE utils IS
  TYPE varchar2s_assoc_array_type IS TABLE OF VARCHAR2(100) INDEX BY PLS_INTEGER;

  FUNCTION assoc_array_to_collection(
    p_assoc_array IN varchar2s_assoc_array_type
  ) RETURN varchar2s_array_type DETERMINISTIC;
END;
/

CREATE PACKAGE BODY utils IS
  FUNCTION assoc_array_to_collection(
    p_assoc_array IN varchar2s_assoc_array_type
  ) RETURN varchar2s_array_type DETERMINISTIC
  IS
    p_array varchar2s_array_type := varchar2s_array_type();
    i PLS_INTEGER;
  BEGIN
    IF p_assoc_array IS NOT NULL THEN
      i := p_assoc_array.FIRST;
      LOOP
        EXIT WHEN i IS NULL;
        p_array.EXTEND();
        p_array(p_array.COUNT) := p_assoc_array(i);
        i := p_assoc_array.NEXT(i);
      END LOOP;
    END IF;
    RETURN p_array;
  END;
END;
/

然后您可以将代码更改为在 SQL 语句中使用 MEMBER OF 而不是 IN

UPDATE MYTABLE
SET    STATUS = 'X'
WHERE  TABLEID MEMBER OF utils.assoc_array_to_collection(:ids)

并使用类似的方式绑定参数(我不是 C# 用户,所以这只是为了让您大致了解该方法,即使语法不完全正确):

var par = cmd.Parameters.Add(":ids", OracleDbType.Varchar2, ParameterDirection.Input);
par.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
par.Value = ids;
par.Size = ids.Length;
cmd.ExecuteQuery();

然后您可以在许多查询中重用通用函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-28
    • 2012-05-22
    • 2016-06-12
    相关资源
    最近更新 更多