您好,欢迎来到爱问旅游网。
搜索
您的当前位置:首页代码安全研究

代码安全研究

来源:爱问旅游网


目 录

摘要 ............................................................................................................................................ 1 关键词 ........................................................................................................................................ 1 Abstract ..................................................................................................................................... 1 Key words .................................................................................................................................. 1 引言 ............................................................................................................................................ 1 1代码安全存在的问题 ............................................................................................................. 2 1.1 重大代码安全问题案例 ..................................................................................................... 2 1.2 何谓代码安全 ..................................................................................................................... 3 1.3 存在代码安全问题的原因 ................................................................................................. 3 1.4 不安全代码造成的后果 ..................................................................................................... 5 2 软件的安全漏洞 .................................................................................................................... 5 2.1 缓冲区溢出 ......................................................................................................................... 6 2.2 SQL注入 ........................................................................................................................... 9 2.3 跨站脚本(XSS)漏洞 ......................................................................................................... 9 2.4 文件访问漏洞 ................................................................................................................... 10 3 代码安全的意义 .................................................................................................................. 11 3.1 不安全的代码容易受到攻击 ........................................................................................... 11 3.2 不安全的代码容易引发系统崩溃 ................................................................................... 11 3.3 修复安全漏洞的代价高昂 ............................................................................................... 11 4 如何编写安全代码 .............................................................................................................. 12 4.1 思想观念 ........................................................................................................................... 12 4.2 技术因素 ........................................................................................................................... 13 4.3 编程技巧 ........................................................................................................................... 15 5 结束语 .................................................................................................................................. 16 致谢 .......................................................................................................................................... 16 参考文献 .................................................................................................................................. 16

代码安全研究

网络工程专业学生 鲁凤伟

指导教师 吴俊华

摘要:深入研究代码安全问题。首先是对代码安全问题典型案例汇总、分类,并描述了安全代码的明确定义,其次说明不安全代码会严重影响人们的现代化生产、生活,并进一步详细描述了软件安全漏洞----缓冲区溢出、SQL注入、跨站脚本漏洞、文件访问漏洞。在此基础上分析了安全代码的重要意义,如容易受到攻击,引起系统漏洞,以及修复所需的高昂代价。进一步阐明为维护互联网的安全必须从思想和技术两个方面出发编写安全代码,最后对代码安全的应用做出展望并对全文进行总结。

关键词:代码安全 漏洞 互联网安全

A Survey of the Code Security

Student Majoring in Networking Engineering Lu Fengwei

Tutor Wu Junhua

Abstract: Code security has been researched. First of all, typical cases of code security are

concluded and the definition of security code is described. Secondly, it is generalized that the insecurity code would influence seriously people‟s life and modern production. Next, the loopholes of software security (buffer overrun, SQL injection, cross site script, file access) are introduced in detail. Then, the significances of code security such as liable to be attacked, causing system loopholes and expensive cost of repairing are analyzed. And then, it is indicated that security code should be compiled depending on both concept and technology in order to maintain the security of network. Finally, the application of security code in the future is imaged.

Key words: code security; loophole; network security

引言

当前,黑客的攻击活动日益猖狂、世界著名网站屡屡被黑客攻击,网络安全问题日趋严重,信息安全成了人们最关心的话题。如何保护自己不被网络上的其他非善意的用户攻击备受人们的关注。传统的避免网络攻击的方法不外乎提高自己的警惕、依靠杀毒软件和防火墙。但是时刻以高度警惕的心情享受互联网带给我们的便利是不现实的。杀毒软件、防火墙在攻击防范中也不是万能的:例如还未升级的杀毒软件无法发现新病毒、安全漏洞的发现速度越来越快、防火墙渗透率越来越高等。以前人们是从用户的角度来考虑安全问题—想法设法保护用户,但未站在开发者的角度来思考这一问题—要求软件开发团队写出安全的软件。“千里之堤,溃于蚁穴”,软件中一个极小的失误也将导致整个软件的崩溃。因此代码安全的时代即将到来:首先,时代在变,从“World Wide Web”(万维网)到“World Wild Web”(混乱无序的网)绝非文字游戏。在如今这个充满了敌意的网络环境里面,编写的代码必须经得起考验,再用旧的思维模式去思考新的问题是非常危险的。其次,安全的产品同时也是高质量的产品,安全是高质量产品的一个子集。再次,媒体和竞争对手都喜欢在安全问题上大做文章,这些都是能上头条新闻的信息。最后,修补安全漏洞的代价是十分高昂的,除了直接的人力和误工等损失之外,还包括改善公共关系和客户信任度的降低的损失,这称之为“商誉”上的损失[1]。

1

1代码安全存在的问题

1.1 重大代码安全问题案例

开发者在软件开发时喜欢加入一些自认为是有益于代码安全的技术,但其所加入的检测技术根本就没有任何安全性而言。这也就是开发团队认为安全的代码事实上漏洞百出的原因之一。

DWORD dwRet = IsAccessAllowed (…); if ( dwRet == ERROR_ACCESS_DENIED ) { //security check failed //inform user that success is denied }else { //security check ok //perform task. } 图1-1失效时进入安全模式

图1-1中的代码表面看上去很安全,但是当IsAccessAllowed失效时,情况就会很糟糕。例如:但这个函数被调用时,系统用完了内存或对象句柄,用户就可以执行有特权的任务,因为这个函数可能返回一个像ERROR_NOT_ENOUGH_MEMORY的错误。

1)AT&T电话网络瘫痪

1990年1月15日下午起的9个小时内,AT&T公司的电话网络一直处于瘫痪的状[2]

态。电话交换采用的是计算机系统,在专家的努力下找到了造成此次电话网络瘫痪的问题代码,见图1-2。

network code() { switch( line ) { case THING1: doit1(); break; case THING2: if( STUFF == x ) { do_first_stuff(); if( OTHER_STUFF == y ) break; do_later_stuff(); } initialize_modes_pointer(); //编码者的原意是跳到这里 break; default: processing(); } //但事实上跳到了这里,导致//use_modes_pointer未初始化 use_modes_pointer(); } 图1-2问题代码

2)Sun公司的20,000,000$

1999年的春天,在Sunsoft的操作系统开发小组中,有一个“一级优先”的Bug报告,这个报告是关于异步I/O库的问题。如果这个Bug不解决,将会使一桩价值2000万美元的硬件生意不能继续,因为客户需要使用这个库的功能。在专家的努力排查下,把问题锁定在下面这行代码:x == 2 上,代码的原意是:x = 2 但程序员在写这条语句

使不小心多打了一个‘=’。Sun公司差点就因为那个程序员的疏忽而损失2000万美元[3]。

2

1.2 何谓代码安全

在Internet出现之前,甚至在大型机、终端以及基于它们的应用程序出现以前,很少有人会考虑如何编写安全的代码。那个时代的应用程序写在纸带上,它们仅仅根据不同的输入按照指定的算法计算出某一项结果。它们不会长时间的运行,更不会向不同的用户提供服务。

但是,时代变了,如今越来越多的Service程序应运而生(不管是基于Windows还是Unix, Linux),它们总是日复一日地运行,处理源源不断的各种交易请求。因此,人们很自然的要求那些Service程序能够保证日趋复杂的交易的顺利进行。这显然要更稳 定,处理能力更大,面对攻击更加坚强,还要有更好的扩展性,以满足“7天*24小时”的要求。代码安全就是保证这些要求的必要条件之一。代码安全不是能够被隔离在一个特定代码区域中的东西。就如同性能、可伸缩性、可操纵性和代码的可读性一样,安全意识是每一个软件设计人员、开发人员和测试人员必须知道的规程。代码安全也是一种应该普遍拥有的意识。如同大厦需要安全稳固一样,软件也需要安全稳固,否则随着工程的增大危险也就越发逼近了[4]。 1.3 存在代码安全问题的原因

代码存在不安全因素的原因众多,但是归结起来主要包括两个方面:意识因素和物质因素。这里的意识与物质不同于哲学的意识与物质,但是其关系极为相似。意识因素促使程序员在编写底层代码的时候将其错误意识应用于实践,造成代码的安全缺陷;反之亦然。

1)意识因素

科学研究要求研究者坚持唯物主义,但却不能忽视精神因素对物质的反作用。软件开发者关于代码安全的错误意识将指导其编码,从而造成无法估量的后果。常见的认识错误有以下几种:

(1)何谓高质量软件

在程序员的意识形态中可靠软件的概念和高质量软件的概念是等价的。毕竟软件工程的思想认为:没有完全正确的软件,只有能可靠运行的软件。然而高质量软件与可靠软件的确是不同的,其关系见图1-3。

的安全代码 码 可靠代码 高质量软件

图1-3高质量软件和安全软件的关系

(2)迷信防火墙

防火墙可以在很大程度上保护用户的计算机免受攻击,但认为配置了防火墙之后,网络就安全了的想法是不对的。防火墙的局限性表现在以下几个方面:

①防火墙不能防范内部人员的攻击

3

防火墙只能提供周边保护,并不能控制内部用户对内部网络滥用授权的访问。内部用户可以窃取数据、破坏硬件和软件,并可巧妙地修改程序而不接近防火墙。统计表明,由内部引起的网络安全问题约占总数的80%。

②防火墙不能防范绕过它的连接

防火墙可有效地检查由它进行传输的信息,但不能阻止绕过它传输的信息。比如,如果站点允许对防火墙后面的内部系统进行拨号访问,那么防火墙就没有办法阻止攻击者进行的拨号入侵。

③防火墙不能防御全部威胁

防火墙可以防御已知的威胁。设计优秀的防火墙理论上应该可以防御新的威胁,但是目前没有任何一款防火墙有能力防御所有的威胁。

④防火墙不能防御恶意程序和病毒

虽然大多数防火墙能扫描所有通过的信息,以决定是否允许他们通过防火墙进入内部网络,但扫描是针对源、目的地址和端口号的,而不是扫描数据的确切内容。因为在网络上传输二进制文件的编码方式太多,并且有太多的不同结构的病毒,因此防火墙不可能查找所有的病毒,也就不能有效地防范像病毒这类程序的入侵。

如今恶意程序发展迅速,病毒可以依附于共享文档传播,也可通过E-Mail附件的形式在Internet上迅速蔓延。Web本身就是一个病毒源,许多站点都可以下载病毒程序甚至源码。某些防火墙可以根据已知病毒和木马的特征码检查数据流,虽然这样做会有些帮助但并不可靠,因为类似的恶意程序的种类很多,有多种手段可使它们在数据中隐藏,防火墙对那些新的病毒和木马程序是为力的。此外,防火墙只能发现从其它网络来的恶意程序,但许多病毒却是通过被感染的移动存储设备或系统直接进入网络的。所以,防火墙并不是万能的。

(3)否认安全的必要性

一个现实的问题是众多的软件开发者在开发过程中没有把安全问题作为软件创收的一项功能。开发商甚至认为安全性应该被看作是低劣产品的一项功能,完美的产品是不需要安全性能的、代码的安全性并不是软件开发者必备的重要技巧之一。正是基于此,开发商不愿意花钱培训程序员以使之能够尽可能的编写安全的代码,也不愿意在安全技术上投资。在攻击发生以后才认识到安全的重要性,才给产品打补丁确保产品的安全。古人云:“亡羊补牢,为时未晚”,但是对于软件开发这种特殊行业来说,的确是晚了一步。对一个失去生命的生物说对不起没有丝毫作用[5]。

2)物质因素

与万物归于物质类似,任何代码安全问题归根到底可以看作是技术问题。 (1)对软件的运行环境一知半解 不了解软件运行的环境,对其依赖的操作系统一知半解甚至对编写软件所用的语言不甚了解。例如:递归是每个程序员必须掌握的思想,但是对于何时才能应用递归思想却不是每个程序员都知道的。递归只能应用于以下两种情况:

(2)用其它的方法难以描述待解决的问题 (3)运算结果与所求结果存在逆序关系

正是用递归思想来求解斐波那契数列的算法把程序员引向了错误的深渊。关于递归的第二种用法见图1-4。

4

//问题描述:把1234转换成‘1’,‘2’,‘3’,‘4’并输出; //方案:用取余法得到每一位数+‘0’入栈出栈。 void int_to_ascii ( int i ) { int temp = i; if ( temp != 0 ) int_to_ascii ( temp / 10 ); printf ( “%c ”, temp % 10 + „0‟ ); } 图 1-4递归的用法

(4)乱用随机数技术

有些软件需要输入一定的随机数才能使用。但这就一定能保证这个随机数生成函数产生的是随机数吗?随机数的产生一般利用库函数:rand() ,rand()的源代码(\\安装目录\\Microsoft Visual Studio\\VC98\\CRT\\SRC)见图 1-5。由源代码可以看出,rand()生成的随机数是有规律的,完全可以根据上次生成的随机数,推算出下一次的随机数。

int __cdecl rand(void) { #ifdef _MT _ptiddata ptd = _getptd(); return( ((ptd->_holdrand = ptd->_holdrand * 214013L + 2531011L) >> 16) & 0x7fff ); #else return(((holdrand = holdrand * 214013L + 2531011L) >> 16) & 0x7fff); #endif } 图 1-5 rand()源码

(5)乱用密码技术

大部分开发者认为采用了密码技术的软件产品一定是安全的,所以在任何可能用到密码技术的地方都用上密码技术,但是密码技术不是万能的。 1.4 不安全代码造成的后果

一旦代码出现安全问题,软件就会有漏洞。漏洞(Bug):顾名思义就是软件中有缺陷、有问题的地方。漏洞的来源有:系统设计上的疏忽;应用软件设计上的疏忽;程序设计语言的缺陷;软件底层代码存在不安全因素。存在漏洞的软件不但容易成为黑客攻击的目标,还可能在运行时崩溃。能够利用漏洞进行攻击的黑客往往也是编程高手,他们能够利用仅有的提示信息发现系统或软件中有问题的代码进而进行攻击。目前熟知的攻击、入侵方式大多都是基于漏洞的,并且造成后果比较严重、颇具影响力的网络攻击也是基于漏洞的。现在的软件好比是一座在某些不为人知的角落里设有开口的建筑,高明的人可以利用某些手段找到这些开口,从而可以不需要出入证就可以进出。为了避免这种情况,软件开发商为其产品不断的打补丁但却从来没有把所有的开口关闭。更糟糕的情况是:为一个开口打的补丁却打开了新的开口(回归性漏洞)。软件有漏洞才会被黑客利用从而进行攻击行为,根据逆向思维的提示:攻击者无法攻击一个没有找出漏洞的软件。“苍蝇不叮无缝的鸡蛋”说的就是这个道理。虽然做到这一点很难,但与其每天花时间打补丁,倒不如在软件开发之前就注意代码的安全性问题。安全的代码应该是高质量代码的前提条件,而不是其特性。目前,软件漏洞的存在形式主要有:缓冲区溢出、内存泄露、数组下标越界等[6]。

2 软件的安全漏洞

要想提高代码的安全性,必须知道造成软件存在漏洞的代码有哪些,因此熟知软件漏洞的分类有易于在编写代码时避免犯相同的错误。

5

2.1 缓冲区溢出

1)漏洞概述

缓冲区溢出是黑客进行攻击以及破解软件的惯用手法。由于缓冲区是程序在内存中动态分配的,为了不占用太多的内存,一个有动态分配变量的程序在运行时才给变量分配内存空间。当程序试图把数据放到内存中的某一位置但没有足够的空间时,就会发生缓冲区溢出。 缓冲区溢出程序使用这个溢出的数据将汇编语言代码放到计算机内存的某一位置:通常是产生超级用户权限的地方。简单的缓冲区溢出并不会产生太大的安全缺陷,最多造成程序崩溃;把溢出送到能够以超级管理员权限运行命令的区域才是最大的安全缺陷。最早的缓冲区溢出发生在Unix系统上。这是因为Unix操作系统是用c语言编写的,而c语言又不进行缓冲区边界检查,故在一定情况下如果用户输入的数据长度超过了应用程序指定的缓冲区的大小,就会覆盖其他的数据区造成程序崩溃或其他不可预知的错误。目前,利用缓冲区溢出进行的攻击数占攻击总数的一半以上。例如在c语言中,动态申请内存用的是malloc()函数[7]。用法见图 2-1。 char *pString; pString = ( char*)malloc( sizeof (char) * 10 ); 图 2-1 malloc()函数的应用

在上面的代码中malloc() 用于分配一段内存空间并返回指向该空间的指针。有的程员总假想内存足够大,故malloc() 总能返回正确的指针。但是一旦malloc() 分配失败,在不进行空指针检测的情况下引用pString就会出现Bug。可用

图 2-2的代码检查可分配的内存空间。 。 int MB = 0; while( malloc( 1 << 20 ) ) ++MB; printf ( “系统可分配的内存数是:%d”, MB ); 图 2-2可分配的内存数

2)缓冲区溢出的分类

缓冲区溢出主要有:静态缓冲区溢出,堆溢出,格式化字符串,Unicode与ANSI等原因引起的缓冲区溢出。静态缓冲区溢出和堆溢出上面已提到过,在此就不在介绍。

(1)格式化字符串Bug

严格说来,格式化字符串的Bug并不是缓冲区溢出,但是因为它会导致与缓冲区溢出同样的问题。这个问题来源于这样一个事实:对某个参数变量可变的函数,没有任何现实可行的方法来确定到底输入了多少个参数。造成这一问题的原因是通过%n格式指示符写入的一定数量的字节将被格式字符串写入到参数提供的指针中。要解决上述问题相对来说比较简单:在printf系列函数中,始终传递一个格式字符串。例如:printf ( input )会存在安全漏洞,但printf (“ %s”, input )就不会存在安全漏洞。

(2)Unicode和ANSI问题

由于Unicode和ANSI的不匹配而引起的缓冲区溢出在Windows操作平台上是很常见的。如果一时糊涂,在Unicode缓冲区以字节大小来计算元素数量就会发生缓冲区溢出的问题。该问题有两个常见的原因:

①Windows NT及以后的Windows版本都是即支持ANSI也支持Unicode字符串。 ②绝大多数Unicode函数是按照宽字符格式而不是按字节大小来计算缓冲区的大小。

6

BOOL GetInfo ( char* szName ) { WCHAR wszName[256]; //convert ANSI name to Unicode MultiByteToWideChar ( CP_ACP, 0, szName, -1, wszName, . sizeof (wszName) ); 图2-3 Unicode和ANSI-1

实例代码如 中间代码的问题在于MultiByteToWideChar函数的最后一个参数。对于改函数,文档中解释说:用宽字符来指定由lpWideCharStr参数所指向缓冲区的大小。此次调用输入的值是sizeof (wszName),这个值是256。但是因为wszName是一个Unicode字符串,有256个宽字符大小,而一个宽字符是了两个字节,所以sizeof (wszName)的值是512,因此该函数就会认为缓冲区的大小是512个字节,由于wszName位于栈中,所以有可能引起缓冲区溢出。正确的写法见图2-4。

BOOL GetInfo ( char* szName ) { WCHAR wszName[256]; //convert ANSI name to Unicode MultiByteToWideChar ( CP_ACP, 0, szName, -1, . . wszName, sizeof (wszName) / sizeof (wszName[0]) ); 图2-4 Unicode和ANSI-2

(3)内存泄露

内存泄漏是指堆栈区内存的泄漏。堆栈区内存是程序用malloc(), new 从堆栈中分配的、大小任意的(内存块的大小可以在程序运行期决定)一块内存,使用完后必须用free(), delete显示释放。否则,这块内存就不能被再次使用,即这块内存泄漏了。

内存泄漏会导致可用内存的数量逐步减少而造成计算机性能降低。最糟糕的情况是:过多的可用内存被分配掉而导致全部或部分设备停止正常工作,或者应用程序崩溃。 特别是当泄露发生在操作系统内部或主要的驱动程序是情况会更加糟糕。

造成内存泄露的原因无外乎重复申请动态内存而不释放以及访问以释放的内存。具体情况见图 2-5。

struct node { int value; struct node *next; }; void deleteList ( List &list ) { node *p = list; if ( p && p->next ) { free ( p ); p = p->next; } 图 2-5内存泄露代码

7

(4)整数溢出

自从计算机编程出现之后,整数的上溢、下溢及算术溢出(尤其是浮点数错误)就 一直存在。整数溢出已经成为软件安全的下一个威胁。此漏洞的核心在于:把一个数据声明为某种类型以后,其运算结果和人工运算的结果不一致。当然,也有某些语言实现了变长的整数类型,但是这种实现并不常见,而且这种实现还会带来额外的开销。

对不同语言而言,整数溢出漏洞的表现形式也不相同。C/C++拥有真正的整数类型;Visual Basic使用Variant的浮点数类型来表示数。C/C++可能是受影响最大的语言,并且还可能由于整数溢出漏洞而导致缓冲区溢出,甚至攻击者可以执行任意代码。但是所有的语言都会因为此漏洞而拒绝服务,出现逻辑错误。由于整数截断所造成的逻辑错误使得NFS中出现一个漏洞,该漏洞可使得任何用户可以以root权限来访问文件。观察2-6中的代码。 const long MAX_LEN = 0x7fff;

short len = strlen (input); if (len < MAX_LEN) //do something 图 2-6整数溢出

上述代码除了整数截断错误之外,当len和MAX_LEN比较时,语言规范告诉我们:在进行比较前,如果操作数类型不同,则要对数据类型进行向上转换。因此,实际上上述代码所做的是把len从有符号的16位整数转换为有符号的32位整数。转换时要想保持原有的值不变,则需要对原来的值进行符号扩展,直到长度和数据类型的值保持一致。图2-6的转换结果为:

len = 0x0100;

(long)len = 0x00000100; 或者

len = 0xffff;

(long)len = 0xffffffff;

这样,如果攻击者构造的len的值超过32K,那么len的值就变成了负值,由于进行的是向上类型转换,符号位不变,所以仍为负值。故针对len是否大于MAX_LEN的检查会得到相反的结果,最终出现程序的逻辑错误。

3)如何防止溢出

各种溢出的弥补之路是漫长而艰辛的。常见的避免溢出的方法主要有以下几种: (1)替换危险的字符串处理函数[8]

字符串处理是缓冲区溢出的最大来源,所以有必要回顾一下经常使用的函数。由于Windows还支持lstrcpy, lstrcat, lsrcpyn等函数,再加上Windows Shell还包含了类似的函数:来源于shlwapi.dll中的StrCpy, StrCat, StrCpy等,情况变得更加糟糕。所以在编写代码是应尽量避免使用那些函数,尽可能的应用STL。STL不仅节省了程序员的时间,还提高了效率,其提供的函数允许程序员可以在一个字符串中查找字符串和字符以及截断字符并提供了宽字符格式的函数版本。另外在VC++.NET中还引入了一个新的选项:/GS,它能够减少某些易被利用的缓冲区溢出。/GS选项在应用程序或DLL的开始代码中插入一些特殊代码,同样也在某些函数的prolog和epilog代码中插入这些特殊的代码。这个编译选项在本地数据和函数返回地址之间的堆栈中加入特殊数据:cookie,canary。cookie的值是随机的,而且是在进程或DLL的开始代码中确定的。当函数返回时会检查cookie,若发现cookie改变,就会调用一个特殊的错误处理程序。默认情况下,如果发现cookie改变,系统会挂起此进程:终止程序的运行总比受到攻击要好。

(2)审计分配操作

缓冲区溢出的另一根源是算术运算错误,所以要对自己编写的代码中计算分配空间的代码进行审计。

8

(3)使用分析工具

市面上有一些很好的工具可以分析c/c++代码中的安全缺陷,如:Coverity, PREfase, Klockwork等。Visual Studio.Net 2005 包含了PREfast和SAL(source code annotation language),这两个工具可以跟踪一些类似缓冲区溢出的安全缺陷。 2.2 SQL注入

1)漏洞概述

SQL注入是一种很普遍的代码漏洞,它可以导致机器被入侵以及敏感信息泄露。但是真正让人担心的是:受这些攻击影响的系统通常都是一些用来处理敏感信息以及PII(personally identifiable information)的电子商务系统。大多使用个人或商用的数据库驱动程序都会存在SQL注入漏洞。

要想从WEB站点得到信息由两种方法:SQL注入;查看Internet上是否开放了某些数据库端口(Microsoft SQL Server的TCP/1433,Oracle 的TCP/1521等),然后尝试使用默认的系统管理员账号等录。SQL注入引起的损失不仅限于数据库中的数据,它还可能导致服务器甚至整个网络受到入侵。对于攻击者而言,一个受到入侵的后台数据库仅仅只是获得更大以及更多的入侵战果的跳板而已。 2)如何避免SQL注入漏洞

对于任何程序只要包含如下模式,就可能存在SQL注入漏洞: (1)接受用户输入

(2)没有检查用户输入的有效性

(3)使用用户输入的数据来查询数据库

(4)使用字符串链接或者字符串替换函数来构造SQL查询,或者使用SQL exec来进行SQL查询。

要避免程序代码中隐藏SQL注入漏洞应做到: (1)代码审核中查找改漏洞

首先要寻找用于查询数据库德代码,没有明显包含数据库操作的代码一般不会引起SQL注入攻击。对代码进行扫描,找出含有数据库操作的代码块,然后寻找所有执行SQL语句的地方,判断是否在不可信的数据上使用了字符串链接后的替换函数。

(2)验证所有的输入

(3)不使用字符串链接来构造SQL语句 2.3 跨站脚本(XSS)漏洞

1)漏洞概述

最开始的时候,跨站脚本攻击(Cross Site Scripting)缩写为CSS,但这会与层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,所以后来人们便把跨站脚本攻击缩写为XSS以示区别。

跨站脚本攻击(也称为XSS)能够利用网站的漏洞窃取用户的个人信息。用户在浏览网站、使用即时通讯软件、甚至在阅读电子邮件时,通常会点击其中的链接。攻击者通过在链接中插入恶意代码,就能够盗取用户信息。攻击者通常会用十六进制(或其他编码方式)将链接编码,以免用户怀疑它的合法性。网站在接收到包含恶意代码的请求之后,如果没有对用户的输入进行严格的过滤的话,就会产成一个包含恶意代码的页面,而这个页面看起来就像是那个网站应当生成的合法页面一样。许多流行的留言本和论坛程序允许用户发表包含HTML和JavaScript的帖子,如果用户A发表了一篇包含恶意脚本的帖子,那么用户B在浏览这篇帖子时,恶意脚本就会执行,从而盗取用户B的会话信息。

9

XSS不仅常见,而且危害颇深。为了搜集用户信息,攻击者通常会在有漏洞的程序中插入JavaScript、VBScript、ActiveX或Flash以欺骗用户。用户一旦中招,就会导致用户帐号被盗,用户设置被篡改,cookie失窃等。

2)如何预防XSS漏洞 (1)代码审查中查找该漏洞

当审查代码以查找XSS漏洞时,要寻找这样的代码:从某种请求对象中读取数据,然后将数据传递给相应对象。很多人认为Response.Write以及类似的函数是XSS-style问题的唯一根源。事实上,Response.Redirect和Response.SetCookie这类构造函数也可以导致类似的漏洞,称之为HTTP响应分离攻击。因此:任何Web输入如果本身是不安全的,然后又直接转换为输出,就会存在安全方面的问题。

(2)向cookie中加入httponly选项。这对于IE6.0或更高版本的用户有所帮助,因为如果cookie做了这个标记后,将不能由document.cookie读取。

(3)根据输入,对标记的属性使用双引号。这会使得某些绕过HTML编码的攻击失败。

(4)使用XSSDetect检测跨站脚本XSS漏洞 XSSDetect 实际上是一个Visual Studio插件,它可以识别ASP.NET Web应用程序中的非持久性的XSS漏洞。同时,XSSDetect也是一种静态分析工具,它使用微软的CCI库进行分析。插件FxCop和 XSSDetect都使用了CCI程序库,但是XSSDetect要比FxCop插件更大,因为XSSDetect利用过程间数据流分析技术检测XSS安全漏洞。它使用了源(起点,输入的入口点)和汇(终点,输出的方法)的思想来检测可能引起XSS安全漏洞的数据通路。XSSDetect工作时,会为二进制文件建立一个庞大的数据图,并标识这些源和汇的数据通路[5]发现了使用编码库(例如AntiXss库)的数据通路,就将其排除在结果之外。

2.4 文件访问漏洞

1)漏洞概述

非法文件访问在代码审查中相对难以查找,并且可以轻易躲过安全设施的防范。此漏洞有三种常见的安全问题。第一个是:竞争条件,对某个文件设定了安全检查措施之后,在实施文件安全检查的时刻和文件使用的时刻之间存在着一个威胁窗口。竞争条件通常与同步错误有关,这些错误提供了机会窗口,在此期间一个进程可以干扰另一个进程,从而可能会引入安全漏洞。第二个常见的安全问题是典型的“非真正文件”问题:程序员的代码打开了一个文件,认为这段代码正在打开磁盘上的一个简单文件,但实际上却只是打开了到另一个文件的连接或是一个设备名或一个通道而已。第三个常见的问题是:将文件名的某些控制权提供给攻击者,而其本来不应该具备这些权限,这将允许其读取敏感数据,并存在写入敏感信息的可能性。

当前的大多数操作系统(Windows, Linux, Unix, Mac OS X, 等)上的应用程序并不是以一种孤立的方式进行操作的。其它进程可以随时中断正在运行的应用程序,而应用程序可能并没有为这种事件做好准备。也就是说:某些文件操作可能看上去是原子操作,但其实并不是。这些竞争条件可能会导致特权提升或通过故障和死锁来造成拒绝服务。

另一种类似的漏洞就是:获取某个文件的文件名,然后不管文件的属性就打开文件。在Linux, Unix, Mac OS X这类操作系统上,通常可以在符号链接的误用方面出现这类漏洞。程序打开某个文件,还认为那是一个真正的文件,但实际上它却是攻击者放在那里的一个符号链接。如果进程以root身份运行,这将是一个严重的问题,因为root进程可以删除所有的文件。

10

图 2-的这段代码中,没有考虑到“非一般”用户群。如果此代码是服务器端代码,那就属于不良代码,因为如果攻击者提供某个设备名(比如打印机端口lpt1),服务器将暂停响应直到设备超时。

void AccessFile ( char* szFileNameFromUser) { HANDLE hFile = CreateFile ( szFileNameFromUser, 0, 0, NULL, OPEN_EXISTING, 0, NULL); …… } 图 2-7 Windows中的文件漏洞

2)如何避免此类漏洞出现在代码中 (1)代码审查

查找这个漏洞最简单的办法是在代码审查期间搜索所有的文件I/O函数,特别是那些使用文件名的函数。如果发现一个这样的函数,就问问自己下面的问题:

①文件名来自何处,是否可信?

②文件名是否被多次用来访问或者操作这个文件? ③文件是否位于文件系统中一个攻击者可访问的位置?

④攻击者是否有什么办法可以操作文件名,并将其指向他们本来不应该访问的文件?

在接受合法文件名时一定要严格把关。不要认为文件名就代表着有效的文件而盲目的接受它们,特别是在服务器平台上。在用户的临时文件目录而不是在共有的目录中保存临时文件。这种方法还可使得在最小特权下运行应用程序更加容易,因为用户对其私有目录有完全的访问权限。但是,在很多情况下,只有root账号才能够访问系统临时文件目录。

3 代码安全的意义

代码安全的意义主要表现在以下三个方面: 3.1 不安全的代码容易受到攻击

正是因为不安全的代码容易受到黑客的攻击以及世界上存在一批喜欢攻击他人电脑的黑客,代码安全的意义才会显得尤其重要。电脑被攻击首先得有实施攻击的主体,如果不存在实施攻击的主体就算软件有漏洞,其造成的后果也不会像今天这么严重。人生而就有一种好奇心、好胜心,所以总想知道别人的秘密,总想做点别人做不到的事来证明自己的实力。所以总是想方设法的寻找软件的漏洞来满足内心深处的虚荣心。这是人之天性所致,是无法避免的,所以不安全的代码更容易受到那些人的光顾。编写安全的代码也因此显得格外重要。

3.2 不安全的代码容易引发系统崩溃

即便存在代码缺陷的软件没有受到黑客的攻击,其频繁造成的系统崩溃也足以恼怒其用户。可以想象:用户花钱买的软件不但没有为其带来方便反而经常发生系统崩溃,用户的心情会怎样?如果此软件运行在大型服务器上,造成的损失更是无法估算。 3.3 修复安全漏洞的代价高昂

首先,定位漏洞极其不易。不能简单的用漏洞/价格来确定漏洞定位的代价,所以漏洞定位的代价如何确定是第一个问题。其次,漏洞定位以后为了防止regression bug的出现而造成的额外损失,还要对软件进行新的考虑,这一部分的费用又因具体情况而异。微软在印度著名的咨询服务机构惠普罗组织的调查发布以后声称:“如今,当漏洞出现

11

时公司需要及时开发补丁,并及时组织安装。尽管Windows系统存在大量的补丁,但修复补丁的花费却不是很高”。微软声称的不是很高到底是多少,我们无从得知。但是MSRC(Microsoft Security Response Center)的研究人员认为,每一个安全漏洞的花费在100,000$左右。

4 如何编写安全代码

编写安全的代码涉及到安全设计、安全编码、测试技术。对于一个程序员而言,如何才能尽量做到编写出的代码是安全的呢?部分针对各种代码安全漏洞给出了在编写代码的过程中避免出现代码陷阱的方法。“不识庐山真面目,只缘身在此山中”,要想真正做到编写安全的代码,还应通览全局。因此,这一部分将从宏观角度分析并给出如何才能编写安全的代码。由于代码不安全因素来源于意识和物质,所以要尽可能的保证代码的安全也只有通过意识和物质。 4.1 思想观念

1)有责任心

“修身,齐家,治国,平天下”,所以要写出安全的代码必须先从自身做起。只有创建安全设计和编写安全代码的开发人员才能构建出安全的产品。但是软件最终的编码是由个人完成的。工具不能取代程序员完成编程工作。因此,产品的安全性很大程度上是代码编写者的责任。Blaster 和 CodeRed 蠕虫利用的就是个人编写的代码的漏洞。产 品的安全特性和程序员的关系见图4-1。

产品 组件1 组件2 ……….

1 底层编码由程序员完成 ,有可能出现错误,从而导致软件崩溃。 图 4-1有漏洞的代码

凡是可能发生的事一定会发生,所以应该仔细检查所有的代码,因为所有的代码都有可能受到攻击。从某种意义上说只有代码编写者才可以决定最终结果。所以,程序员应重视自己在软件开发中的作用,在编程的过程中也应该秉承其责任。

2)借鉴经验

这要求程序员要养成阅读代码的习惯。任何一个优秀的程序员都不能否认阅读别人的代码是其进步的便利途径。多读存在安全隐患的代码做到“俯而读,仰而思”。只有这样,才能从中汲取教训。多读好的代码,从中学习别人严谨的编程风格。

3)改正对代码安全的错误意识

这一点也是编写安全代码的关键,因为错误的思想将会指导其实践,所以应该从思想上改正对代码安全的错误意识。应该把代码的安全性作为软件的前提而不是一种特性;应该在设计时就考虑安全机制而不是在软件完成后在想安全问题。

12

4.2 技术因素

正确的思想观念有利于开发出安全的产品,但是仅有思想观念是不够的。要编写出安全的代码还应有过厚的技术因素(知识)作后盾。

1)熟知产品的运行环境

要使产品得到安全保障,首先要对产品的应用环境有所了解。且不说系统设计了,单单编程就是一项很复杂的工作,除了熟练使用编程工具外,还应熟知系统本身的内部工作机制,这是稳定安全的程序所必不可少的前提。例如要编写基于windows的应用程序,就必须熟知windows的内部机制,熟知事件、消息、窗口、线程、进程、句柄等概念。还应该了解系统调用与库函数调用的区别,对系统提供的API函数要有深入了解(包括参数、返回值),否则稍有疏忽就可能被黑客利用。

2)掌握所用的语言

在写程序之前先想一想是否熟知所用的编程语言,或许有点可笑但是问题往往出在这里。对于c开发者如果不明确表 4-1的正确含义,其编写的代码就有可能是不安全的。

表 4-1声明的不同含义

类型1 的Int*array[] IIIInt*fun() 含确Int*a,b,c,

类型2 IIiIInt(*array)[] IntInt(*fun)() IntInt*,*b,*c

毕竟在c语言中,很容易写出能够轻松通过调试却暗藏杀机的语句。1994年,Sun公司的Pascal在进行国际化时想使之可以按当地的日期格式打印日期。工作的流程是:编译器调用stat()得到Unix格式的修正时间,然后调用localtime()将其转换为tm结构,最后调用strftime()把tm结构转换为以当地时间格式表示的ASCII字符串。但是却因为变量生存时间的问题,每次都打印出一堆乱码。问题代码见图4-2。

char *localized_time ( char *filename ) { struct tm *tm_ptr; struct stat stat_block; char buffer[120]; stat ( filename, &stat_block); tm_ptr = localtime ( &stat_block.st_mtime); strftime ( buffer, sizeof ( buffer ), “%a %b %e %T %Y”, tm_ptr); return buffer; //buffer为局部变量,在localized_time() 运行结束后buffer 的 //生存周期结束,buffer 所占的内存释放,所以调用此函数并 //不能取得当地时间格式的字符串。 图4-2问题代码

时下的很多软件漏洞(缓冲区溢出,数组下标越界,等)一部分源自程序员的粗心,另一原因就是程序员不了解自己所用的语言。所以编程之前先掌握自己用的语言(只把用到此语言的相关部分掌握即可)。

(1)区分各类相似的运算符

„‟,””的含义不清楚。用 „‟ 引起的字符串代表一个整数,其值对应于该字符在编译器采用的字符集中的序列值。如编译器采用的是ASCII字符集,那么„a‟的含义与97(十进制)严格一致。而“”引起的字符串,代表的是一个指向无名数组起始字符的指针。=, ==;&&, &;|, ||的含义不同。虽然每一个刚步入编程之道的人都知道其区别,但在实际编程的时候还是会有不少的程序员由于一时疏忽而错用。这里的错用是不经意的,例如明明知道应该是if(a == b)……,但是写的时候却写成了if(a = b),程序中一旦出现此类Bug,排查起来是非常难的。论文中提到的Sun公司的2000万美元就是最好的例子。

(2)运算符的优先级

13

这个问题对于编程的入门者来说不是什么问题,因为其语句中很难看到多个不同的运算符连用的情况,大多是类似if(cond1 && cond2……)的语句。但如果假设hi 和 low 是两个整数,值介于0-15之间,r是一个整数,要使r的低四位与low各位一致,高四位与high各位一致,怎么办?r = hi<<4 + low?但是根据优先级,上式等价于 r = hi<<(4 + low)与题意完全不同。因此对于c语言中的这15个优先级应熟练掌握。

(3)声明与定义

C语言中的对象(函数、变量)有且只有一个定义,但是可以有多个声明。定义只能出现在一个地方,用于确定对象的类型并为其分配内存,从而创建一个新的对象,如:char m_str[100];声明用于描述对象的类型,用于指代其他地方定义的对象,如:extern char m_str[].混淆两者往往会写出类似图 4-3的代码。

File1.cpp char m_str[100]; …… File2.cpp extern char *m_str; 图 4-3声明与定义

(4)数组与指针

在c/c++ 编程中有一种危险的想法:“数组与指针是相同的”。许多的c语言书籍对于二者的异同也是含糊其辞,一提而过。所以有些程序员往往会写出类似图 4-3声明与定义的代码却不知错误之所在。显然,除了对声明、定义的含义混淆外,还没有理解数组、指针的异同。数组与指针的不同见表4-2。

表 4-2指针与数组

指针 数组 存储指向的数据的地址 直接存储数据 , 用mallloc()/free(),new/delete申请,释放内存 自动申请并回收 常用于动态数据结构 指针指向匿名的数据

指针名可以再赋值

且数据类型相同的元素

数组本身即为数据名 数组名不能再赋值

关于指针和数组的区别,下面这句话引自K&R C:As formal parameters in a function definition, char s[]; and char *s; are equivalent; we prefer the latter because it says more explicitly that the variable is a pointer.。由此可见只有作为函数定义的参数时数组下标表达式才可以写成带偏移量的指针的形式;只有在特定的上下文环境中(当他们作为一个函数调用的参数)数组名才会被转换成一个指向数组第一个元素的指针。除此之外指针就是指针数组就是数组,二者不可混用。数组和指针就好比中国的诗和词,二者有相似之处但二者的表现手法、技巧各异,不可将其混为一谈。

(5)边界计算

边界计算是容易出问题的地方,也是缓冲区溢出容易发生的缘由之一。c语言本身是一种不检查边界的语言,再加上某些库函数(对字符串操作的库函数)也不进行边界检查,因此稍有闪失就可能让黑客有机可乘。否认用户的任何输入 。

任何安全的信息如果经过了不安全的区域都有可能变成不安全的:常在河边走,怎能不湿鞋。时下,众多软件尤其是网络软件都提供处理用户输入信息的功能(用户登录信息验证)。

但是当用户输入的信息是非法信息时(输入′),上述步骤就会出现问题。有名的SQL注入攻击就是利用服务器不检查用户输入信息的合法性这个漏洞来进行攻击。SQL漏洞攻击的范例见图4-4。

14

输入大连大学研究生成绩查询网 http://202.199.155.6/dlugis/zhaosheng/ShowClass.asp?ClassID=8; 在考号栏输入:‟ or 1 = „1‟,提交; 服务器返回错误信息:执行查询代码时发现错误。 Select,waiyu,score1,score2,totalscore from tw_score_in where rtrim(ExamID)='' or 1 = '1'' and Name='' 错 误 号:-2147217900 '1' and Name='' 之前有未闭合的引号。 t 根据错误信息的提示在考号,姓名框都输入:' or 1 = '1,提交信息; 返回考号为112585000100001的考生的信息。 图 4-4非法获取信息

由错误提示可知服务器的sql语句并没有错,但是用户输入非法字符时却造成数据库中的信息泄露。如果这种代码发生在验证管理员信息的合法性模块,后果是可想而知的。如果先检查用户输入信息的合法性再对其做应有的操作,就不会造成这种数据泄露。另一种防范SQL注入攻击的方法是把对数据库操作的代码放在try…catch…语句块中。这样当对数据库的操作出现异常时就不会暴露服务器的信息图 4-5。 try { //对数据库的操作 …… } catch ( SQLException ex ) { System.out.println ( “操作错误,请重试!” ); } 图 4-5捕获异常

4.3 编程技巧

除了正确的意识和过硬的技术外,在编写代码时要保证代码尽可能的安全,还应掌握编写安全代码的常用规则、技巧。

1)编程风格

好的编程风格虽然不能产生安全的代码,但其对编写安全代码的作用却不可小视。好的编程风格有利于发现程序中的错误,有助于代码的维护工作等。文章写得再好但没有人认得出又有何用。所以别小看了编程风格的重要性,提高代码的安全性就需要从一点一滴做起。世上不存在最好的编程风格,一切都因需而定,所以在此就不再论述怎样的风格是最好的编程风格了。编程规范的重要性见4-6。显然就算程序员的代码中出现了错误,编写具备良好编程风格的Ex_2.cpp的程序员也可以容易的发现程序中的错误加以改正[9]。

//Ex_1.cpp void main(int arg,char **arv) { int i = 0; int j=0; while(j<10) { i+=j; j++; } //Ex_2.cpp void main ( int arg, char **arv ) { int sum = 0; int index = 0; while ( j < 10 ) { sum += index; index++; 图 4-6编程风格的重要性 2)软件运行权限

应该确保软件在最少的权限下就能运行,因为软件运行需要的权限越高,软件因各种原因崩溃时对系统造成的危害就越大。例如如果软件只有在root权限下才能运行,则黑客利用此软件的漏洞对系统进行攻击时,系统受到的威胁是难以估计的。

3)代码审查

15

代码审查可以识别出可能引起安全问题和事故的不安全的编码技术和缺陷。虽然可能非常耗时,但是代码审查必须在项目开发周期中定期进行,因为在开发时修补安全缺陷的成本和工作量比以后(产品部署或者维护周期中)修补要小得多。代码审查的另一个原因是人的天性所赋:人们在看自己的作品时常带着欣赏的眼光,而看别人的东西时却带着批判的眼光。所以让团队中的其他人来审核自己的代码,更容易找出代码中的安全漏洞,避免软件运行时崩溃[10]。

5 结束语

总之,在入侵、攻击、软件崩溃频频发生的今天,与其把希望寄托于防火墙和打补丁,还不如刚开始就建立安全的产品。这就好比与其采用筑堤的方式来治理黄河泛滥,还不如从根本上治理:从河床挖沙,降低河床高度。苍蝇不叮无缝的鸡蛋,只有提高软件自身的安全性才能最大限度的避免被黑客攻击;只有提高代码的质量才能有效地防止软件运行时崩溃。 致谢

夏始春余,山有嘉卉。初夏的日照早已艳阳朝天,花满枝头。在这个美好的季节里,我的本科毕业论文已接近尾声,这也意味着我最纯美的大学生活即将结束。四年寒窗,所收获的不仅仅是愈加丰厚的知识,更重要的是在阅读、实践中所培养的思维方式、表达能力和广阔视野。很庆幸这些年来我遇到了许多恩师益友,无论在学习上还是在生活上都给予了我无私的帮助和热心的照顾,让我在诸多方面都有所成长。感恩之情难以用语言量度,谨以最朴实的话语致以最崇高的敬意。

在此衷心的感谢我的指导吴俊华渊博专业知识,严谨的治学态度,精益求精的工作作风,诲人不倦的高尚师德,严以律己、宽以待人的崇高风范,朴实无华、平易近人的人格魅力对我来说有深远的影响。不仅使我树立了远大的学术目标、掌握了基本的研究方法,还使我明白了许多待人接物与为人处世的道理。本论文从选题到完成,每一步都是在老师的指导下完成的,倾注了老师大量的心血。在此,再次向我的指导老师吴俊华老师表示崇高的敬意和衷心的感谢!

感谢求学路上遇到的各位老师对我的教育和培养。在我的学习过程中曾得到他们细心地指导。在此,我要向各位老师深深地鞠上一躬。

还要感谢我的父母,给予我生命并竭尽全力给予了我接受教育的机会,养育之恩没齿难忘。

最后我的同学及室友在我大学四年中,对我的学习和生活都给予了诸多帮助,在此一并致谢。 参考文献

[1]刘远生. 计算机网络安全[M]. 北京:清华大学出版社,1982:5-6. [2]银石动力. 实战网络安全[M].北京:北京邮电大学出版社,2004:55-59.

[3]牛冠杰,网络安全机密与解决方案[M]. 北京:清华大学出版社,2003:234-275.

[4]笋大伟,李晨旸等. 网络安全—技术与实践[M]. 北京:北京邮电大学出版社,1982:19-36. [5]叶忠杰. 计算机网络安全技术[M]. 上海:科学出版社,1999:76-86. [6]李卫. 计算机网络安全与管理[M]. 北京:清华大学出版社,1998:56-67. [7]斯坎布里. 黑客大曝光[M]. 北京:清华大学,1982:45-67.

[8]舍马等著,余杰,黄彩霞译. 反黑客工具箱[M]. 济南:山东电子音像出版社,2005:51-63. [9]牛冠杰,笋大伟等. 网络安全技术实践与代码详解[M]. 北京:清华大学出版社,2007:25-23. [10]杨青编著. 电脑黑客攻防技术[M]. 北京:中国电力出版,2003:145-168.

16

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- awee.cn 版权所有 湘ICP备2023022495号-5

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务