软件安全-3.输入安全
3.1 一般性讨论
3.1.1 输入安全概述
输入是一个很广泛的概念,既是用户和软件之间的交互手段,也是软件内部模块之间的交互手段。针对软件用户的输入有很多类型,如:
- 用户在软件上输入一个命令,进行相应操作;
- 用户输入自己的账号密码,进行登录验证;
- 用户输入一个关键字,进行查询,等等。
模块之间进行数据传递时,也会有相应输入,如:
-
一个模块调用另一个模块时,输入一些参数;
-
一个模块读取一个配置文件来对自己的行为进行配置,等等。
从程序本身的角度讲,很多情况下,软件的安全问题就出在输入;从攻击者的角度讲,输入是进行攻击的重要手段。
经过调查总结,大部分的软件安全问题来源于应用程序接收输入数据前,没有进行安全性验证
对于编程人员来说,程序的所有输入数据,在进行安全性验证之前,都必须被认为是有害的。一旦忽略了这条规则,程序就可能遭受攻击。以上规则说起来很容易,也容易理解,但是在传统情况下,安全性验证往往被忽略。其主要原因是:
(1)在同一软件中,由于每一个输入到达最后的执行模块的过程中,都需要经过许多关口,每个关口都有可能进行检查。但就是因为这样,许多的开发人员都回避对输入的检查,因为他们一般假定这些数据在通过其他关口时,已经由其他关口的应用程序函数检查过了,他们不愿意牺牲性能去对数据进行多次校验。结果导致大家都没有进行验证。
(2)随着软件的分工,现在许多应用程序的功能都分块分布在不同的机器上(如客户机器和服务器上,或者对等机器上),开发人员有充足理由依赖应用程序的其他模块提供安全的检验。
但是从上面的例子又可以看出,输入安全解决不好,在严重的情况下,可能会带来巨大的危害。
攻击者可以利用这一漏洞让系统去执行他们想要执行的任何代码。其方法为:发布一个网页,当用户察看这个网页时,相当于用户在执行本地DirectXMIDI库中的内容,因为Internet Explorer在察看一个包含MIDI文件链接的网页时,会自动加载那个文件并播放它。因此,攻击者如果输入设计足够精巧,则可以利用这个库的漏洞来做很多事情,如:
-
删除用户计算机的所有文件;
-
将用户的一些机密文件通过电子邮件发送到攻击者的邮箱;
-
让机器崩溃,等等。
3.1.2 预防不正确的输入
数据安全验证,说起来比较容易,做起来要考虑很多问题。并且就是因为被很多软件开发者认为太容易了,反而会忽略科学的方法。
数据安全验证的一般步骤如下:
(1)对安全的输入加以定义。
所有的输入设计都应该有一个安全定义,在这个安全定义内,数据被认为是格式正确的,并且是安全的。输入的数据一旦符合这个安全定义,或者说在这个安全定义的边界内,就认为不必要进行检查了。
(2)对输入的数据,针对前面的定义进行检查。一般情况下,可在对任何资源的访问之前设置一个检查模块,对输入数据进行检查。设计过程中,如果输入数据没有经过这个检查模块,就无法访问资源。可以设置多个检查模块,每个数据源(如网页、注册表、文件系统、配置文件等)都有一个检查模块
在对输入进行检查时,为了保证实际工作的可行性,在设计时,可以采用以下几个策略:
-
尽量让程序可以输入的入口少一些。这样的话,如果程序分为若干个模块,那么攻击者直接和某些模块通信的概率大大减小,也就是说,攻击者进入程序的途径将大大减少,同时也限制了他们对各模块之间的通信路径进行攻击的可能。安全验证的代价大大减小。
-
尽量减少允许的输入类型。这样可以让验证的工作更加简化。比如,如果仅仅允许输入的值为数字,验证时只需要针对数字进行验证,验证是相对简单的;如果将输入设计为任何字符串都可以输入,那么将要考虑更多的问题,验证难度会增加很多。
-
严格检查不可信的输入。不仅在数据最初进入程序时要执行检查,而且在程序实际使用这些数据时,也要进行检查。当然,检查的项目可以不一样,但是检查应该是时时存在的。不过,相对来说,更重要的是数据在使用之前的检查。一般情况下,可以采用如下方法:一个数据在进入模块时,在各个模块内进行针对该模块的安全检查。
-
转变观念,从定义“非法”到定义“合法”。安全程序开发人员往往有个误区,他们首先定义的是“什么样的数据非法?”,这个定义很容易下,比如,在E-mail中可以定义没有@符号为非法,但是这是不安全的。因为不可能将所有非法的数据都加以定义,攻击者常常会想出其他非法数据。定义“什么是非法”,容易想到,但是无法定义全;而定义“什么是合法”,就相对容易得多。“正确答案只有几个,而错误答案可以千千万”,就是这个道理。
所以应该做的是确定“什么样的数据是合法的?”。在数据输入时,检查数据是否符合定义,拒绝所有不符合定义的数据;而不是检查数据是否不符合定义,接受符合定义的数据。
例如,有一个程序,根据用户的输入,创建一个文件。很显然,有些字符,如/,是不允许的。但是仅仅去检查这一个字符也不够,其他字符,比如,控制字符、空格、横线等,都有可能不合法。即使创建了一个“非法”字符的列表,也可能没有办法定义完全,因为总可能有没有考虑到的情况。
因此,正确的方法应该是:确定文件名输入的一个安全的特定格式,而拒绝不符合这个特定格式的所有输入。
3.2 几种典型的输入安全问题
3.2.1 数字输入安全问题
数字的输入安全,是比较常见的。比如在表单上输入一个人的年龄,一般就会有范围限制。对数字的安全检查主要有:
- 格式,如整数、小数等;
- 精度,如小数保留的位数等;
- 范围,如某个输入数字的大小取值范围等;
- 类型和范围的匹配,如为了预防整数溢出,对短整数的输入进行范围检查,等等。
针对数字输入的安全要注意以下几点:
(1)将数字格式进行确定。比如,有的系统中数字是阿拉伯数字,也有的系统支持中文数字(如一、二、三,甚至壹、贰、叁等)输入,有的系统中数字每三位就有一个“,”,等等。一般情况下,可以用正则表达式来进行验证字符串是否是数字,然后进行数字的安全检查。
(2)对于负数的验证。一般最好不要根据有没有符号位来确定该数是不是负数。因为有些程序中,如果输入一个很大的正数,也可能导致数值“溢出”而变成一个负数,而要进行一些底层的判断。
(3)特别要注意判断数值溢出(如整数溢出)的问题。
3.2.2 字符串输入安全问题
字符串输入的安全性,也是很重要的。根据前面的原则,一般情况下,要确定合法的字符串,拒绝所有其他字符串;而不是确定非法的字符串,接受所有其他字符串。
同样,指定合法字符串最简单的方法是使用正则表达式,这种情况下,只需要正确使用正则表达式,描述合法字符串的模式,判断输入的字符串是否符合这个模式,拒绝不符合这个模式的数据。
关于对字符串的验证,有以下问题值得注意:
(1)如果使用正则表达式,最好明确地指出要匹配数据的开始(通常用^来标识)和
结束(通常用$来标识),否则,攻击者可能在输入中嵌入攻击文本,并且能够绕过安全检查。
(2)尽可能在输入中拒绝特殊字符。因为有很多特殊字符在某些系统下拥有特殊的含义,如\,在Windows系统中可能作为文件路径分隔符。在开发阶段这个问题可能不容易引起注意,但是可能会被攻击者利用。
这些字符可能包括以下几类:
-
常规特殊字符,一般在ASCII码表内,如$、%、@、*、\0、\n等,但是由于有时候会用来表达特定含义,攻击者可能用数字代替这些字符来进行输入,达到攻击的目的。
-
不在ASCII码表内,字符值大于127的国际化的字符,也可能会有许多可能的含义。例如,UTF-8编码的字符,用两个字节进行编码,有些特殊字符也可以在该字符集里进行表达,尽量不要使用。
-
和某些特定应用有关的字符或者字符串,如shell中的命令名称(rm、ls、mount)、SQL中的关键词或者关键字符(如select、注释符号-一、单引号、exec)等。
-
在程序中有特定含义的字符,如某些系统中,将系统的配置信息用“#“隔开之后保存在配置文件内,这不是一个规定,但是可由软件的开发者在程序中自行确定;又如,在XML和HTML中用<和>表示结点,这些字符都不应该在合法范围之内。
3.2.3 环境变量输入安全问题
在操作系统中,环境变量是交互环境(shell)中的变量,在该交互环境下运行的进程,可以访问环境变量并修改其值。
环境变量在同一个交互环境下只有一个实例。不同的交互环境有不同的实例,互不干扰。其功能是用于影响该环境下进程的行为。
例如,在Linux中,输入env命令,就可以看到系统中的环境变量。它们以“变量名=值”的形式出现。在Windows中,输入set命令,也可以看到系统中的环境变量。
环境变量的输入可能给攻击者带来机会。主要来源于:
(1)环境变量的内容给攻击者以机会。
有些系统中,环境变量存储了和系统有关的一些配置信息,如在Linux中,很多程序配置被环境变量以某些隐含、模糊或未公开的方式所定义。例如,sh和bash shell使用IFS变量来决定分隔命令行参数的字符。那么,在执行一个shell时,把IFS设置为某些值,就可能进行攻击。
另一方面,由于并不是所有的环境变量都有文档的说明,这让用户遇到攻击之后无所适从;并且由于环境变量的可编辑性,攻击者甚至可以增加一些更加危险的环境变量,来达到攻击的目的。
(2)环境变量的存储格式给攻击者机会。在Linux下,环境变量在底层,通常是以字符申数组形式存储的,这个字符串指针数组有一个头指针,该数组中,按顺序存储各个环境变量,每一个环境变量是这个数组中的一个元素,该数组以NULL.指针结尾。
每一个元素的格式都为NAME=value。
这就潜在地说明,环境变量名不能包含等号,也不能包含一些其他敏感符号,如NIL(ASCI码为0的字符,一般表示一个字符串结尾)。但是如果这一点被攻击者利用,也会给系统带来损害。
实际上,环境变量方面的安全问题还有很多,读者可以参考相关文档。
如何解决以上安全问题?可从以下几个方面着手:
- 限制环境变量的使用权限;
- 可适当破坏环境变量在shell之间的共享;·对用户定义的环境变量,需要进行严格的检查。
3.2.4 文件名安全问题
在很多和文件输出有关的系统中,文件名有可能成为安全隐患。无论在什么样的操作系统中,文件名应该遵循以下安全准则:
(1)最好不要让用户来自己输入文件名,应该在界面上给用户一个默认的文件名。如Windows中将文件另存时,界面中“文件名”框中,会显示一个默认的合法名称,避免用户因为输入一些不合法的文件名造成安全问题。
(2)如果不得不让用户输入文件名,那么最好将文件名限制只能含有字母和数子。
特别应该考虑将一些特殊字符如/、\、、.和通配符(如*、?、[]和{})等从合法模式中去掉。
例如,如果文件名中可以用-,有一个文件名为-rf,那么在UNIX/Linux中执行命令rm*,将会变成执行rm-rf。这实际上是一个安全隐患。
(3)不要允许用户命名一些可能和物理设备冲突的文件名。比如,在一些系统中,一些文件名可以被认为是物理设备。例如,如果一个程序试图去打开COM1文件,可能被系统误解为是尝试和串口通信,系统就去进行串口的读写,而该操作又不是用户的朗望。因此,该种文件命名也要避免。
3.3 数据库输入安全问题
3.3.1 数据库的恶意输入
攻击者通过对数据库的恶意输入,可以将信息注入正在运行的流程,获联敏感效据,甚至危害进程的运行状态。
例如以下常见代码:
String sql = "select * From T_CUSTOMER where name = '"+name+"'";
变量name是由用户提供。这个SQL语句看上去没有问题,如果用户的输入为name=lihua,它将创建完整、良好的SQL语句.
但是这可能会给用户恶意输入的机会。该SQL语句的问题在于攻击者可在变量name中植入SQL语句。如果用户的输入为:lihua'OR1=1--,语句变为:
select * From T_CUSTOMER where name = 'lihua'or 1=1 --
这条语句将返回表T_CUSTOMER列NAME值为lihua的行,或者所有满足1=1子句的行。而对于表中的每一行,1=1都返回true,因此表中的所有行都将被返回,此种情况下,攻击者将能够获得表T_CUSTOMER中所有数据。
攻击者通过这种技术,可以完成以下攻击活动:
- 改变一条SQL语句的具体条件;
- 添加并且运行额外的SQL的语句;
- 秘密调用函数和存储过程。
在有些数据库产品中允许一次性运行多条语句,这给攻击者更大的攻击空间。如上面的例子,如果攻击者输入:lihua';DROP TABLE T_CUSTOMER--,语句变为:
select * From T_CUSTOMER where name = 'lihua';DROP TABLE T_CUSTOMER--
这条语句将返回表T_CUSTOMER列NAME值为lihua的行,然后删除T_CUSTOMER表。此外,这种攻击还包括各种可以改变数据库结构的操作,例如创圭、删除以及更新数据库对象等。
3.3.3 账户和口令问题
在应用程序中,连接到数据库通常要确定账户和口令。但是很多程序员对这一点不讲究,直接用管理员账户连接到数据库,这是很危险的。例如,在SQL Server中,以sa进行连接是很危险的,在Oracle数据库中,以system进行连接也是很危险的,它们都是功能强大且很可能对各自系统造成损害的账户。
实际上,应用程序的使用,并不一定要用到管理员账户,使用管理员账户,反而给了攻击者更多的机会。如在SQL Server中,当连接以sa账户进行,且SQL代码中有bug,攻击者可以执行任何管理员账户可以执行的任务,如:
- 删除系统中的数据库或表;
- 删除系统中表中的数据;
- 修改系统中表中数据;
- 修改存储过程、触发器;
- 删除日志;
- 添加新的数据库用户,等等。
很多情况下,程序员会将口令以明文的形式存放于代码中,运行阶段,这些口令置人进程的内存空间。此时,口令如果被攻击者获知,则可执行攻击者希望执行的任何代码。危险性也很大。
解决以上问题的方法主要有:
- 不到万不得已,不使用管理员账户;
- 使用最小特权账户,不给以额外的权限;
- 不允许使用空口令连接数据库,防止管理员疏忽而创建了空口令;
- 数据库连接字符串存放在配置文件中,最好可以加密,而不是代码中以明文显示;
- 发生错误时,仅给客户端通知信息,不给具体原因,防止攻击者利用这些通知信息进行数据库猜测,等等。