SQL IN 子句内连接字符串的注入预防

SQL Injection prevention for concatenated string inside IN clause

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

但我不确定这是否会带来 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);

但是不行。有什么想法吗?

像这样创建一个 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();
}

作为快速且部分(我们假设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));
      ...

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

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];

   ...

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();

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