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();
然后您可以在多个查询中重复使用通用函数。
我有一个变量,它是一个字符串数组。我想传递变量的所有值,将它的所有元素连接成一个字符串。
但我不确定这是否会带来 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();
然后您可以在多个查询中重复使用通用函数。