让敏感数据远离堆
Keep Sensitive Data Off Heap
在使用 Checkmarx 扫描 ASP.net MVC 应用程序时,我经常看到堆检查漏洞。所以我开始想知道我是否可以使用自定义模型绑定器或内置 ByteArrayModelBinder
将密码和其他敏感字符串保留在堆之外,以进行受控处置。我想出了以下解决方案,它通过字节数组发布和显示敏感数据,但我想知道这些数据是否仍在通过某个字符串进入堆中。 (注意:显示动作只是为了调试。)
ViewModel
public class ByteArrayViewModel
{
public byte[] SensitiveData { get; set; }
}
输入视图
@model MvcHandlingSensativeStrings.Models.ByteArrayViewModel
@{
ViewBag.Title = "byte[] Post";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>@ViewBag.Title</h2>
@using (Html.BeginForm("Post", "ByteArray", FormMethod.Post))
{
@Html.TextBoxFor(m=>m.SensitiveData);
<button type="submit">Send</button>
}
控制器
public class ByteArrayController : Controller
{
public ActionResult Index()
{
return View(new ByteArrayViewModel());
}
[HttpPost]
public ActionResult Post(ByteArrayViewModel viewModel)
{
try
{
// Handle sensitive data here
return View("Display", viewModel);
}
catch
{
return View("Index");
}
finally
{
// Clear sensitive data from memory
//Array.Clear(viewModel.SensitiveData, 0, viewModel.SensitiveData.Leng
}
}
public ActionResult Display(ByteArrayViewModel viewModel)
{
return View(viewModel);
}
}
显示视图
@model MvcHandlingSensativeStrings.Models.ByteArrayViewModel
@{
ViewBag.Title = "byte[] Display";
Layout = "~/Views/Shared/_Layout.cshtml";
string s = Convert.ToBase64String(Model.SensitiveData);
}
<h2>@ViewBag.Title</h2>
<p>@s</p>
<p>@Model.SensitiveData.GetType().ToString()</p>
显示输出
*更新*
这表明在 ByteArrayModelBinder
或任何其他模型绑定器执行之前,表单参数存储在字符串数组中,因此容易受到堆检查。
* 更新 2 *
如果你看一下微软对 NetworkCredential 的实现,你会发现尽管 Password 属性 是一个字符串,但底层使用 SecureString 进行存储。
答案是否定的,你并没有让这更安全一点。
为了避免在内存中存储可以恢复的秘密,您需要使用 C# 中的 SecureString class(或 Windows 中的底层加密 API)。这将通过一些努力来通过在处置时擦除它们来使秘密更难恢复,但也确保它们永远不会出现在页面文件中。
但更重要的是,将秘密打印到网页(即使使用 TLS)显然会将秘密暴露给 Web 堆栈的所有层,使其容易受到多种本地攻击以及对浏览器的攻击客户.
解决方案是永远不要以明文形式存储密码,而是使用加盐哈希。不要尝试自己发明这样的东西,使用经过验证的解决方案来安全地验证和存储密码。很容易忽略一些完全破坏应用程序安全性的东西。例如,密码 API 有一个工具可以做到这一点。参见此处的示例:
如果您绝对需要接受来自表单的 "secret" 用户数据,并且您希望防止对服务器的攻击:
- 保护该进程免受同一台机器上其他进程的远程攻击(运行 在隔离的 VM 中,运行 在 Docker 中,运行 一切都是独立的(非-root) 用户
- 为了防止本地攻击,请确保敏感数据不会最终进入持久存储(即 SecureString)。正如已经指出的那样,使用您当前的实现很难做到这一点,因为您需要强化应用程序的 所有 层,从 SSL/TLS 终止到控制器。
这是静态分析工具不能总是被认真对待的一个例子。
当密码和敏感数据进入您的应用程序时,猜猜它进入的是什么类型的结构?一个 RequestObject,以字符串形式出现。你猜怎么着?它已经在堆上,您无能为力。当然,当你将它传递给你的结构时,你可以玩游戏试图防止它再次被放入堆中,但这不会改变它已经在其他地方不受控制的事实。
不要误以为 SecureString 和其他噱头就是解决方案。微软现在 acknowledges that SecureString isn't what it claims to be,它正在从 .Net Core 中删除。
底线:当 Checkmarx 和 Fortify 等工具抱怨堆检查漏洞时,请忽略它们。这些规则应该被抛弃。
在使用 Checkmarx 扫描 ASP.net MVC 应用程序时,我经常看到堆检查漏洞。所以我开始想知道我是否可以使用自定义模型绑定器或内置 ByteArrayModelBinder
将密码和其他敏感字符串保留在堆之外,以进行受控处置。我想出了以下解决方案,它通过字节数组发布和显示敏感数据,但我想知道这些数据是否仍在通过某个字符串进入堆中。 (注意:显示动作只是为了调试。)
ViewModel
public class ByteArrayViewModel
{
public byte[] SensitiveData { get; set; }
}
输入视图
@model MvcHandlingSensativeStrings.Models.ByteArrayViewModel
@{
ViewBag.Title = "byte[] Post";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>@ViewBag.Title</h2>
@using (Html.BeginForm("Post", "ByteArray", FormMethod.Post))
{
@Html.TextBoxFor(m=>m.SensitiveData);
<button type="submit">Send</button>
}
控制器
public class ByteArrayController : Controller
{
public ActionResult Index()
{
return View(new ByteArrayViewModel());
}
[HttpPost]
public ActionResult Post(ByteArrayViewModel viewModel)
{
try
{
// Handle sensitive data here
return View("Display", viewModel);
}
catch
{
return View("Index");
}
finally
{
// Clear sensitive data from memory
//Array.Clear(viewModel.SensitiveData, 0, viewModel.SensitiveData.Leng
}
}
public ActionResult Display(ByteArrayViewModel viewModel)
{
return View(viewModel);
}
}
显示视图
@model MvcHandlingSensativeStrings.Models.ByteArrayViewModel
@{
ViewBag.Title = "byte[] Display";
Layout = "~/Views/Shared/_Layout.cshtml";
string s = Convert.ToBase64String(Model.SensitiveData);
}
<h2>@ViewBag.Title</h2>
<p>@s</p>
<p>@Model.SensitiveData.GetType().ToString()</p>
显示输出
*更新*
这表明在 ByteArrayModelBinder
或任何其他模型绑定器执行之前,表单参数存储在字符串数组中,因此容易受到堆检查。
* 更新 2 *
如果你看一下微软对 NetworkCredential 的实现,你会发现尽管 Password 属性 是一个字符串,但底层使用 SecureString 进行存储。
答案是否定的,你并没有让这更安全一点。
为了避免在内存中存储可以恢复的秘密,您需要使用 C# 中的 SecureString class(或 Windows 中的底层加密 API)。这将通过一些努力来通过在处置时擦除它们来使秘密更难恢复,但也确保它们永远不会出现在页面文件中。
但更重要的是,将秘密打印到网页(即使使用 TLS)显然会将秘密暴露给 Web 堆栈的所有层,使其容易受到多种本地攻击以及对浏览器的攻击客户.
解决方案是永远不要以明文形式存储密码,而是使用加盐哈希。不要尝试自己发明这样的东西,使用经过验证的解决方案来安全地验证和存储密码。很容易忽略一些完全破坏应用程序安全性的东西。例如,密码 API 有一个工具可以做到这一点。参见此处的示例:
如果您绝对需要接受来自表单的 "secret" 用户数据,并且您希望防止对服务器的攻击:
- 保护该进程免受同一台机器上其他进程的远程攻击(运行 在隔离的 VM 中,运行 在 Docker 中,运行 一切都是独立的(非-root) 用户
- 为了防止本地攻击,请确保敏感数据不会最终进入持久存储(即 SecureString)。正如已经指出的那样,使用您当前的实现很难做到这一点,因为您需要强化应用程序的 所有 层,从 SSL/TLS 终止到控制器。
这是静态分析工具不能总是被认真对待的一个例子。
当密码和敏感数据进入您的应用程序时,猜猜它进入的是什么类型的结构?一个 RequestObject,以字符串形式出现。你猜怎么着?它已经在堆上,您无能为力。当然,当你将它传递给你的结构时,你可以玩游戏试图防止它再次被放入堆中,但这不会改变它已经在其他地方不受控制的事实。
不要误以为 SecureString 和其他噱头就是解决方案。微软现在 acknowledges that SecureString isn't what it claims to be,它正在从 .Net Core 中删除。
底线:当 Checkmarx 和 Fortify 等工具抱怨堆检查漏洞时,请忽略它们。这些规则应该被抛弃。