3.1 一般性讨论 3.1.1 输入安全概述
输入是一个很广泛的概念,既是用户和软件之间的交互手段,也是软件内部模块之间的交互手段。针对软件用户的输入有很多类型,如:
用户在软件上输入一个命令,进行相应操作;
用户输入自己的账号密码,进行登录验证;
用户输入一个关键字,进行查询,等等。
模块之间进行数据传递时,也会有相应输入,如:
一个模块调用另一个模块时,输入一些参数;
一个模块读取一个配置文件来对自己的行为进行配置,等等。
从程序本身的角度讲,很多情况下,软件的安全问题就出在输入;从攻击者的角度讲,输入是进行攻击的重要手段。
经过调查总结,大部分的软件安全问题来源于应用程序接收输入数据前,没有进行安全性验证
对于编程人员来说,程序的所有输入数据,在进行安全性验证之前,都必须被认为是有害的。一旦忽略了这条规则,程序就可能遭受攻击。以上规则说起来很容易,也容易理解,但是在传统情况下,安全性验证往往被忽略。其主要原因是:
(1)在同一软件中,由于每一个输入到达最后的执行模块的过程中,都需要经过许多关口,每个关口都有可能进行检查。但就是因为这样,许多的开发人员都回避对输入的检查,因为他们一般假定这些数据在通过其他关口时,已经由其他关口的应用程序函数检查过了,他们不愿意牺牲性能去对数据进行多次校验。结果导致大家都没有进行验证。
(2)随着软件的分工,现在许多应用程序的功能都分块分布在不同的机器上(如客户机器和服务器上,或者对等机器上),开发人员有充足理由依赖应用程序的其他模块提供安全的检验。
但是从上面的例子又可以看出,输入安全解决不好,在严重的情况下,可能会带来巨大的危害。
攻击者可以利用这一漏洞让系统去执行他们想要执行的任何代码。其方法为:发布一个网页,当用户察看这个网页时,相当于用户在执行本地DirectXMIDI库中的内容,因为Internet Explorer在察看一个包含MIDI文件链接的网页时,会自动加载那个文件并播放它。因此,攻击者如果输入设计足够精巧,则可以利用这个库的漏洞来做很多事情,如:
删除用户计算机的所有文件;
将用户的一些机密文件通过电子邮件发送到攻击者的邮箱;
让机器崩溃,等等。
3.1.2 预防不正确的输入数据安全验证,说起来比较容易,做起来要考虑很多问题。并且就是因为被很多软件开发者认为太容易了,反而会忽略科学的方法。
数据安全验证的一般步骤如下:
(1)对安全的输入加以定义。
所有的输入设计都应该有一个安全定义,在这个安全定义内,数据被认为是格式正确的,并且是安全的。输入的数据一旦符合这个安全定义,或者说在这个安全定义的边界内,就认为不必要进行检查了。
(2)对输入的数据,针对前面的定义进行检查。一般情况下,可在对任何资源的访问之前设置一个检查模块,对输入数据进行检查。设计过程中,如果输入数据没有经过这个检查模块,就无法访问资源。可以设置多个检查模块,每个数据源(如网页、注册表、文件系统、配置文件等)都有一个检查模块
在对输入进行检查时,为了保证实际工作的可行性,在设计时,可以采用以下几个策略:
尽量让程序可以输入的入口少一些。这样的话,如果程序分为若干个模块,那么攻击者直接和某些模块通信的概率大大减小,也就是说,攻击者进入程序的途径将大大减少,同时也限制了他们对各模块之间的通信路径进行攻击的可能。安全验证的代价大大减小。
尽量减少允许的输入类型。这样可以让验证的工作更加简化。比如,如果仅仅允许输入的值为数字,验证时只需要针对数字进行验证,验证是相对简单的;如果将输入设计为任何字符串都可以输入,那么将要考虑更多的问题,验证难度会增加很多。
严格检查不可信的输入。不仅在数据最初进入程序时要执行检查,而且在程序实际使用这些数据时,也要进行检查。当然,检查的项目可以不一样,但是检查应该是时时存在的。不过,相对来说,更重要的是数据在使用之前的检查。一般情况下,可以采用如下方法:一个数据在进入模块时,在各个模块内进行针对该模块的安全检查。
转变观念,从定义“非法”到定义“合法”。安全程序开发人员往往有个误区,他们首先定义的是“什么样的数据非法?”,这个定义很容易下,比如,在E-mail中可以定义没有@符号为非法,但是这是不安全的。因为不可能将所有非法的数据都加以定义,攻击者常常会想出其他非法数据。定义“什么是非法”,容易想到,但是无法定义全;而定义“什么是合法”,就相对容易得多。“正确答案只有几个,而错误答案可以千千万”,就是这个道理。