在删除变量(或超出范围)之前覆盖存储在变量中的敏感数据是否是一种良好的安全编程习惯?我的想法是,这将阻止黑客由于数据残留而无法读取RAM中的任何潜在数据。覆盖几次会增加安全性吗?这是我在C ++中谈论的一个小例子(包括注释)。

void doSecret()
{
  // The secret you want to protect (probably best not to have it hardcoded like this)
  int mySecret = 12345;

  // Do whatever you do with the number
  ...

  // **Clear out the memory of mySecret by writing over it**
  mySecret = 111111;
  mySecret = 0;
  // Maybe repeat a few times in a loop
}


一个想法是,如果这确实增加了安全性,它将很好,如果编译器自动添加了执行此操作的指令(也许默认情况下,或者可能是在删除变量时告诉编译器执行此操作)。



被称为“本周信息安全问题”。
阅读2014年12月12日博客条目以了解更多详细信息,或提交自己的“本周问题”。


评论

请注意,根据语言,这可能会很棘手。 (请参阅两个链接的问题,尤其是它们的答案)

另请注意,java中的字符串是不可变的,因此覆盖(将新值分配给引用变量)将无效。

这不仅是一个好主意,而且您可能想采取的其他步骤是:锁定内存(以确保不被写入以交换),保护页面,以便在初始化秘密数据后将其设为只读(也可能是)以将页面标记为根本不可访问(除了您打算访问的小窗口中的所有窗口之外),并在机密之后立即将“ canary”值写入内存,以便在解除分配期间检测是否被溢出覆盖,并在发生上溢和下溢时在SEGV的机密之前和之后分配额外的不可访问的保护页。

@rkosegi,这就是为什么键应该使用byte []而不是字符串的原因。
除非您将变量声明为volatile,否则C / C ++编译器实际上可以自由地优化覆盖,因为它们不会改变执行代码的结果(该值在任何地方都不会使用)。

#1 楼

是的,覆盖然后删除/释放该值是一个好主意。不要假设您要做的只是“覆盖数据”或让它超出GC的处理范围,因为每种语言与硬件的交互方式都不相同。
保护变量时,您可能需要考虑一下关于:

加密(在内存转储或页面缓存的情况下)
固定在内存中
能够标记为只读(以防止任何进一步的修改)
安全的构造,不允许在优化编译器中传递常量字符串(请参见链接文章中的注释:零内存宏)

“擦除”的实际实现取决于语言和平台。研究您使用的语言,看看是否可以安全地进行编码。
为什么这是个好主意?故障转储以及包含堆的任何内容都可能包含您的敏感数据。保护内存中数据时请考虑使用以下内容。

SecureZeroMemory
.NET SecureString
C ++模板

请参见StackOverflow了解每种语言的实现指南。
您应该注意,即使使用供应商指南(在这种情况下为MSFT),仍然可以转储SecureString的内容,并且可能具有针对高安全性场景的特定使用指南。

评论


虽然也可以在程序运行期间查看程序存储空间的内容。删除内存只会删除指向内存的指针(就像删除文件会从文件系统删除指向文件的文件指针一样)。覆盖它有助于保持其“安全”。

– Desthro
2014年12月4日16:35



假定覆盖实际上是覆盖它,并且不只是将指针移动到具有新值的内存的某个新部分(这意味着旧数据会一直存在,直到重新使用内存为止,就像删除它一样)。

– Lawtonfogle
2014年12月4日下午16:52

@Desthro:除非您在实模式下工作(即您正在编写OS内核),否则指针通常只是虚拟内存地址;只有内核可以访问实存储器地址。当内存被覆盖时,操作系统当然有可能将两个内存页面移动到不同的实际地址,这可以在不更改虚拟地址的情况下完成。例如,这发生在交换期间。

– Lie Ryan
2014年12月4日17:37



我认为您绝对希望volatile关键字确保发生的事情实际上是覆盖

–raptortech97
2014年12月5日的1:48

@ raptortech97 volatile实际上可以确保吗?假定在敏感变量可以被覆盖之前,进程已被挂起并且其内存已被调出,然后将其恢复。无法保证虚拟内存将被分页回物理内存的同一部分,是吗?在这种情况下,volatile不能保证覆盖。我认为。老实说,我不确定。 (volatile阻止了某些编译器优化,我明白了,但似乎您在说它的作用远不止于此)

– David Z
2014-12-5 8:57



#2 楼

是否存储不再使用的值?似乎可以对其进行优化,无论它可能带来什么好处。

此外,根据语言本身的工作方式,您实际上可能不会覆盖内存中的数据。例如,在使用垃圾收集器的语言中,不会立即将其删除(这是假设您没有留下任何其他引用)。例如,在C#中,我认为以下内容不起作用。

string secret = "my secret data";

...lots of work...

string secret = "blahblahblah";
一直闲逛直到垃圾被收集,因为它是不可变的。最后一行实际上是在创建一个新字符串并对其进行秘密指向。它不能加快删除实际机密数据的速度。

有好处吗?假设我们以汇编语言或低杠杆语言编写了数据,那么我们可以确保覆盖数据,并让计算机进入睡眠状态或在应用程序运行时将其保留,并且我们的RAM被一个邪恶的女仆刮掉了,邪恶的女仆在机密被覆盖之后但在被删除之前(可能是很小的空间)就获得了我们的RAM数据,而RAM或硬盘驱动器上的其他任何内容都不会泄露此秘密...然后我看到了可能的增加在安全性方面。

但是成本与收益似乎使此安全性优化在我们的优化列表中非常低(并且通常在大多数应用程序中都低于“有价值”的意义)。

我可能会看到这种在特殊芯片中的使用受到限制,意味着可以在短时间内保存秘密,以确保它们在尽可能短的时间内保存,但是即使如此,我仍然不确定成本的任何好处。 />

评论


“可能是一个很小的空间”-提出一个很小的空间至少是该过程的剩余生命周期的案例可能并不难。也就是说,可能存在由于各种原因(而且我不仅仅是说内存泄漏)而可能会长时间闲置的未使用内存。毕竟,邪恶的女仆会扫描我们所有的记忆,寻找任何可能有用的东西。因此,在不使用它时覆盖它会关闭一个窗口。但是就像您说的那样,我们仍然无法指望mySecret = 0带来任何安全益处; mySecret = -1;。

–史蒂夫·杰索普(Steve Jessop)
2014年12月4日18:36



如果您担心分配会被优化,只需将变量声明为volatile(在C / C ++中)。这样就可以了。如果值是字符串,请覆盖字符串的内容,而不要使用简单的赋值。

– Alex D
14-12-5在7:21



许多与语言相关的技巧不会传播。您使用的任何存储机密数据的库都需要逐行检查以确保它们能够执行此操作。如果没有,您是否重新实现数学/密码库?每个资源花费的安全性增加比率是如此之低,并且(通过重写库)使您的安全性恶化的可能性非常高,以至于我看不到普通开发人员的合理用例。

– Lawtonfogle
2014年12月5日13:54

#3 楼

您需要一个威胁模型

您甚至不应该开始考虑覆盖安全变量,除非您有一个威胁模型来描述要阻止的黑客类型。安全总是要付出代价的。在这种情况下,成本就是教导开发人员维护所有这些额外代码以保护数据的开发成本。这笔费用意味着您的开发人员很可能会犯错误,并且这些错误比内存问题更可能是泄漏的根源。


攻击者能否访问您的内存?如果是这样,您是否有理由认为他们不能/不会在覆盖值之前就嗅探它?攻击者可以在什么时间范围内访问您的内存
攻击者可以访问核心转储吗?您是否介意它们是否可以访问敏感数据以换取足够高的噪声以首先导致核心转储?
这是开放源还是封闭源?如果它是开源的,则必须担心多个编译器,因为编译器将始终优化诸如覆盖数据之类的内容。他们的工作不是提供安全性。 (对于一个现实生活的例子,Schneier的PasswordSafe具有专门的类来保护未加密的密码数据。为此,他使用Windows API函数来锁定内存,并强制其被正确覆盖,而不是使用编译器来执行此操作。给他)
这是垃圾收集的语言吗?您知道如何迫使您的特定版本的特定垃圾收集器实际删除您的数据吗?
攻击者可以进行几次尝试来获取敏感数据,然后再注意并以其他方式(例如防火墙)?
这是在虚拟机中运行吗?您如何确定Hypervisor的安全性?
攻击者是否具有物理访问权限?例如,Windows非常乐意使用闪存驱动器缓存虚拟内存。攻击者所需要做的就是说服Windows将其推入闪存驱动器。发生这种情况时,真的很难解决。实际上,是如此的困难,以至于没有可靠的方法可以从闪存驱动器中可靠地清除数据。

在尝试覆盖敏感数据之前,需要解决这些问题。尝试在不解决线程模型的情况下覆盖数据是一种错误的安全感。

评论


另一个代价是需要更多的代码和更多的指令来执行计算机(速度较慢),但是是的,考虑威胁的好点。我认为黑客在程序运行时(甚至在此之后)从RAM访问机密数据将是最大的威胁之一。我认为,如果可以告诉编译器(或使用您所使用的语言的任何构建/运行方式)在完成时将数据归零,那将是很好的。

–乔纳森
2014年12月6日17:29



“攻击者可以访问您的内存吗?”-好,我们知道攻击者将使用该内存(如果有)。而且它不必在本地计算机上。例如,我们知道NSA会将Windows错误报告记录到其XKeyscore系统中,以帮助获得未经授权的访问。无论您的威胁模型是否包括它,都会发生这种情况:)

–user29925
2014年12月8日下午6:17

它是否发生与您是否真的想对此做任何事情有很大的不同。用凯文·米特尼克(Kevin Mitnick)的话说:“唯一真正安全的计算机是断开互联网连接,拔下电源,存储在地下水泥仓中并由武装警卫控制的计算机,即使这样,我也会不时检查一下。”如果您确实想确保程序NSA的安全,那么除了StackExchange以外,您还需要更多的帮助;-)

–Cort Ammon
2014年12月8日15:43

#4 楼

是的,在安全性方面,优良作法是在不再需要数据时覆盖特别敏感的数据,即作为对象析构函数的一部分(由语言提供的显式析构函数或程序在取消分配对象之前执行的操作)目的)。甚至最好的做法是覆盖本身不敏感的数据,例如,将不再使用的数据结构中的指针字段清零,并且当指向的对象被释放时将指针清零,即使您知道您将不再使用该字段。

执行此操作的一个原因是,如果数据因外部因素而泄漏,例如暴露的核心转储,被盗的休眠映像,受感染的服务器允许正在运行的进程等的内存转储。攻击者提取RAM记忆棒并利用数据剩余性的物理攻击很少引起关注,除非在便携式计算机上,也许在移动设备(例如电话)上(由于RAM被焊接,所以阈值较高) ,即使如此,大多数情况下也仅在有针对性的情况下进行。改写值的剩余性不是问题:要在RAM芯片内部进行探测以检测可能受改写值影响的任何微小的微观电压差,都将需要非常昂贵的硬件。如果您担心对RAM的物理攻击,那么更大的担忧将是确保将数据写在RAM中,而不仅仅是在CPU缓存中。但是,通常,这通常是一个很小的问题。

覆盖陈旧数据的最重要原因是为了防御程序错误,这些错误会导致使用未初始化的内存,例如臭名昭​​著的Heartbleed。这超出了敏感数据的范围,因为风险不仅限于数据泄漏:如果存在某个软件错误导致指针字段在未初始化的情况下被取消引用,则该错误不那么容易被利用,并且如果如果该字段可能指向有效但无意义的内存位置,则该字段包含全零。

请注意,如果好的编译器检测到不再使用该值,它们将优化置零。您可能需要使用一些特定于编译器的技巧,以使编译器相信该值仍在使用,从而生成清零代码。

在许多具有自动管理功能的语言中,对象可以在内存中移动不知不觉中。这意味着很难控制陈旧数据的泄漏,除非内存管理器本身会擦除未使用的内存(出于性能目的通常不会这样做)。从好的方面来说,外部泄漏通常是您所要担心的,因为高级语言往往会阻止未初始化内存的使用(请注意具有可变字符串的语言中的字符串创建函数)。

顺便说一句,我在上面写了“归零”。您可以使用除全零以外的位模式。全零的优点是在大多数环境中它都是无效的指针。您知道的位模式是无效的指针,但是更具特色的位模式可以帮助调试。

许多安全标准要求擦除敏感数据(例如密钥)。例如,用于加密模块的FIPS 140-2标准甚至在最低保证级别上也要求它,除此之外,它仅需要功能合规性,而无需抵抗攻击。

评论


通常,秘密不会直接作用。使用了一个加密库(因为实现一种算法,即使是一种好的算法也很困难,而且实现错误会使您的安全性失效)。那么,是否有人将自己限制为仅在删除数据之前才更改数据的库(可能会删除最适合该工作的库)?还是使用一种允许数据一直挂起直到删除的库,从而失去了将其应用于自己的代码的好处?

– Lawtonfogle
2014年12月5日14:00

@Lawtonfogle密码库通常以这样一种方式编写:在使用后至少擦除密钥。在加密实施者中,这被认为是良好的卫生习惯,而且我期望(尽管我没有关于该主题的可靠数据)与合理的质量度量相关。即使该库没有执行此操作,您通常也可以从应用程序控制该库的内存管理。

–吉尔斯'所以-不再是邪恶的'
2014年12月5日14:03

我不得不承认,编写加密库的安全专家似乎也将纳入其他安全实践,即使它们与加密本身并不直接相关。但是您希望确保它是开源的(如果仅出于验证其加密完整性的其他原因)。

– corsiKa
2014年12月7日22:41

“如果您担心对RAM的物理攻击,那么更大的担忧将是确保将数据写在RAM中,而不仅仅是在CPU缓存中。” -您可以强制执行内存屏障(最低级别作为CPU指令实现),以强制将CPU缓存刷新到RAM。如果编程语言是高级语言,一种实现方法是采用一些内存共享原语,例如互斥体(锁定,擦除,解锁),该互斥体使用内存屏障作为实现的一部分。

– FooF
2014-12-19 8:26



#5 楼

除了剩下的答案(希望很有趣),许多人低估了用C正确覆盖内存的难度。我将在Colin Percival的博客文章“如何将缓冲区归零”中大量引用。 >
天真的尝试覆盖C语言中的内存所面临的主要问题是编译器优化。大多数现代编译器都非常“聪明”,足以认识到用于覆盖内存的通用方法实际上并不会改变程序的可观察行为,因此可以对其进行优化。不幸的是,这完全破坏了我们想要实现的目标。上面链接的博客文章中介绍了一些常见的技巧。更糟糕的是,适用于一个版本的编译器的技巧可能不一定与另一版本的编译器甚至同一版本的其他版本一起使用。除非您仅分发二进制文件,否则这将是一个有问题的情况。

我知道,可靠地覆盖C语言中的内存的唯一方法是memset_s函数。可悲的是,这仅在C11中可用,因此为较早版本的C编写的程序不走运。


memset_s函数将c的值(转换为无符号字符)复制到每个s指向的对象的
前n个字符中的第一个。与memset不同,对memset_s的任何调用都必须严格按照抽象机的规则进行评估,如5.1.2.3中所述。也就是说,对memset_s的任何
调用均应假定s和n表示的内存在将来可能会被访问​​,因此必须包含c。


很遗憾,Colin Percival认为重写内存是不够的。他在标题为“零缓冲不足”的后续博客中指出,


稍加注意,使用协作编译器,我们可以将缓冲区归零—但这不是我们所需要的。我们需要做的是将可能存储敏感数据的每个位置都设为零。请记住,我们之所以首先将敏感信息存储在内存中,是因为我们可以使用它。而且这种用法几乎肯定会导致敏感数据被复制到堆栈和寄存器中。


他继续介绍了使用x86平台上的AESNI指令集泄漏数据的AES实现示例。

他声称,


无法安全地在C中实现任何提供前向保密性的密码系统。


确实是令人不安的声明。

评论


感谢您的见解!如果C和C ++编译器包含一个超出范围的内存清零选项(或者一种告诉编译器要安全删除哪些变量的方法),那绝对好。

–乔纳森
2014年12月16日15:06

#6 楼

重要的是在需要后立即覆盖敏感数据,因为否则,否则:


数据会一直保留在堆栈上直到被覆盖,并且可能会因其他过程的帧溢出而可见。
数据容易受到内存刮擦的影响。

实际上,如果查看安全敏感型应用程序(例如openssh)的源代码,您会发现它会在仔细检查后将敏感数据清零。用途。

编译器可能会尝试优化覆盖,即使没有这样做,也很重要,要知道数据是如何物理存储的(例如,如果密钥存储在SSD上) ,覆盖它可能不会由于损耗平衡而擦除旧内容。)

#7 楼

原始示例显示了一个堆栈变量,因为它是本机int类型。

覆盖它是一个好主意,否则它会在堆栈上徘徊直到被其他东西覆盖。

我怀疑如果您是在C ++中通过指针和malloc分配了堆对象或C本机类型,那么使用是一个好主意
br />使用编译指示符使用该变量将代码括起来并禁用优化。
如果可能的话,只能将秘密汇编成中间值而不是任何命名变量,因此它仅在计算期间存在。 >在JVM或C#下,我认为所有赌注都没有了。

评论


“如果可能的话,只在中间值而不是任何命名变量中组合秘密”,这没有多大意义,因为大多数发行版编译的二进制文件都不包含命名信息。如果我反汇编一个C ++编译的应用程序,我将能够说出它执行什么指令,但是几乎可以肯定,我不能说出任何变量名。

–用户
2014年12月8日,12:26

我可能是错的,但是我在考虑中间值的存储位置,而不是分配给变量的任何单个值。如果在代码中放置一个常量并将其分配给变量,则该常量有时会链接到静态块中的值。中间值存在于寄存器中或内部在芯片高速缓存中。如果您正在计算某种键作为中间值,然后将该结果与服务中的值进行比较,则整个过程将更加短暂。

–安迪·邓特(Andy Dent)
2014年12月8日13:55

#8 楼

内存块有可能在您去掉并删除之前被调出页面,具体取决于页面文件中的使用情况分布如何播放数据,数据可能永远存在在那里。

因此,要成功从计算机中删除机密,必须首先通过固定页面来确保数据永远不会到达持久性内存。然后确保编译器没有优化对要删除的内存的写操作。

#9 楼

实际上答案是肯定的,您可能应该在删除它之前将其覆盖。如果您是网络应用程序开发人员,那么您可能应该在使用deletefree函数之前覆盖所有数据。

如何利用此bug的示例:

用户可以在输入字段中插入一些恶意数据。您继续处理数据,然后调用分配的内存的free函数而不会覆盖它,因此数据将保留在内存中。然后,用户使您的Web应用程序崩溃(例如,通过上传非常大的图片或类似的东西),UNIX系统将把核心内存转储到corecore.<pid>文件中。然后,如果用户可以对<pid>进行暴力破解,这不会花费太长时间,并且核心转储文件将被解释为Web Shell,因为它包含用户的恶意数据。

#10 楼

int secret = 13123在本地范围内不是一个常量吗?

您不应太在意所写的东西,而该书几乎值得一读。实际上,在一些相反的建议下,我建议您故意使其可读。而且,用时间相关的字符串随机填充它,这些字符串不是以标准方式调用的。

这样,如果您查看黑暗的网站以发现自己是否受到攻击,则可以准确地知道它是如何完成的。由于数据库转储与刮板转储是分开的。