1个技巧教你如何应对缓冲区溢出和其他内存管理错误
内存管理充满危险,尤其是在C和C++中。实际上,与内存管理弱点相关的错误占CWE Top 25的很大一部分。前25名中的八个与缓冲区溢出、不良指针和内存管理直接相关。
最大的软件弱点是CWE-119,“内存缓冲区范围内的操作限制不当”。这些类型的错误在各种软件的安全性和安全性问题中占主导地位,包括汽车,医疗设备和航空电子设备中的安全关键型应用程序。
以下是与CWE Top 25中常见的漏洞枚举有关的内存错误:
尽管这些错误困扰了C,C++和其他语言数十年,但如今它们仍在以越来越多的数量出现。就质量、安全性和可靠性的后果而言,它们是危险的错误,并且它们的存在是导致安全漏洞的主要原因。
安全漏洞的主要原因
Microsoft发现,在过去的12年中,其产品中超过70%的安全漏洞是由于内存安全问题引起的。这些类型的错误是其应用程序的最大攻击面,黑客正在使用它。根据他们的研究,安全攻击的最根本原因是堆越界,释放后使用和未初始化的使用。正如他们指出的那样,漏洞类别已经存在了20年或更长时间,并且在今天仍然很流行。
谷歌以类似的方式发现,Chromium项目(Chrome浏览器的开源基础)中70%的安全漏洞是由于这些相同的内存管理问题引起的。它们的最根本原因还在于免检后使用,其次是其他不安全的内存管理。
考虑到这些实际发现的示例,软件团队必须认真对待这些类型的错误,这一点至关重要。幸运的是,有一些方法可以通过有效而有效的静态分析来预防和检测这些类型的问题。
内存管理错误如何变成安全漏洞
在大多数情况下,内存管理错误是由于不良的编程习惯所导致的,原因是在C/C++中使用了指针并直接访问了内存。在其他情况下,这与对数据的长度和内容做出错误的假设有关。
这些软件弱点最常被污染数据利用,这些数据来自应用程序外部,未经检查其长度或格式。臭名昭著的Heartbleed漏洞是利用缓冲区溢出的一种情况。从技术上讲,这是缓冲区溢出。正如我们在上一个博客中有关SQL注入的讨论中所讨论的那样,使用未经检查和不受约束的输入会带来安全风险。
让我们考虑一下内存管理软件弱点的一些主要类别。最重要的一个是CWE-119:在内存缓冲区范围内对操作的不当限制。
缓冲区溢出
允许直接访问内存并且不自动验证访问的位置是否有效且容易发生内存损坏错误的编程语言(通常为C和C++)。这种损坏可能发生在内存的数据和代码区域中,从而可能暴露敏感信息,导致意外的代码执行或导致应用程序崩溃。
以下示例显示了CWE-120缓冲区溢出的经典情况:
char last_name[20]; printf ("Enter your last name: "); scanf ("%s", last_name);
在这种情况下,对来自scanf()的用户输入没有任何限制,但last_name的长度限制为20个字符。输入超过20个字符的姓氏,最终会将用户输入复制到内存中,超出了缓冲区last_name的限制。这是CWE-119的一个更微妙的示例:
void host_lookup(char *user_supplied_addr){ struct hostent *hp; in_addr_t *addr; char hostname[64]; in_addr_t inet_addr(const char *cp); /*routine that ensures user_supplied_addr is in the right format for conversion */ validate_addr_form(user_supplied_addr); addr = inet_addr(user_supplied_addr); hp = gethostbyaddr( addr, sizeof(struct in_addr), AF_INET); strcpy(hostname, hp->h_name); }
此函数采用用户提供的包含IP地址(例如127.0.0.1)的字符串,并检索其主机名。
该函数验证用户输入(好!),但不检查gethostbyaddr()的输出(不好!)。 在这种情况下,较长的主机名足以溢出当前仅限于64个字符的主机名缓冲区。 请注意,如果在找不到主机名时gethostaddr()返回null,那么也会出现null指针取消引用错误!
释放后使用错误
有趣的是,Microsoft在他们的研究中发现,售后使用错误是他们面临的最常见的内存管理问题。顾名思义,该错误与访问先前释放的内存的指针(对于C/C++)有关。C和C++通常依靠开发人员来管理内存分配,这对于完全正确地进行操作通常很棘手。如以下示例(来自CWE-416)所示,通常很容易假设指针仍然有效:
char* ptr = (char*)malloc (SIZE); if (err) { abrt = 1; free(ptr); } ... if (abrt) { logError("operation aborted before commit", ptr); }
在上面的示例中,如果err为true,则指针ptr是空闲的,但是如果abrt为true(则设置为true,如果err为true),则在释放之后,指针ptr稍后将被取消引用。这似乎是人为的,但是如果这两个代码段之间有很多代码,则很容易忽略。此外,这可能仅在未经过正确测试的错误情况下发生。
空指针解除引用
软件的另一个常见弱点是使用预期有效但为NULL的指针(或C++和Java中的对象)。尽管这些取消引用在Java之类的语言中被视为异常,但它们可能导致应用程序停止、退出或崩溃。以Java为例,从CWE-476中获取以下示例:
String cmd = System.getProperty("cmd"); cmd = cmd.trim();
由于开发人员可能会假定getProperty()方法始终返回某些内容,因此这看起来很无害。实际上,如果属性“cmd”不存在,则返回NULL,在使用该属性时会导致NULL解除引用异常。尽管这听起来不错,但却可能导致灾难性的后果。
在极少数情况下,当NULL等于0x0内存地址并且特权代码可以访问它时,就可以写入或读取内存,这可能导致代码执行。
缓解措施
开发人员应实施几种缓解措施。首先,开发人员需要通过经过验证的逻辑和全面检查,确保指针对C和C++等语言有效。
对于所有语言,任何操作内存的代码或库都必须验证输入参数,以防止越界访问。以下是一些可用的缓解措施。但是开发人员不应依靠它们来弥补不良的编程习惯。
编程语言选择
某些语言提供了针对溢出的内置保护,例如Ada和C#。
安全库的使用
可以使用诸如Safe C字符串库之类的提供内置检查以防止内存错误的库。但是,并非所有缓冲区溢出都是字符串操作的结果。除此以外,程序员应始终使用以缓冲区长度作为参数的函数,例如strncpy()与strcpy()。
编译和运行时强化
这种方法利用了编译选项,这些选项将代码添加到应用程序中以监视指针的使用情况。此添加的代码可以防止在运行时发生溢出错误。
执行环境强化
操作系统具有防止在应用程序的数据区域中执行代码的选项,例如带有代码注入的堆栈溢出。还有一些选项可以随机排列内存映射,以防止黑客预测可利用代码的位置。
尽管有这些缓解措施,但还是没有替代适当的编码实践来避免缓冲区溢出的问题。因此,检测和预防对于降低这些软件漏洞的风险至关重要。
转移检测和消除缓冲区溢出
在软件开发中采用DevSecOps方法意味着将安全性集成到DevOps管道的各个方面。正如在SDLC中尽早推动代码分析和单元测试之类的质量过程一样,安全性也是如此。
如果开发团队更广泛地采用这种方法,则缓冲区溢出和其他内存管理错误可能已成为过去。正如Google和Microsoft的研究表明的那样,这些错误仍占其安全漏洞的70%。无论如何,让我们概述一种可以尽早阻止它们的方法。
与修补已发布的应用程序相比,查找和修复内存管理错误可节省大量时间。下面概述的检测和阻止方法基于将缓冲区溢出的缓解向左转移到开发的最早阶段。并通过静态代码分析进行检测来增强这一点。
侦测
检测内存管理错误依赖于静态分析以在源代码中找到这些类型的漏洞。检测发生在开发人员的桌面和构建系统中。它可以包括现有的、旧的和第三方代码。
连续检测安全问题可确保找到以下所有问题:
- 开发人员错过了IDE。
- 存在于比您的新的检测预防方法还早的代码中。
推荐的方法是信任但验证模型。安全性分析是在IDE级别上完成的,开发人员在该级别上根据所获得的报告做出实时决策。接下来,在构建级别进行验证。理想情况下,构建级别的目标不是找到漏洞。这是为了验证系统是否干净。
Parasoft C/C++test包括针对这些类型的内存管理错误(包括缓冲区溢出)的静态分析检查器。考虑以下示例,该示例取自C/C++测试。
图xx:Parasoft C/C++test示例,显示了对越界内存访问的检测。
放大细节,函数printMessage()错误会检测到错误:
Parasoft C/C++test还提供有关该工具如何出现此警告的跟踪信息:
边栏显示有关如何修复此漏洞的详细信息以及适当的参考:
准确的检测以及支持信息和补救建议对于使静态分析和对这些漏洞的早期检测非常有用,并使开发人员可立即采取行动至关重要。
防止缓冲区溢出和其他内存管理错误
防止缓冲区溢出的理想时间和地点是开发人员在其IDE中编写代码时。采纳安全编码标准的团队,例如C和C++的SEI CERT C和Java和.NET的OWASP Top 10或CWE的Top 25,都具有警告内存管理错误的准则。
例如,CERT C包括以下有关内存管理的建议:
这些建议包括预防性编码技术,这些技术可首先避免内存管理错误。每套建议都包括风险评估和修复成本,使软件团队可以按以下优先级排列准则:
一项关键的预防策略是采用符合SEI CERT等行业准则的编码标准,并在以后的编码中强制执行。通过更好的编码实践来预防这些漏洞更便宜、风险更低,并且投资回报率最高。
对新创建的代码进行静态分析既快速又简单。团队很容易将其集成到桌面IDE和CI/CD流程中。为了防止将此代码写入到内部版本中,最好在此阶段调查所有安全警告和不安全的编码做法。
Parasoft集成到Eclipse IDE中的屏幕截图,它将预防和检测漏洞带到开发人员的桌面。
检测不良编码实践的同等重要的部分是报告的实用性。了解静态分析违规的根本原因很重要,以便快速、有效地解决它们。这就是Parasoft的C/C++test,dotTEST和Jtest等商业工具的发源地。
Parasoft的自动测试工具可对警告进行完整跟踪,在IDE中进行说明,并连续收集构建信息和其他信息。这些收集的数据以及测试结果和指标可提供对团队编码标准合规性以及总体质量和安全状态的全面了解。
开发人员可以基于其他上下文信息(例如项目中的元数据、代码的年龄以及负责代码的开发人员或团队)进一步过滤发现。带有人工智能(AI)和机器学习(ML)的Parasoft之类的工具使用此信息来帮助进一步确定最关键的问题。
仪表板和报告包括风险模型,这些模型是OWASP、CERT和CWE提供的信息的一部分。这样,开发人员可以更好地了解该工具报告的潜在漏洞的影响以及应优先考虑哪些漏洞。在IDE级别生成的所有数据都与上面概述的下游活动相关。
总结
缓冲区溢出和其他内存管理错误继续困扰着应用程序。它们仍然是安全漏洞的主要原因。尽管知道它是如何工作和被利用的,但它仍然很普遍。有关最新示例,请参考。
我们提出了一种预防和检测方法来补充主动安全性测试,该方法可以防止缓冲区溢出在SDLC中尽早将其写入代码之前。防止在IDE上出现此类内存管理错误并在CI/CD管道中检测到这些错误,是将其从软件中路由出去的关键。
智能软件团队可以最大程度地减少内存管理错误。他们可以通过其现有工作流程中正确的流程、工具和自动化来对质量和安全性产生影响。