在代码中查找安全性缺陷的专家提示
作者: 来源: 添加时间:2006-5-23 8:33:21我的大部分工作就是检查别人的代码,寻找安全性错误。不可否认,这并不是我的首要任务(我的首要任务是设计检查和威胁建模),但是,我的确因此而接触到了大量的代码。
希望您明白,检查其他人的代码是一件好事的同时,却并不是创建安全软件的方式。通过设计、编写、测试以及编写有关安全系统的文档,然后安排时间进行安全性检查、培训和工具使用,才可以生产出安全的软件。仅仅依靠设计、编写、测试以及编写有关项目的文档,然后寻找安全性错误并不能创建安全的软件。代码检查只是该过程的一部分,其自身不能创建安全的代码。
在本文中,我不对代码安全漏洞(例如,整数溢出攻击、SQL 注入式攻击以及缓冲区溢出)加以论述;您可以通过阅读书籍(如我撰写的 Writing Secure Code,Microsoft Press®,2002)来了解这些问题。但是,我将从一个较高的角度来审视代码检查过程中要注意的问题。在正式开始之前,尽管我希望指出这就是我检查代码中安全性错误的方式,但是您没有必要以这种方式来检查代码,而且我也不能保证这就是检查某种缺陷的最完美形式。在查看代码时,我会记录下自己头脑中的想法,希望这些想法对您会有所帮助。
我认为有三种方法可用于检查代码:深入分析、快速分析以及混合方法。我倾向于将重点放在混合方法上,因为它的优势在于可快速覆盖大部分范畴;如果我认为某些地方需要进行更深入的分析,我会做个标记以便稍后对它进行代码检查,这可能会牵涉到相关领域其他的专家提示。但是现在,让我来介绍一下最初的快速代码检查,我喜欢称之为 Sweep“n”Tag 方法(扫描与标记方法),该方法快速扫描代码并标记要求进一步检查的代码。以下是我的这一过程的概要。
分配时间和精力
我使用一个优先级系统来确定检查代码需要花费的相关时间。该系统根据潜在的损坏程度建立,这种潜在的损坏程度体现为安全漏洞是否被利用并且是否存在可能的攻击。配额系统基于下列特征:
默认情况下代码是否运行?
是否利用提升的权限运行代码?
是否对网络接口进行代码侦听?
网络接口是否未经身份验证?
代码是否以 C/C++ 编写?
代码是否具有安全漏洞的历史?
安全性研究人员仔细是否审查过该组件?
代码是否处理敏感或者专有数据?
代码是否可重复使用(例如,DLL、C++ 类标头、库或者程序集)?
根据威胁模型,该组件是处于高风险环境中,还是易于遭受很多高风险威胁?
如果我从该列表得到多于三个或四个肯定的回答,那么我将对代码进行更进一步的检查。事实上,如果代码对传输控制协议 (TCP) 或用户数据报协议 (UDP) 套接字进行侦听,并且在默认情况下处于运行状态,那么请准备花费大量时间来检查代码。
在寻找安全性错误的过程中,我倾向于检查三种主要代码类别:C/C++ 代码、Web 服务器应用程序代码(例如,ASP、ASP.NET、CGI 和 Perl )以及托管代码(主要是C#,还有若干 Visual Basic®.NET)。
应该注意,每种语言之间都有一些细微的差异。首先,关于 C 和 C++ 的首要问题是缓冲区溢出。不可否认,还有其他问题存在,但是在同一句话中听到“缓冲区”和“溢出”这些单词时,您几乎就可以确认这是涉及到 C 或 C++ 了。高级语言(例如,C#、Visual Basic .NET 和 Perl)应该没有缓冲区溢出问题。如果有,错误可能存在于运行时环境,而不在检查的代码中。然而,这些语言通常会用于 Web 服务器应用程序代码中,而且会面临其他类型的缺陷。缓冲区溢出最令人讨厌,因为攻击者可以将代码注入到运行进程中并对它进行袭击。因此,让我们首先看看缓冲区溢出。
C 和 C++ 中的缓冲区溢出
缓冲区溢出是软件行业中的棘手问题,您应该尽最大的努力将它们从您的代码中根除。然而,最好是首先确保它们没有进入到代码中。
我有两种检查缓冲区溢出的方法。第一种,标识所有应用程序的入口点,尤其是网络入口点,同时跟踪在代码中移动的数据并审查数据的处理方式。我假定所有的数据格式都不正确。当查看涉及数据(读取数据或写入数据)的任何代码时,我会问“是否存在可导致该代码失败的数据版本呢?”这种方法检查得很彻底,但是很费时。另一种方法是寻找已知的和潜在危险的构造并跟踪数据回到入口点。例如,请看下面的代码:
void fuction(char *p) {
char buff[16];
•••
strcpy(buff,p);
•••
}
如果我看到这样的代码,我将跟踪变量 p 返回到它的源,如果它来自于我不信任的位置,或者它所复制的来源的有效性未经检查,那么我就知道我发现了一个安全性错误。请注意,strcpy 本身并不危险或不安全。而危险的是使函数变得如此可怕的数据。如果检查数据格式正确,则 strcpy 可以是安全的。当然,如果您判断出错,就会有安全性错误了。我也检查了“n”字符串操纵函数(例如 strncpy),因为您还需要检查缓冲区大小计算是否正确。
我对处理标记文件格式的代码比较提防。我指的标记文件由几个块区组成,每个块区都具有说明下一块区数据的标头。MIDI 音乐格式就是一个很好的例子。在名为 quartz.dll(处理 MIDI 文件)的 Windows 组件中发现并修改了一个严重的安全性错误。格式错误的 MIDI 构造将导致处理文件的代码失败,甚至更糟糕。您可以在 Unchecked Buffer in DirectX Could Enable System Compromise 中阅读有关该错误的详细信息。
我要注意的另一个构造如下所示:
while (*s != '\\')
*d++ = *s++;
这个循环受到源中字符的限制,但不受目标大小的限制。基本上,我使用下面的正则表达式来扫描 *x++ = *y++:
\*\w+\+\+\s?=\s?\*\w+\+\+
当然,也可以使用 *++x = *++y,因此您也需要对它进行扫描。我想再一次强调,该构造没有危险(除非源不受信任),因此您需要确定源数据的可信性。
接下来应该注意的是另一个与缓冲区溢出相关的问题:整数溢出漏洞。
C 和 C++ 中的整数溢出
如果您不知道整数溢出攻击是什么以及如何修复它们,那么您应该首先阅读我的文章“Development Impacts of Security Changes in Windows Server 2003”。当执行算法以计算缓冲区大小并且计算导致上溢或下溢时,真正的安全性漏洞会随同这些缺陷一起出现。请看下面的示例:
void func(char *b1, size_t c1, char *b2, size_t c2) {
const size_t MAX = 48;
if (c1 + c2 > MAX) return;
char *pBuff = new char[MAX];
memcpy(pBuff,b1,c1);
memcpy(pBuff+c1,b2,c2);
}
上面的代码看起来没有问题,但如果将 c1 和 c2 相加,结果超过 232-1,您就会意识到有问题了。例如,0xFFFFFFF0 和 0x40 相加的结果为 0x30(十进制为 48)。当这些值用于 c1 和 c2 时,加起来的和可以通过大小检查,然后代码会将大约 4GB 复制到 48 个字节的缓冲区。这样就会出现缓冲区溢出!类似于这样的许多错误都可以被利用,使攻击者将代码注入您的进程中。
当检查 C 和 C++ 代码的整数溢出问题时,我将查找运算符 new 和动态内存分配函数(alloca、malloc、calloc、HeapAlloc 等等)的所有实例,然后确定如何计算缓冲区大小。然后,我会询问自己下列问题:
这些值可以超过特定的最大值吗?
这些值可以小于零吗?
数据是否被截断(将 32 位值复制到 16 位值中,然后复制 32 位大小)?
第 1 页,共 2 页 [1] [2]
站内搜索