首页 > 安全资讯 >

检查代码是否存在整数操作安全漏洞

04-12-03

来自:微软中国   Michael HowardSecure Windows Initiative 摘要: Michael Howard 提出关于整数操作安全漏洞的问题,并且阐述可以用来保护自己应用程序的安全性计划。 很多年以前,很少有人听说过整数溢出攻击,但现在好像每

来自:微软中国

 


Michael Howard
Secure Windows Initiative

摘要: Michael Howard 提出关于整数操作安全漏洞的问题,并且阐述可以用来保护自己应用程序的安全性计划。

很多年以前,很少有人听说过整数溢出攻击,但现在好像每隔几天它就会出现一种新的形式。下面的简短列表就是在最近几个月内发现的一些整数溢出的安全性错误:

? Sun RPC xdr_array
 
? OpenSSH authentication
 
? Apache Chunked Encoding
 
? Microsoft JScript
 
? FreeBSD socket and system calls
 
? Snort TCP Packet Reassembly
 

在本月的专栏中,我将阐述这些错误是如何出现的,如何在代码中搜寻它们以及如何修复它们。

在进入到本文的正题之前,我非常高兴的宣布"Writing Secure Code"接收到了在 2003 年 4 月于 San Francisco 召开的 RSA Security Conference 的"Conference Award for Industry Innovation"。

现在,回到整数攻击问题吧!

我不想解释什么是整数,我假设您知道它们是什么,并且知道有两种类型(有符号和无符号),其中当值是负数时,有符号整数的高位设置为 1,这是对 2 求补算法的结果。您也知道整数大小各有不同,最常见的长度为 64 位、32 位、16 位和 8 位的整数。这就是我所说的关于整数的全部内容,对于本文而言,如果您知道这些知识就足够了。

有三种主要整数操作可以导致安全性漏洞:

? 上溢和下溢
 
? 有符号与无符号的错误
 
? 截断
 

取决于它们自己的情况,这些问题可能不会产生安全性错误。但是,如果您的代码显示有一个或多个这样的问题,并且您的代码会操作内存,那么产生缓冲区溢出错误或应用程序故障的可能性就会增加。让我们仔细查看一下每一项。

上溢和下溢
快速看一下这段代码有什么问题?

bool func(size_t cbSize) {
   if (cbSize < 1024) {
      // we never deal with a string trailing null
      char *buf = new char[cbSize-1];
      memset(buf,0,cbSize-1);

      // do stuff

      delete [] buf;

      return true;
   } else {
      return false;
   }
}
代码是正确的,对吗?它验证 cbSize 不大于 1 KB,并且 new 或 malloc 应该始终正确地分配 1 KB,对吗?让我们忽略以下事实,new 或 malloc 的返回值应该在此时进行检查。同样,cbSize 不能为负数,因为它是 size_t。但是,如果 cbSize 是零,又会如何呢?查看一下分配缓冲区的代码,它从缓冲区大小请求中减去一。从零减去一会产生 size_t 变量,这是一个无符号的整数,其限制为 0xFFFFFFFF(假设为 32 位的值)或者 4 GB。您的应用程序只有结束了,或者更糟!

请看下面相似的问题:

bool func(char *s1, size_t len1,
          char *s2, size_t len2) {
   if (1 + len1 + len2 > 64)
      return false;

   // accommodate for the trailing null in the addition
   char *buf = (char*)malloc(len1+len2+1);
   if (buf) {
      StringCchCopy(buf,len1+len2,s1);
      StringCchCat(buf,len1+len2,s2);
   }

   // do other stuff with buf

   if (buf) free(buf);

   return true;
}
同样,代码看起来编写得很好;它检查数据大小,验证 malloc 是否成功,并且使用 safe 字符串处理函数 StringCchCopy 和 StringCchCat(您可以从 http://msdn.microsoft.com/library/en-us/dnsecure/html/strsafe.asp 阅读更多关于这些字符串处理函数的内容)。但是,这段代码可能会受到整数上溢的危害。如果 len1 是 64,len2 是 0xFFFFFFFF,又会如何呢?确定缓冲区大小的代码合法地将 1、64 和 0xFFFFFFFF 加在一起,由于加操作的限制,会产生 64。接下来,代码仅分配了 64 个字节,然后代码生成了一个长度为 64 个字节的新字符串,然后将 0xFFFFFFFFF 字节与该字符串相连。同样,应用程序将会结束,在某些情况下,如果利用精心设计的大小进行攻击,代码可能会受到可用缓冲区溢出攻击。

此处的另外一个教训就是,如果缓冲区大小计算不正确,_safe 字符串处理函数就不安全。

返回页首
JScript 溢出攻击
当使用乘法时也会出现相同类型的上溢错误,这发生在 Microsoft JScript 错误中。该错误仅表明它自己在使用 JScript 稀疏数组支持时会出现:

var arr = new Array();
arr[1] = 1;
arr[2] = 2;
arr[0x40000001] = 3;
在这个示例中,数组具有三个元素,且长度为 0x40000001(十进制为 1073741825)。但是,由于该示例使用稀疏数组,它只占用内存的三个元素的数组。

实现 JScript 自定义排序例程的 C++ 代码在堆上分配临时的缓冲区、将三个元素复制到临时缓冲区中、使用自定义函数排序临时缓冲区,然后将临时缓冲区的内容移动回数组中。下面是分配临时缓冲区的代码:

TemporaryBuffer = (Element *)malloc(ElementCount * sizeof(Element));

Element 是一个 20 字节的数据结构,用于保存数组项。看起来程序将尝试为临时缓冲区分配大约 20 GB。您可能认为由于大多数人的计算机上不会有 20 GB 的内存,分配尝试将会失败。那么,JScript 常规内存不足处理例程将会处理该问题。遗憾的是,并没有发生这样的情况。

当使用 32 位的整数算法时,由于结果 (0x0000000500000014) 太大无法保存在 32 位的值中,我们会受到一个整数上溢攻击:

0x40000001  *  0x00000014  =   0x0000000500000014
C++ 会丢弃所有不符合的位,因此我们会得到 0x00000014。这就是分配并未失败的原因 - 分配没有尝试去分配 20 GB,而是仅仅尝试分配了 20 个字节。然后,排序例程会假设缓冲区对于保存稀疏数组中的三个元素来说足够大,因此它将组成这三个元素的 60 个字节复制到了 20 个字节的缓冲区中,这样就溢出缓冲区 40 个字节。实在太冒险了!

返回页首
有符号与无符号错误
快速查看下面的代码。它类似于第一个示例。看看您是否能够发现错误,如果您发现了错误,请试着确定该错误会产生什么结果。

bool func(char *s1, int len1,
          char *s2, int len2) {

   char buf[128];

   if (1 + len1 + len2 > 128)
      return false;

   if (buf) {
      strncpy(buf,s1,len1);
      strncat(buf,s2,len2);
   }

   return true;
}
此处的问题在于字符串的大小存储为有符号的整数,因此只要 len2 是负值,len1 就可以大于 128,从而和就小于 128 个字节。但是,到 strncpy 的调用将会溢出 buf 缓冲区。

返回页首
截断错误
让我们查看最后一种攻击类型,通过代码示例,您来猜猜看。

bool func(byte *name, DWORD cbBuf) {
   unsigned short cbCalculatedBufSize = cbBuf;
   byte *buf = (byte*)malloc(cbCalculatedBufSize);
   if (buf) {
      memcpy(buf, name, cbBuf);
      // do stuff with buf
      if (buf) free(buf);
      return true;
   }

   return false;
}
这种攻击,至少这种结果,与前面阐述的 JScript 错误有一点类似。如果 cbBuf 是 0x00010020,又会如何呢?cbCalculatedBufSize 只有 0x20,因为只从 0x00010020 复制了低 16 位。因此,仅分配了 0x20 个字节,并且 0x00010020 字节复制到新分配的目标缓冲区中。请注意,使用 Microsoft Visual C++?/W4 选项编译这段代码会生成:

warning C4244: initializing : conversion from DWORD to unsigned
short, possible loss of data
请注意类似下面的操作不要标记为一个警告:

int len = 16;
memcpy(buf, szData, len);
memcpy 的最后一个参数是 size_t,而参数 len 是有符号的。不会发出警告是因为 memcpy 始终假设第三个参数是无符号的,转换成无符号不会改变函数的输出。

注意,如果您尝试为 DWORD 分配一个 size_t,您将会收到一个警告,并不是因为在 32 位平台上可能会出现数据丢失,而是因为在 64 位平台上将会出现数据丢失。

warning C4267: = : conversion from size_t to DWORD, possible loss
of data
您将收到这个警告,因为所有默认 C++ 项目都使用 -Wp64 选项进行编译,该选项会通知编译器监视 64 位可移植性问题。

返回页首
托管代码中的整数操作问题
整数操作错误可能会发生在托管语言中,例如 C# 和 Visual Basic?.NET,

相关文章
最新文章
热点推荐