美国情报机构国家安全局(NSA),于11月10日发布了指南,以帮助软件开发人员和运营商预防和缓解软件内存安全问题,这些问题占可利用漏洞的很大一部分。
“软件内存安全”网络安全信息表强调了恶意网络行为者如何利用糟糕的内存管理问题来访问敏感信息、发布未经授权的代码执行以及造成其他负面影响。
网络安全技术总监NealZiring说:“内存管理问题已经被利用了几十年,今天仍然非常普遍,在开发软件以消除恶意网络参与者的这些弱点时,我们必须始终使用内存安全语言和其他保护措施。”
微软和谷歌都表示,软件内存安全问题是其漏洞的大约70%。糟糕的内存管理也会导致技术问题,例如不正确的程序结果、程序性能随着时间的推移而下降以及程序崩溃。
NSA建议组织尽可能使用内存安全语言,并通过编译器选项、工具选项和操作系统配置等代码强化防御来加强保护。
正文
摘要
现代社会严重依赖基于软件的自动化,隐含地信任开发人员编写以预期方式运行且不会因恶意目的而受到损害的软件。虽然开发人员经常执行严格的测试,以准备软件中的逻辑以应对意外情况,但可利用的软件漏洞仍然经常基于内存问题。示例包括内存缓冲区溢出以及利用软件如何分配和取消分配内存的问题。微软在年的一次会议上透露,从年到年,他们70%的漏洞是由于内存安全问题造成的。谷歌还在Chrome中发现了类似比例的内存安全漏洞。恶意网络行为者可以利用这些漏洞进行远程代码执行或其他不利影响,这通常会危及设备,并成为大规模网络入侵的第一步。
常用语言(如C和C++)在内存管理方面提供了很大的自由度和灵活性,同时严重依赖程序员对内存引用执行所需的检查。简单的错误可能导致可利用的基于内存的漏洞。软件分析工具可以检测内存管理问题的许多实例,操作环境选项也可以提供一些保护,但内存安全软件语言提供的固有保护可以防止或缓解大多数内存管理问题。NSA建议尽可能使用内存安全语言。虽然对非内存安全语言使用附加保护和使用内存安全语言并不能提供针对可利用内存问题的绝对保护,但它们确实提供了相当大的保护。因此,私营部门、学术界和美国政府的总体软件社区已经开始采取措施,推动软件开发文化利用内存安全语言。
内存安全问题
软件程序如何管理内存是防止许多漏洞和确保程序健壮的核心。利用糟糕或粗心的内存管理可以让恶意网络参与者执行恶意行为,例如随意使程序崩溃或更改执行程序的指令以执行参与者想要的任何事情。即使是不可利用的内存管理问题也可能导致不正确的程序结果、程序性能随时间推移而下降或看似随机的程序崩溃。
内存安全是与程序如何管理内存相关的一类广泛的问题。一个常见的问题称为“缓冲区溢出”,其中数据是在数组边界之外访问的。其他常见问题与内存分配有关。语言可以在程序执行时分配新的内存位置,然后在不再需要内存时释放内存,也称为释放或释放内存。但是,如果开发人员没有仔细完成此操作,则在程序执行时可能会一次又一次地分配新内存。因此,当不再需要内存时,内存并不总是被释放,从而导致内存泄漏,最终可能导致程序耗尽可用内存。由于逻辑错误,程序还可以尝试使用已释放的内存,甚至已释放的内存。当语言允许使用尚未初始化的变量时,可能会出现另一个问题,从而导致变量使用以前在内存中该位置设置的值。最后,另一个具有挑战性的问题称为竞争条件。当程序的结果取决于访问相同数据的程序的两个部分的操作顺序时,可能会出现此问题。所有这些内存问题都非常常见。
通过利用这些类型的内存问题,不受软件使用正常期望约束的恶意行为者可能会发现他们可以在程序中输入异常输入,从而导致以意外方式访问、写入、分配或释放内存。在某些情况下,恶意行为者可以利用这些内存管理错误来访问敏感信息、执行未经授权的代码或造成其他负面影响。由于可能需要对异常输入进行大量实验才能找到导致意外响应的输入,因此参与者可能会使用一种称为“模糊测试”的技术来随机或智能地为程序制作大量输入值,直到找到导致程序崩溃的输入值。近年来,模糊测试工具和技术的进步使恶意行为者更容易找到有问题的输入。一旦参与者发现他们可以使用特定输入使程序崩溃,他们就会检查代码并确定特制内容输入可以做什么。在最坏的情况下,这样的输入可能允许参与者控制运行程序的系统。
内存安全语言
使用内存安全语言有助于防止程序员引入某些类型的内存相关问题。内存作为计算机语言的一部分自动管理;它不依赖于程序员添加代码来实现内存保护。该语言使用编译时和运行时检查的组合来建立自动保护。这些固有的语言功能可防止程序员无意中引入内存管理错误。内存安全语言的示例包括C#,Go,Java,Ruby,Rust和Swift。
即使使用内存安全语言,内存管理也不是完全内存安全的。大多数内存安全语言认识到软件有时需要执行不安全的内存管理功能来完成某些任务。因此,可以使用被识别为非内存安全的类或函数,并允许程序员执行潜在的不安全内存管理任务。某些语言要求显式注释任何不安全的内存,以使程序员和程序的任何审阅者意识到它是不安全的。内存安全语言还可以使用以非内存安全语言编写的库,因此可以包含不安全的内存功能。尽管这些包含内存不安全机制的方法破坏了固有的内存安全性,但它们有助于定位可能存在内存问题的位置,从而允许对这些代码部分进行额外的审查。
语言通过固有的保护和缓解措施建立的内存安全程度各不相同。某些语言仅提供相对最低的内存安全性,而某些语言非常严格,并通过控制内存的分配、访问和管理方式提供相当大的保护。对于具有极端固有保护级别的语言,由于检查和保护,可能需要大量工作才能简单地编译程序。
内存安全在性能和灵活性方面可能代价高昂。大多数内存安全语言都需要某种垃圾回收来回收已分配但程序不再需要的内存。检查可能位于阵列外部的每个阵列访问的边界也会产生相当大的性能开销。
或者,由于程序员向程序添加检查以执行边界检查和其他内存管理保护,因此在非内存安全语言中可能存在类似的性能影响。使用非内存安全语言的额外成本包括难以诊断的内存损坏和偶尔的程序崩溃以及利用内存访问漏洞的可能性
将成熟的软件开发基础设施从一种计算机语言转移到另一种计算机语言并非易事。熟练的程序员需要接受新语言的培训,并且在使用新语言时效率会受到影响。程序员必须忍受学习曲线,并克服任何“新手”错误。虽然另一种方法是雇用精通内存安全语言的程序员,但他们也将有自己的学习曲线来理解现有的代码库和软件将在其中运行的领域。
应用程序安全测试
可以使用多种机制来强化非内存安全语言,使其对内存更加安全。使用静态和动态应用程序安全测试(SAST和DAST)分析软件可以识别软件中的内存使用问题。
静态分析检查源代码以查找潜在的安全问题。使用SAST可以检查所有代码,但它可能会通过错误地识别潜在问题来生成大量误报。但是,SAST可以在整个软件开发过程中使用,从而可以在软件开发过程的早期识别和修复问题。严格的测试表明,即使是性能最好的SAST工具也只能识别最简单的软件程序中的一部分内存问题,并且通常会产生许多误报。
与SAST相比,动态分析在执行代码时检查代码。DAST需要一个正在运行的应用程序。这意味着大多数问题要到开发周期的后期才能识别出来,这使得识别出的问题修复和回归测试的成本更高。DAST只能在工具运行时识别执行路径上的代码问题,因此代码覆盖率也是一个问题。但是,DAST的误报率比SAST低得多。诸如内存泄漏之类的问题可以通过DAST识别,但内存问题的根本原因可能很难在软件中识别。
SAST和DAST都不能使非内存安全代码完全内存安全。由于所有工具都有其优点和缺点,因此建议运行多个SAST和DAST工具,以增加识别内存或其他问题的机会。解决工具标识的问题可能需要大量工作,但会生成更可靠、更安全的代码。漏洞关联工具可以从多个工具中提取结果,并将其集成到单个报告中,以简化分析并帮助确定分析的优先级。
反漏洞利用功能
编译和执行环境可用于使网络参与者更难利用内存管理问题。这些添加的功能大多侧重于限制代码在内存中执行的位置,并使内存布局不可预测。因此,这减少了恶意行为者使用将数据作为代码执行并覆盖返回地址以将程序流定向到恶意位置的利用交易的机会。
利用控制流防护(CFG)等选项将对代码的执行位置施加限制。同样,地址空间布局随机化(ASLR)和数据执行保护(DEP)增加了项在内存中位置的不可预测性,并阻止数据作为代码执行。绕过ASLR和DEP对于恶意行为者来说并非不可克服,但它使开发漏洞变得更加困难,并降低了漏洞利用成功的几率。反利用功能有助于缓解内存安全和非内存安全语言中的漏洞。
未来发展
软件中的内存问题占现有可利用漏洞的很大一部分。NSA建议组织考虑在可能的情况下从提供很少或没有固有内存保护的编程语言(如C/C++)进行战略转变,以使用内存安全语言。内存安全语言的一些示例是C#,Go,Java,Ruby和Swift。内存安全语言提供不同程度的内存使用保护,因此也应使用可用的代码强化防御(如编译器选项、工具分析和操作系统配置)来保护它们。通过使用内存安全语言和可用的代码强化防御,可以防止、缓解许多内存漏洞,或者使网络参与者很难利用这些漏洞。
最常见的20种网络安全攻击类型
卡内基梅隆大学:缓解内部威胁的常识指南
关基保护:关键信息基础设施安全保护要求的一点杂谈
了解英国供应链网络安全评估
良好数据安全实践推动数据治理的7种方式