让敏感数据远离堆

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" 用户数据,并且您希望防止对服务器的攻击:

  1. 保护该进程免受同一台机器上其他进程的远程攻击(运行 在隔离的 VM 中,运行 在 Docker 中,运行 一切都是独立的(非-root) 用户
  2. 为了防止本地攻击,请确保敏感数据不会最终进入持久存储(即 SecureString)。正如已经指出的那样,使用您当前的实现很难做到这一点,因为您需要强化应用程序的 所有 层,从 SSL/TLS 终止到控制器。

这是静态分析工具不能总是被认真对待的一个例子。

当密码和敏感数据进入您的应用程序时,猜猜它进入的是什么类型的结构?一个 RequestObject,以字符串形式出现。你猜怎么着?它已经在堆上,您无能为力。当然,当你将它传递给你的结构时,你可以玩游戏试图防止它再次被放入堆中,但这不会改变它已经在其他地方不受控制的事实。

不要误以为 SecureString 和其他噱头就是解决方案。微软现在 acknowledges that SecureString isn't what it claims to be,它正在从 .Net Core 中删除。

底线:当 Checkmarx 和 Fortify 等工具抱怨堆检查漏洞时,请忽略它们。这些规则应该被抛弃。