我有一个 .net Core API,我需要从中调用存储过程。我有以下代码,但我被告知它存在 SQL 注入的风险。我的经验不够,无法确定威胁在哪里。我怎样才能以更安全的方式调用这个过程?
[HttpPut]
[Route("move")]
public IActionResult Move(string id, string dir)
{
if (id == null || dir == null)
{
return BadRequest();
}
try
{
// todo: sql injection threat
_db.Database.ExecuteSqlCommand($"Exec ProcMove @ParaId = {id}, @Direction = {dir}");
_db.SaveChanges();
return Ok();
}
catch (Exception)
{
return BadRequest();
}
}
我使用 Angular 5 作为前端,并使用
调用端点http.put("http://localhost:5000/api/move?id={id}&dir={dir}");
这里有几个问题。第一个唾手可得的成果是输入在发送到数据库之前没有经过验证。作为字符串,
id
和dir
都可以是用户想要发送的任何内容。例如,如果攻击者发送了如下恶意表述:
.../move?id={id}&dir=SomeValidDir;DROP%20TABLE%20someTable;
你可能没空了。在这种情况下,Q&D(快速而肮脏)的解决方案是在将
id
和 dir
传递给数据库之前确保对它们进行一些约束。 id
真的是整数吗?您的上下文中有效的 dir
是否符合“始终是单个单词,仅包含字母数字字符”等标准?例如,快速修复 id
可能是更改函数签名:
public IActionResult Move(int id, string dir)
攻击者能做的最糟糕的事情就是选择一个奇怪的数字;您的框架(实际上是语言)将负责确保它是整数的其余部分。如果有进一步的限制,例如“
id
必须介于 10 到 42,000 之间”,则在发送到数据库之前包括这些进一步的检查。
第二种更优雅且“更普遍正确”的解决方案是使用准备好的语句。预准备语句的概念是,您首先告诉数据库查询的“格式”以及参数的内容/位置,然后在单独的调用中传递参数。我将在这里省略该描述(但指向有关准备好的语句的 MSDN 文档),因为存储过程本质上是准备好的语句。 因此,您的(建设性)批评者可能会因为您“没有”使用“准备好的陈述”而犹豫不决,而他们这样做是正确的。为了解决他们的批评,您需要直接调用存储过程,并单独传递参数:
using ( SqlCommand cmd = new SqlCommand("ProcMove", con) ) {
// ...
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@ParaId", SqlDbType.VarChar).Value = id;
cmd.Parameters.Add("@Direction", SqlDbType.VarChar).Value = dir;
cmd.ExecuteNonQuery();
// ...
}
我不确定这段代码是否真的有效,但这似乎是您在尝试字符串替换或字符串连接,这就是风险所在。您想要参数化 SQL。
类似这样的: