| 复审它:在你的代码中寻找安全缺陷的专家技巧 
 
   我的大部分工作涉及到复审别人的代码,寻找安全方面的错误。诚然,这并不是我的首要任务——往往是设计复审和威胁模型分析[编者:threat modeling 即威胁模型分析]—但是我确实要看到大量的代码。 
 我有一套等级评定体系,我用它来确定复审代码所需的相对时间。这个体系基于如果弱点被利用后存在的潜在危害以及可能受到的潜在攻击。 具体针对的范围基于以下几个特点: 
   如果该列表中同时有三或四个以上项目被言中,这时我将在更深的层次上复审代码。事实上,如果代码是侦听传输控制协议(TCP)或用户数据报协议(UDP)的 socket 并以缺省值运行,那么就要准备大量时间来复审该代码。 
 void fuction(char *p) {        char buff[16];        …        strcpy(buff,p);        …        }如果我看到像这样的代码,我将跟踪变量 p 到它的源头,并且如果它来源于某个我并不信任的地方,或在接近它被拷贝的地方没有进行合法性检查,这时我知道已经找到了一个安全缺陷。值得注意的是,并不是说 strcpy 本身是危险的或者说是不安全的。应该说,恰恰是这个数据使得这类函数惊慌失措。如果你检查的数据具有良好的格式,那么 strcpy 可能就是安全的。当然,如果你犯错,那么你的代码就有一个安全错误。我也检查“n”版本字符串处理函数,比如 strncpy,因为你也要检查那些缓冲区大小的计算是正确的。我谨慎对待那些处理标注文件格式的代码。通过标注那些由块组成的文件,这里每个块都有一个头描述下一个数据块。MIDI音乐格式就是一个很好的例子。一个严重的安全缺陷被发现后,在一个处理 MIDI 文件的名为 quartz.dll 的 Windows 组件中被修复。有个畸形的 MIDI 结构导致了处理文件的代码失败,或更糟。你可以在 Unchecked Buffer in DirectX Could Enable System Compromise 得到更多关于这个缺陷的内容。 另一个我留心的结构是: while (*s != ''\\'') *d++ = *s++;这个循环囿与源中的某个字符;它不受目的地大小的限制。我主要用下面的正则表达式扫描 *x++ = *y++。 \*\w+\+\+\s?=\s?\*\w+\+\+   当然,人们也可能用 *++x = *++y,因此你也需要对此进行扫描。我想在这里再次强调的是这个结构并不危险,除非数据源是不可信的,因此你需要确定数据源是否可信赖。 
 
         void func(char *b1, size_t c1, char *b2, size_t c2) {这段代码看起来挺好,但是,如果你将 c1 和 c2 相加并且结果超过 232-1。你便会认识到有问题,举个例子,0xFFFFFFF0 和 0x40 相加的结果是 0x30 (十进制数为 48)。当它们被用来做 c1 和 c2 的值时,这个加法通过了检查,此时这个代码将拷贝近 4GB 的内容到一个48字节的缓冲区。你正好遭遇到缓冲区超现!许多像这样的缺陷是可被人利用的,它允许攻击者将代码注入到你的进程中。当复审C 和 C++代码的整数溢出时,我查找所有 new 操作符以及动态内存分配函数(alloca, malloc, calloc, HeapAlloc等等)的实例,然后,我确定缓冲区大小是如何被计算的。接着我问自己如下几个问题: 这里有一个我在微软的同事常用的规则:如果你某个被用于比较的表达式中执行一个数学运算,此时你将有一个潜在的上溢和下溢数据。如果该计算被用于确定一个缓冲区 的大小,那么事情会变得加倍地糟糕,尤其是如果一个或更多的缓冲区大小的计算方法被攻击者利用时。 
   作为一般原则,数据库应用软件的开发者使用更高级的语言如C#,脚本语言和类似的语言。相对而言,很少有数据库代码是用 C 和 C++ 写的,但是一些人使用各种C/C++类库 ,如 MFC 中的CDatabase 类。 DRIVER={SQL Server};SERVER=hrserver;UID=sa;PWD=$esame实际上,这个例子中有两个缺陷。第一,该连接串由系统管理员帐户 sa 构成;它破坏了给予最小优先权原则。代码决不能用系统管理员帐 号连接到数据库,因为心怀不轨的人能用这样一个帐户对数据库造成严重破坏。第二,口令是硬编码。说它是错误有两个原因:首先,它能被发现; 其次,口令被更改了怎么办?(你将不得不更新所有客户端)下一个主题是SQL入侵攻击。SQL入侵的症结在于使用字符串连接生成 SQL 语句。当扫描代码时,我要看 SQL 语句在什么地方建立。一般来说,它涉及到寻找 下面 “update”,“select”,“insert”,“exec”以及我所知的任何表或数据库名称。为此,我使用 ildasm.exe 象下面这样反汇编托管程序集以便详细查看。 ildasm /adv /metadata /out:file test.exe然后我 在输出结果中查看“用户串”部分。如果我发现任何使用串连接的数据库查询,这是一个潜在的安全缺陷,并且必须用参数化查询赖取代它以修复该缺陷。 用字符串连接建立存储过程对SQL入侵也于事无补。总之,字符串连接加SQL语句是很槽的,但是字符串连接加SQL语句加系统管理员帐户加简直就是灾难。 
 
 在基于 WEB 的应用程序中最为普通的错误是跨站点脚本(XSS)问题。虽然还有我要找的其它问题,比如 SQL 入侵和弱的加密系统,XSS 缺陷相当普遍。XSS 的核心弱点是可能将不可 信的用户输入显示在受害者的浏览器上,因此我首先要搜索任何发送数据到用户代码构成。举个例子,我在ASP中寻找 Response.Write 和 <%= %> 标签。然后,我查看所写的数据来源于什么地方。如果数据来源于一个HTTP实体,比如一个表单或一个查询串,并且没有经过合法性检查就被送到用户的浏览器上。那么便存在一个 XSS 缺陷 。这里有一个非常简单但是并不常见的 XSS 例子: Hello,正如你所看到的,“Name”参数并没有首先经过合法性检查以及格式是否良好便被送回给用户。 
 
   一些开发者有个癖好就是将密码数据存储在代码中,比如口令和密钥,并且创造他们自己的不可思议的加密算法。千万不要做这两件傻事! 
   我在复审一个新的 ActiveX® 控件时总在问一个问题:为什么不能用托管代码来写它们?我之所以问这个问题是因为托管代码允许局部信任情 形,但是 ActiveX 不会。 [HKEY_CLASSES_ROOT\CLSID\<GUID>\Implemented Categories\{7DD95801-9882-11CF-9FA9-00AA006C42C4}]前面我提及用 SendFile 方法访问和发送用户文件不是件好事。事实上,它是一个隐密的缺陷,即使我能存取 SendFile 方法并根据该方法返回 的出错代码确定某个文件存在于用户硬盘上。 
   这是我在复审代码时首先要考虑的重中之重。一些缺陷并不复杂,有人会说开发人员不应该犯这种错,但他们确实犯了。然而意识到为了安全而复审代码,通常会 促使你将编写更加安全的代码放在首位。  背景信息请看 
 | 
|  作者简介 Michael Howard 是高级程序管理员,是微软 Secure Windows Initiative 小组的创立人之一,Writing Secure Code (Microsoft Press, 2002) 的合著者之一。他致力于设计、构建、测试和配置有计划的抵抗攻击的应用软件,同时也要数以百万计的无技术背景的用户仍然能够使用它。 | 
 
                
            
         
                    
                     
                    
                 
                    
                 
 浙公网安备 33010602011771号
浙公网安备 33010602011771号