继上文服务器反黑技巧方案简述一之后,本文接着介绍服务器反黑技巧,具体内容如下所述。
方案五
编写安全的ASP代码
发帖子的目的是为了大家讨论与研究。。。
ASP中数据库的安全是一个很严肃的问题。很多代码的编写者意识到了这类问题,并且小心翼翼地对他们认为有问题的地方做了补救,但常见的情况是要么没有穷尽所有的可疑地点,要么这种补救逻辑上有误。对于一个耐心且嗅觉灵敏的攻击者来说,这种意义上的补救措施和没有任何补救措施没有本质上区别。
下面罗列的是一些可能出现的问题:有些是常见易犯的错误,有些根本就是逻辑上有问题。看看你是不是也这样写过?对于攻击者而言,倒着看这些东西,应该对寻找漏洞有点帮助,更为完整一点的检测方法,请等我的关于黑/白盒分析和自动化测试文章。
一、令人疑惑的过滤方式
典型例子是不管不顾地对所有的输入变量都去掉单引号,或者是把单引号替换成合法的两个单引号,例如:
id=replace(request.querystring("id"),"","")
str=replace(request("someinput"),"","")
现在很明了的是,第一个做法很有可能是错误的。因为引起SQLInjection的不总是单引号,再扩大一点,引起问题的不是任何单独的符号,这样子的过滤,有些冤枉单引号了。正确的利用注入,重要的一点是闭合前面的一句SQL查询语句——往往是得先正确地闭合前面一个条件,因为我们可能会在同一句里面引入新的条件,补救措施只要破坏注入条件应该就可以了,但是考虑到其复杂性(下面会说),最好还是较为完整的限制一下输入的字符种类。
第二个看起来是没有什么问题的,但潜在的会带来一些隐患。这很容易给人造成的一个错觉是,我对输入的字符串已经很有效的做过处理了,以后使用没有什么问题。这句话没有错,对字符串来说这样做也是很正确的,但是他扮演了一个不光彩的角色,试想一下,如果过滤后的字符串放进了数据库,而后续的语句有直接拿出来使用的,这种对前面过滤的依赖性,是不是正确的呢?
也许较好的做法应该是,针对具体的情况来确定过滤的准则。
常见的输入变量有三种:数字,字符串还有集合。对于数字型的输入变量,简单调用一下判断函数即可,见得到的代码中,凡是检查了这类变量的,几乎都正确。对于字符串型的来说,基本上在插入到生成的SQL语句时,前后都有单引号,如果仅从破坏注入条件来看,把单引号替换成两个单引号应该问题不大。同理的,如果是一个字符串的集合,也可以简单的用这种方法。而如果是数字的集合,情况可能稍微麻烦一点,至少你得允许数字、逗号或许还有空格之类的符号在输入中正常出现,这样子的过滤规则可能显得复杂,不过你可以借鉴一下dvBBS6.1打过补丁后的版本,总的来说,对于已经发现的过滤漏洞而言,他们还是补得比较好的。
对于第二句话,至少现在不能说它说错的,我们留待后面解决。
二、获取的数据值得信赖吗?
其实这样子说范围显得有点大,一下子涉及到很多方面,一个例子一个例子地举来看好了。
首先是关于选择过滤数据的问题。一直以来,我们认为凡是用户输入的东西,都要经过适当的处理。没错,但真正的是否都做到呢?随便找个抓包的工具,比如Ethereal,看看在你用IE提交表单或者是打开连接的时候,都提交了什么。或者,简单一些,打开NetAnt编辑一个任务,在协议标签中,看看那个“自定义提交者”和“用户代理”的选项。
我想你已经明白了,对方可以自己定制的东西不仅仅是GET或POST过来的数据!如果所有的用户都规规矩矩地用浏览器,确实不用防备这么严,如果对方不这么老实,在取服务端变量或Cookie的时候可要小心了,没有任何人能够保证你获得的数据是合法的。对于Cookie而言,很多程序都出过问题,所以以前强调得比较多,至于另外的,关注的人可能比较少一点,但你是否看过或者写过这样的代码:
- sql="ShowHOT_COM_inst_online_char2,"&statuserid&","&membername&","&memberclass&","&Request.ServerVariables("REMOTE_HOST")&","&boardid&","&Request.ServerVariables("HTTP_USER_AGENT")&","&replace(stats,"","")&","&Request.ServerVariables("HTTP_X_FORWARDED_FOR")&","&UserGroupID&","&actCome&","&userhidden&","&userid&""
Request.ServerVariables("HTTP_USER_AGENT")就是你在NetAnt中看到的用户代理选项,也就是说你可以伪造,同样可以伪造的还有Request.ServerVariables("HTTP_REFERER"),也就是你在NetAnt中看到的提交者选项等等。在做一些项目的时候,很有可能要将这一类的变量添加入数据库,这时候要千万小心,这个地方的忽略,引起的后果和其他类型变量未过滤导致的后果是一样的。
在Google上搜索Referer和Request.ServerVariables两个关键字,还可以看到很多有问题的写法,或者去看看五月份左右的关于动网论坛入侵的文章,也许你的理解会更加深刻一点。
然后是一个隐藏得稍微深一点的问题,不是用户的直接输入要不要过滤?
这就回到了我们前面留下的那个问题,单引号换成两个单引号的潜在威胁。在第二次构造SQL语句的时候,倘若数据是从数据库里面直接去取出来用的,多数情况下人们会认为前面已经处理过的东西看起来似乎并没有必要再处理,或者干脆就是没有意识到应该处理。这是极其错误的!从两个方面来看,首先你入库的时候对提交数据中的单引号处理,仅仅是保证了单次SQL语句构造的正确性,并没有一劳永逸地解决问题;再说了,后面取出数据用的时候,对数据安全性检查的依赖并没有得到保证,因为这种依赖关系没有传递下来,而且依赖关系本身还不是可传的。
就replace(request("someinput"),"","")而言,它的不安定性在于这种过滤方式只是一种妥协,换句话说只是在有限的范围内掩盖了可能出现的问题,而没有永久性的处理掉。它还有一个讨厌的地方在于给人一种错觉,似乎是处理过的数据已经安全了,容易让后继的代码编写者产生虚幻的安全感。对这两个弱点,不是*换一个写法就能解决的,因为如果你把单引号干脆去掉,又会引来另外一个问题,输入数据中确实有需要而且正确的单引号怎么办?从一开始我就说,单引号本身是无罪的,过滤它只是一种解决手段而已,所以我们还是就这样写吧,不过要在后继的部分加强一下检查。
这一类的问题,如果依然用动网论坛做例子,我建议看一下六月八号的漏洞文章。
还有就是过滤器的位置,这个掺杂了逻辑问题在内的复杂问题。
我曾经非常惊奇地发现乔客论坛对外散布的版本中一段让人觉得不可思议的问题代码,如果你比较感兴趣的话,翻翻gallery.asp就能看到一个特定的动作序列(action=flash_view),绕过了所有对id的检查。
其实说起来,这一类代码不太可能有太复杂的逻辑结构,对代码进行审查的时候,进行所有的分支覆盖是可以手工完成的,只要稍微想想就会发现对变量的检查是否能够有效地到达你的目的地——生成SQL语句的地方。
关于过滤器的位置,如果要深入下去,马上就会出来一些让人眼花缭乱的东西,中间的分析很麻烦而且很形式化,虽然确实有算法可以保证位置选取的正确性,但是我想这里还是给出一些结论性的东西吧。倘若你很有兴趣,我想你可以来信和我交流。
过滤的位置,取决于两个方面:你获得变量的来源,以及你需要保证到的生成SQL语句的位置。前面一个,不论是来自于直接还是间接输入,先想想可能的输入字符;对于后面一个,你要保证无论程序运行情况怎样,经过了过滤语句的流程一定会经过你需要保证到的生成SQL语句的位置(保证其是有效过滤语句的后向必经节点)。如果你不很清楚流程的判断,我的建议是if中仅仅判断,if嵌套间不要有多余的东西,过滤语句后紧接生成SQL语句。
再回到前面提到的潜在问题,我们终于可以在这里解决了:在取出数据后依然首先进行判断。因为根据前面说的,这一种间接输入依然有可能出现危险。
说到这里,插一句另类的过滤位置问题:不要把对输入的过滤放到客户端解决,那是可以绕过的!谁能保证你的VBScript/JavaScript能起作用,如果别人直接用NC或者一个不支持脚本的浏览器呢?
上述两个大的方面,以软件测试的目光来认识,显然是没有穷尽所有的分支所导致。在使用对方提交的数据之前,先做一个对方所有可能进入字符的分析列表,然后就每一种输入分支情况进行类型的审核,这是每个代码编写者都应该做的事情。这是一件很简单的事情,因为只是类型上的审核还好,碰上语义的问题就麻烦了……
三、类型正确意味着放行?
涉及到语义的问题,要是可能的话,我选择最好还是避开。
譬如对于一个整型数字,你输入的确实是一个整型,通过了过滤器,潜在的问题是你的输入内容上合法吗,或者根本就不应该从你这里获得信息?很多年前就有人提出来,有些注册的模块存在问题:它里面的id是通过一个type=hidden掩盖后隐式提交的,但是我在第一步建立了用户,第二步仍就有可能通过提交内容不合法的id来修改他人的信息。这种异类的问题都是非常难发现,而且几乎都只有*经验而不是某一个具体的算法来处理。我们在联系一下前面的,连起来想想或许能够更加清楚,对于输入的字符串,感觉上没有过滤也不会有错,因为比较数字之类集合来说,字符串所能容纳的几乎是全部可能输入的集合。事实上,常见的是没有过滤造成单引号的错误匹配,进而导致了SQLInjection。严格说起来,这也是一个语义上的问题,不过对于这样子的特殊情况而言,可以通过处理输入中的单引号来保证语义的某种程度上的正确。所以我也一再强调,单引号本身是无罪的,不过是背了语义的黑锅而已。
令人遗憾的是,如果是整型数据出了语义上的问题,没有什么东西可以替语义背黑锅了,所以没有了一个一定程度上通用的解决方案。不过也不要悲观,前面就已经说过,能避开就避开,釜底抽薪不要让可能有语义问题的变量作为输入好了。
仅仅考虑数据库安全的话,所有有威胁的语义问题都几乎出在对数据库的操作上,那么,我们只要注意update/insert等语句就可以了,如果考虑数据内容的安全性的话,select也得算上。一般来说,特别关注的是生成的where后面的条件语句,总觉得条件的语义应该是由服务器端决定的,而不是说用户的输入是什么就是什么。我的建议是对于所有的可能出现语义问题的整型变量,最好都是Session,当然,没有进行非常深入的研究,或许有人能够提出像对付字符串的语义问题一样的有效方法也说不一定。不过话又说回来,在语义层面上看对字符串的过滤,不能证明它不安全,但是更重要的没有人能够证明它安全,只是大家现在用着没有问题,也就默认了罢了。
若要深入的分析语义,也会突然冒出一大堆奇怪的东西,所以还是就此打住吧,真切的希望同行之间能够多一些这方面的交流!
前面说的也许更多地会用在一些对既有代码的补救上,如果是从头开始构架一个软件的话,上面的仅仅是设计上一些参考。所有的漏洞都是源于设计上的缺陷,一个好的软件应该被证明其模型是正确的,这很难但是可以做到。如果你一开始就证明了软件的正确性,我想也不会有漏子可以给别人钻了。
方案六
防范SQL指令植入式攻击
发帖子的目的是为了大家讨论与研究。。。
什么是SQL指令植入式攻击?
在设计或者维护Web网站时,你也许担心它们会受到某些卑鄙用户的恶意攻击。的确,如今的Web网站开发者们针对其站点所在操作系统平台或Web服务器的安全性而展开的讨论实在太多了。不错,IIS服务器的安全漏洞可能招致恶意攻击;但你的安全检查清单不应该仅仅有IIS安全性这一条。有些代码,它们通常是专门为数据驱动(data-driven)的Web网站而设计的,实际上往往同其它IIS漏洞一样存在严重的安全隐患。这些潜伏于代码中的安全隐患就有可能被称为“SQL指令植入式攻击”(SQLinjection)的手段所利用而导致服务器受到攻击。
SQL指令植入式攻击技术使得攻击者能够利用Web应用程序中某些疏于防范的输入机会动态生成特殊的SQL指令语句。举一个常见的例子:
某Web网站采用表单来收集访问者的用户名和密码以确认他有足够权限访问某些保密信息,然后该表单被发送到Web服务器进行处理。接下来,服务器端的ASP脚本根据表单提供的信息生成SQL指令语句提交到SQL服务器,并通过分析SQL服务器的返回结果来判断该用户名/密码组合是否有效。
为了实现这样的功能,Web程序员可能会设计两个页面:一个HTML页面(Login.htm)用于登录,另一个ASP页面(ExecLogin.asp)用于验证用户权限(即向数据库查询用户名/密码组合是否存在)。具体代码可能象这样:
Login.htm(HTML页面)
代码:Username:
Password:
ExecLogin.asp(ASP页面)
代码:
乍一看,ExecLogin.asp的代码似乎没有任何安全漏洞,因为用户如果不给出有效的用户名/密码组合就无法登录。然而,这段代码偏偏不安全,而且它正是SQL指令植入式攻击的理想目标。具体而言,设计者把用户的输入直接用于构建SQL指令,从而使攻击者能够自行决定即将被执行的SQL指令。例如:攻击者可能会在表单的用户名或密码栏中输入包含“or”和“=”等特殊字符。于是,提交给数据库的SQL指令就可能是:
代码:SELECT*FROMtblUsersWHEREUsername=or=andPassword=or=
这样,SQL服务器将返回tblUsers表格中的所有记录,而ASP脚本将会因此而误认为攻击者的输入符合tblUsers表格中的第一条记录,从而允许攻击者以该用户的名义登入网站。
SQL指令植入式攻击还有另一种形式,它发生在ASP服务器根据querystring参数动态生成网页时。这里有一个例子,此ASP页面从URL中提取出querystring参数中的ID值,然后根据ID值动态生成后继页面:
代码:
在一般情况下,此ASP脚本能够显示具有特定ID值的文章的内容,而ID值是由URL中的querystring参数指定的。例如:当URL为http://www.example.com/Article.asp?ID=1055;时,ASP就会根据ID为1055的文章提供的内容生成页面。
如同前述登录页面的例子一样,此段代码也向SQL指令植入式攻击敞开了大门。某些恶意用户可能会把querystring中的文章ID值偷换为“0or1=1”等内容(也就是说,把URL换成http://www.example.com/Article.asp?ID=0;or1=1)从而诱使ASP脚本生成不安全的SQL指令如:
代码:SELECT*FROMtblArticlesWHEREID=0or1=1
于是,数据库将会返回所有文章的内容。
当然了,本例服务器所受的攻击不一定会引起什么严重后果。可是,攻击者却可能变本加厉,比如用同样的手段发送DELETE等SQL指令。这只需要简单地修改前述URL中的querystring参数就可以了!例如:任何人都可以通过“http://www.example.com/Article.asp?ID=1055;DELETEFROMtblArticles”之类的URL来访问Web网站。SQL指令植入式攻击的危害
SQL指令植入式攻击可能引起的危害取决于该网站的软件环境和配置。当Web服务器以操作员(dbo)的身份访问数据库时,利用SQL指令植入式攻击就可能删除所有表格、创建新表格,等等。当服务器以超级用户(sa)的身份访问数据库时,利用SQL指令植入式攻击就可能控制整个SQL服务器;在某些配置下攻击者甚至可以自行创建用户帐号以完全操纵数据库所在的Windows服务器。
杜绝SQL指令植入式攻击
杜绝SQL指令植入式攻击的第一步就是采用各种安全手段监控来自ASPrequest对象(Request、Request.QueryString、Request.Form、Request.Cookies和Request.ServerVariables)的用户输入,以确保SQL指令的可*性。具体的安全手段根据你的DBMS而异,下面给出的都是基于MSSQLServer的例子。
在前述登录页面的例子中,脚本期望得到的两个输入变量(txtUserName和txtPassword)均为字符串类型。无论用户在哪个参数中插入单引号,他都可能让数据库执行单引号中的SQL指令。为了杜绝此类SQL指令植入式攻击,我们可以借助Replace函数剔除单引号,比如:
代码:p_strUsername=Replace(Request.Form("txtUsername"),"","")p_strPassword=Replace(Request.Form("txtPassword"),"","")
在第二个例子中,脚本期望的输入变量是长整型变量(ID)。用户可以通过在ID参数中插入特殊字符来运行不安全的SQL指令。为了为了杜绝此类SQL指令植入式攻击,我们只需要借助CLng函数限制ID值为长整型变量,比如:
代码:p_lngID=CLng(Request("ID"))
当用户试图在ID中包含特殊字符时,CLng就会产生一个错误。
为了进一步减少SQL指令植入式攻击的危胁,请务必清除客户端错误信息文本中的所有技术资料。某些错误信息往往泄露了技术细节,从而让攻击者可以看出服务器的安全漏洞所在。这里指的错误信息不但包括应用程序生成的消息框,还包括来自IIS的出错提示。为此,你可以禁止由IIS发送的详细错误信息,而改用自定义的出错页面。(关于创建自定义的出错页面的更多信息,请务必参阅《CreatingCustomASPErrorPages》。)
最后,为了减轻SQL指令植入式攻击的危害,请限制Web应用程序所用的数据库访问帐号权限。一般来说,应用程序没有必要以dbo或者sa的身份访问数据库。记住,给它的权限越少,你的网站越安全!你还可以考虑分别给每个需要访问数据库的对象分配只拥有必需权限的帐号,以分散安全漏洞。例如:同是前端用户界面,当用于公共场所时就比用于具有本地内容管理机制的平台时更加需要严格限制数据库访问权限。
总结:
希望本系列介绍的服务器反黑技巧能够对读者有所帮助,更多有关操作系统的知识还有待于读者去探索和学习。
【编辑推荐】