原文地址:
http://blog.csdn.net/estyle/archive/2004/07/02/32269.aspx
欢迎转载!但请注明出处(原文地址)和我的姓名:靳田
谢谢啦! ^_^
尽管大家都提“防御性编程”,但我还是比较喜欢“防御”——毕竟防御不是目的,只是措施而已,过分强调难免迷失方向。
首先要弄清楚的问题是:什么是防御?为什么要进行防御?
回答第一个问题,简单地说,防御有两个主要内容,既尽量避免潜在的错误发生和尽量减小错误带来的危害。
如果你已有一定编程经验,或许会拿防御和异常处理进行对比。就我看来,异常处理提供的是一套死的机制,而防御则属于活的思路编程范畴,把异常处理用活了,也就成了一种防御(注意这远远不是防御的全部)。
对于第二个问题,这样说或许比较贴切:就算你能容忍你的程序中存在大量高发且明显甚至危险的漏洞,你的客户和程序使用者却是无论如何都不能接受的!你惹他们试试看?
其次,我们要弄清楚的问题是:如何进行防御?
前面说过了,防御是活的,其根本任务不在于形式,而在于思路!现在我们先理清思路。
最传统的防御思路是:先为程序设置一个理想的应用环境(不仅仅是软硬件环境,还包括使用者行为等可变因素),分析法找出尽可能多的潜在错误(既那些可能与理想的运行环境不同的地方),制定验证依据和响应措施并实施,然后运用错误处理机制对未知的潜在错误进行捕获和处理。——注意,我用了“最传统的”来修饰这个防御思路,建议大家先不要在意这个思路的优劣,看完全文再慢慢讨论。
无论是什么样的防御思路,其最根本的问题总是“分析法找出尽可能多的潜在错误”,所以下面我们先说说这个根本问题。
或许举几个简单而片面,但是很常见的例子比较能够帮助大家理解:
一、用户信息注册,数据表中设计的年龄字段的数据类型是整型,用户可能输入的是“Eighteen”;
二、运用MSSQL数据库的ASP应用程序被部署在一台没有安装MSSQL和MSDE的服务器上;
三、依赖用户输入构造T-SQL语句的WHERE子句,导致埋下的SQL注入隐患;
四、浏览者短时间内多次尝试登陆。
呵呵,是不是一旦举例,就豁然开朗了?问题在于,如何找出尽可能多的潜在错误,强调的是尽可能多!这我可帮不了你,要靠你自己发现并总结经验。当然,借鉴别人的经验也是有效的,但如果缺乏自己的思考仍然是永远不够的。
另外,或许你会问,为什么不把所有问题交给异常处理?原因是,在B/S方面至少VBS和JavaScript/JScript的异常处理机制还比较幼稚,尽管它们以及ASP和ADO都提供了自己专用的记录错误信息的对象,但在实际使用中还是被动的——比如通过URL传递数据记录的页数,传递的数据大于总页数了,这时如果用异常处理,一般只能得到一些相对友好的错误信息,而防御则可以让它返回最后一页(最接近传递值指定的那一页)。如果你在异常处理方面非常有经验,或许你会说,异常处理不仅仅是显示友好错误信息,它同样可以捕获并纠正错误,你甚至有代码为证。我建议你再好好审视一下自己的代码,看看异常处理部分的代码是否用了许多“If”,那实际上还是防御。前面说过了,把异常处理用活了,就成了一种防御。这种“复活异常处理”的做法需要付出很大的代价,你会发现你的异常处理代码已不再简单而优雅……
再说了,一个合格的程序员,仅仅写出实现功能的代码是绝对不够的,找出程序潜在错误并进行防御也是必须技能之一。
写了那么多,我发现居然都还没开始说重点!你是不是都睡着啦?快醒醒!上面的就此打住,开始说重点了。
重点一:最常见的防御主题及相关建议
就算你还没意识到要主动“防御”,你的代码中肯定也会有验证数据有效性的部分!比如上面举的四个例子中的第一个。验证数据有效性就是最常见的防御主题。对于字符串验证,我强烈建议尽可能广泛地使用正则表达式!虽然VBS的字符串处理能力较强,但效率很差,而且也远不如政则表达式简单和优雅。其它情况没有什么悬念,不再赘述。
重点二:关于对SQL注入的防御
大部分情况下,这也属于验证数据(字符串)有效性范畴,比如对单引号的处理。但SQL注入并不仅仅是单引号就能概括的,所以与其费神深入研究SQL注入,不如采用存储过程,一方面可以防止用户构造WHERE带来的SQL注入隐患,另一方面提高了执行效率,还具备事务性的优势,这可是一举多得的好办法啊!哦,难道你没有为你那简单而优雅的代码着迷吗?后面我们还会提到这部分内容。
重点三:关于更高层次的防御
或许你已经发现防御真是一件费神的事,那为什么不采用更高层次的防御呢?比如重点二中提到的对SQL注入的防御,采用存储过程!再比如如下情况:
你必须一次处理两件事,更新表A并删除表B。代码完全正确,但执行的时候你突然发现表B被锁住了。结果表A被更新了,而表B仍然在!假设表A存储的就是数据库中所有数据表信息,不但数据正确性得不到保证,还会使数据库越来越臃肿。
这个时候,为什么不考虑使用事务处理?如果使用ACCESS,仍然可以使用ASP提供的事务处理机制(需要MTS配合才行)。
什么是“更高层次的防御”呢?普通的防御,其思路的根本在于对已知的潜在错误进行探测和验证后响应,属于思路编程范畴,是被动的;而更高层次的防御则是使用相对先进的技术手段防止已知的潜在错误发生,属于机制编程范畴,是主动的!
当然,不是所有的防御都可以被提高到一个新的层次,所以老老实实做好最基本的事也很重要。
重点四:关于服务器端防御和客户端防御
有经验的B/S开发者都知道,客户端防御是不可靠的,因为总有人能绕过客户端的防御措施。所以,最终还是服务器端防御措施起作用!但这是否意味着在客户端的实施防御措施是多此一举呢?绝对不!
想想如果仅仅依赖服务器端防御会是一个什么样的情形?还是以用户信息注册为例,一个天真烂漫的浏览者进行注册,总是输入一些无效的信息,每次所有表单数据都会被发送到服务器端进行处理再返回要求修改后重试!如果你以为这对4个P4志强并行处理加4G内存和SCSI磁盘阵列的服务器只是闹痒痒,你并不吃亏,那就错了。事实是,本来你的应用程序的可承载1000人在线,取消客户端防御后,只能承载250了……
所以,无论从哪个角度考虑,客户端防御仍然很重要。实际上,这个问题可以上升到如何有效并可靠地将压力从服务器端转移到客户端,但超出了本文主题,以后再说吧。
还有个地方需要强调,如果把对客户端表单控件设置为只读或者禁止也是一种防御措施的话,那同样是不可靠的!如果你使用Referrer进行来源验证,无论是在客户端还是在服务器端,也同样是不可信的(但不可信度要低一些)。你的程序越是用在重要场合,你就越是应该注意这些潜在的危险,哪怕不可信度再低……
重点五:关于防御的游戏规则
近来好像对“游戏规则”特别感兴趣,大家有兴趣还可以看看我昨天写的文章——关于B/S游戏规则的浮想(主要以ASP进行说明)。言归正传!
重点四中我很不情愿地说了这样一句话:“如果把对客户端表单控件设置为只读或者禁止也是一种防御措施的话”。实际上,我不是很赞同采用这种防御措施。它总会让用户觉得自己的权益受到损害,好像自己被程序开发者限制着一样,不能做自己想做的事。一种极端的情况是,那些为了防止浏览者查看网页源代码而设置右击鼠标弹出警告对话框的网页。如果你不能采用更高层次的防御化被动为主动(重点三),那就让防御让防御尽量以一种平和的方式运行,尽量少给使用者带来麻烦,更不要触怒使用者。否则,你就违背了防御的游戏规则,同时也违背了B/S的游戏规则!
防御是活的,最重要的不是形式,所以暂时我不认为防御该有技术层面的游戏规则。如果一定要规定一个,那么我觉得最合适的是,防御应该在有效的前提下重视效率。你觉得呢?听起来是废话吧?呵呵。其实我的意思是,能用正则表达式就不要用字符串分析,能用存储过程就不要在为防御SQL注入发愁……当然,如果能然防御措施看起来简单而优雅,那最好不过了。如何让自己的防御简单而优雅,同时又细致而有效呢?你需要有自己的思考。
好了,就到这里结束吧。