0
点赞
收藏
分享

微信扫一扫

Dapper防sql注入,同一条SQL支持多种数据库

大漠雪关山月 2022-03-12 阅读 69

前言

防SQL注入,常用的方案是使用Dapper执行SQL的参数化查询。例如:

using (IDbConnection conn = CreateConnection())
{
string sqlCommandText = @"SELECT * FROM USERS WHERE ID=@ID";

Users user = conn.Query<Users>(sqlCommandText, new { ID = 2 }).FirstOrDefault();
Console.WriteLine(user.Name);
}

但是,不同数据库支持不同的sql参数格式,例如,ORACLE必须使用​​:ID​​,否则上述代码会报错:

Dapper防sql注入,同一条SQL支持多种数据库_oracle

Pseudo-Positional Parameters

查看Dapper的​源代码[1]​,发现有这样一段:

cmd.CommandText = pseudoPositional.Replace(cmd.CommandText, match =>
{
string key = match.Groups[1].Value;
if (!consumed.Add(key))
{
throw new InvalidOperationException("When passing parameters by position, each parameter can only be referenced once");
}
else if (parameters.TryGetValue(key, out IDbDataParameter param))
{
if (firstMatch)
{
firstMatch = false;
cmd.Parameters.Clear(); // only clear if we are pretty positive that we've found this pattern successfully
}
// if found, return the anonymous token "?"
if (Settings.UseIncrementalPseudoPositionalParameterNames)
{
param.ParameterName = (++index).ToString();
}
cmd.Parameters.Add(param);
parameters.Remove(key);
consumed.Add(key);
return "?";
}
else
{
// otherwise, leave alone for simple debugging
return match.Value;
}
});

通过查看​Dapper教程[2]​,原来,这是实现被称为​​Pseudo-Positional Parameters​​(伪位置参数)的代码,作用是为了不支持命名参数的数据库提供者能够使用参数化SQL,例如OleDB:

//代码
var docs = conn.Query<Document>(@"
select * from Documents
where Region = ?region?
and OwnerId in ?users?", new { region, users }).AsList();

//SQL
select * from Documents
where Region = ?
and OwnerId in (?,?,?)

自定义Pseudo-Positional Parameters

依葫芦画瓢,我们可以实现自己的​​Pseudo-Positional Parameters​​,以便支持更多的数据提供者:

private static readonly Regex pseudoRegex = new Regex(@"\$([\p{L}_][\p{L}\p{N}_]*)\$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);

public static string ReplacePseudoParameter(IDbConnection conn, string cmdText)
{
return pseudoRegex.Replace(cmdText, match => {
var key = match.Groups[1].Value;
if (conn is OleDbConnection)
{
return "?" + key + "?";
}
return (conn is OracleConnection ? ":" : "@") + key;
});
}

使用方式是在参数名称两边加上​​$​​:

string sqlCommandText = @"SELECT * FROM USERS WHERE ID=$ID$";

Users user = conn.Query<Users>(ReplacePseudoParameter(conn, sqlCommandText), new { ID = 2 }).FirstOrDefault();

结论

通过实现​​Pseudo-Positional Parameters​​功能,我们让Dapper防sql注入支持了多种数据库。


参考资料

[1]

源代码: https://github.com/DapperLib/Dapper/blob/main/Dapper/SqlMapper.cs#L1783

[2]

Dapper教程: https://riptutorial.com/Dapper/example/13835/pseudo-positional-parameters--for-providers-that-don-t-support-named-parameters-

举报

相关推荐

0 条评论