探索黑客技术攻防,实战研究与安全创新

导航菜单

黑客防御篇:Web框架安全

前面的章节,我们讨论了许多浏览器、服务器端的安全问题,这些问题都有对应的解决方法。总的来说,实施安全方案,要达到好的效果,必须要完成两个目标:

(1)安全方案正确、可靠;

(2)能够发现所有可能存在的安全问题,不出现遗漏。只有深入理解漏洞原理之后,才能设计出真正有效、能够解决问题的方案,本书的许多篇幅,都是介绍漏洞形成的根本原因。比如真正理解了XSS、SQL注入等漏洞的产生原理后,想彻底解决这些顽疾并不难。但是,方案光有效是不够的,要想设计出完美的方案,还需要解决第二件事情,就是找到一个方法,能够让我们快速有效、不会遗漏地发现所有问题。而Web开发框架,为我们解决这个问题提供了便捷。

MVC框架安全

在现代Web开发中,使用MVC架构是一种流行的做法。MVC是Model-View-Controller的缩写,它将Web应用分为三层,View层负责用户视图、页面展示等工作;Controller负责应用的逻辑实现,接收View层传入的用户请求,并转发给对应的Model做处理;Model层则负责实现模型,完成数据的处理。

blob.png

从数据的流入来看,用户提交的数据先后流经了View层、Controller、Model层,数据的流出则反过来。在设计安全方案时,要牢牢把握住数据这个关键因素。在MVC框架中,通过切片、过滤器等方式,往往能对数据进行全局处理,这为设计安全方案提供了极大的便利。比如在SpringSecurity中,通过URLpattern实现的访问控制,需要由框架来处理所有用户请求,在SpringSecurity获取了URLhandler基础上,才有可能将后续的安全检查落实。在SpringSecurity的配置中,第一步就是在web.xml文件中增加一个filter,接管用户数据。

然而数据的处理是复杂的,数据经过不同的应用逻辑处理后,其内容可能会发生改变。比如数据经过toLowercase,会把大写变成小写;而一些编码解码,则可能会把GBK变成Unicode码。这些处理都会改变数据的内容,因此在设计安全方案时,要考虑到数据可能的变化,认真斟酌安全检查插入的时机。在本书第1章中曾经提到,一个优秀的安全方案,应该是:在正确的地方,做正确的事情。举例来说,在“注入攻击”一章中,我们并没有使用PHP的magic_quotes_gpc作为一项对抗SQL注入的防御方案,这是因为magic_quotes_gpc是有缺陷的,它并没有在正确的地方解决问题。magic_quotes_gpc实际上是调用了一次addslashes(),将一些特殊符号(比如单引号)进行转义,变成了\’。对应到MVC架构里,它是在View层做这件事情的,而SQL注入是Model层需要解决的问题,结果如何呢?黑客们找到了多种绕过magic_quotes_gpc的办法,比如使用GBK编码、使用无单引号的注入等。PHP官方在若干年后终于开始正视这个问题,于是在官方文档1的描述中不再推荐大家使用它:

blob.png

所以Model层的事情搞到View层去解决,效果只会适得其反。一般来说,我们需要先想清楚要解决什么问题,深入理解这些问题后,再在“正确”的地方对数据进行安全检查。一些主要的Web安全威胁,如XSS、CSRF、SQL注入、访问控制、认证、URL跳转等不涉及业务逻辑的安全问题,都可以集中放在MVC框架中解决。在框架中实施安全方案,比由程序员在业务中修复一个个具体的bug,有着更多的优势。首先,有些安全问题可以在框架中统一解决,能够大大节省程序员的工作量,节约人力成本。当代码的规模大到一定程度时,在业务的压力下,专门花时间去一个个修补漏洞几乎成为不可能完成的任务。其次,对于一些常见的漏洞来说,由程序员一个个修补可能会出现遗漏,而在框架中统一解决,有可能解决“遗漏”的问题。这需要制定相关的代码规范和工具配合。最后,在每个业务里修补安全漏洞,补丁的标准难以统一,而在框架中集中实施的安全方案,可以使所有基于框架开发的业务都能受益,从安全方案的有效性来说,更容易把握。12.2模板引擎与XSS防御在View层,可以解决XSS问题。在本书的“跨站脚本攻击”一章中,阐述了“输入检查”与“输出编码”这两种方法在XSS防御效果上的差异。XSS攻击是在用户的浏览器上执行的,其形成过程则是在服务器端页面渲染时,注入了恶意的HTML代码导致的。从MVC架构来说,是发生在View层,因此使用“输出编码”的防御方法更加合理,这意味着需要针对不同上下文的XSS攻击场景,使用不同的编码方式。在“跨站脚本攻击”一章中,我们将“输出编码”的防御方法总结为以下几种:

在HTML标签中输出变量;

在HTML属性中输出变量;

在script标签中输出变量;

在事件中输出变量;

在CSS中输出变量;

在URL中输出变量。

针对不同的情况,使用不同的编码函数。那么现在流行的MVC框架是否符合这样的设计呢?答案是否定的。在当前流行的MVC框架中,View层常用的技术是使用模板引擎对页面进行渲染,比如在“跨站脚本攻击”一章中所提到的Django,就使用了DjangoTemplates作为模板引擎。模板引擎本身,可能会提供一些编码方法,比如,在DjangoTemplates中,使用filters中的escape作为HtmlEncode的方法:

DjangoTemplates同时支持auto-escape,这符合SecurebyDefault原则。现在的DjangoTemplates,默认是将auto-escape开启的,所有的变量都会经过HtmlEncode后输出。默认是编码了5个字符:

如果要关闭auto-escape,则需要使用以下方法:

或者

为了方便,很多程序员可能会选择关闭auto-escape。要检查auto-escape是否被关闭也很简单,搜索代码里是否出现上面两种情况即可。但是正如前文所述,最好的XSS防御方案,在不同的场景需要使用不同的编码函数,如果统一使用这5个字符的HtmlEncode,则很可能会被攻击者绕过。由此看来,这种auto-escape的方案,看起来也变得不那么美好了。(具体XSS攻击的细节在本书“跨站脚本攻击”一章中有深入探讨)再看看非常流行的模板引擎Velocity,它也提供了类似的机制,但是有所不同的是,Velocity默认是没有开启HtmIEncode的。在Velocity中,可以通过EventHandler来进行HtmIEncode。

使用方法如下例,这里同时还加入了一个转义SQL语句的EventHandler。但Velocity提供的处理机制,与Django的auto-escape所提供的机制是类似的,都只进行了HtmIEncode,而未细分编码使用的具体场景。不过幸运的是,在模板引擎中,可以实现自定义的编码函数,应用于不同场景。在Django中是使用自定义filters,在Velocity中则可以使用“宏”(velocimacro),比如:

通过自定义的方法,使得XSS防御的功能得到完善;同时在模板系统中,搜索不安全的变量也有了依据,甚至在代码检测工具中,可以自动判断出需要使用哪一种安全的编码方法,这在安全开发流程中是非常重要的。在其他的模板引擎中,也可以依据“是否有细分场景使用不同的编码方式”来判断XSS的安全方案是否完整。在很多Web框架官方文档中推荐的用法,就是存在缺陷的。Web框架的开发者在设计安全方案时,有时会缺乏来自安全专家的建议。所以开发者在使用框架时,应该慎重对待安全问题,不可盲从官方指导文档。

Web框架与CSRF防御

关于CSRF的攻击原理和防御方案,在本书“跨站点请求伪造”一章中有所阐述。在Web框架中可以使用securitytoken解决CSRF攻击的问题。CSRF攻击的目标,一般都会产生“写数据”操作的URL,比如“增”、“删”、“改”;而“读数据”操作并不是CSRF攻击的目标,因为在CSRF的攻击过程中攻击者无法获取到服务器端返回的数据,攻击者只是借用户之手触发服务器动作,所以读数据对于CSRF来说并无直接的意义(但是如果同时存在XSS漏洞或者其他的跨域漏洞,则可能会引起别的问题,在这里,仅仅就CSRF对抗本身进行讨论)。因此,在Web应用开发中,有必要对“读操作”和“写操作”予以区分,比如要求所有的“写操作”都使用HTTPPOST。在很多讲述CSRF防御的文章中,都要求使用HTTPPOST进行防御,但实际上POST本身并不足以对抗CSRF,因为POST也是可以自动提交的。但是POST的使用,对于保护token有着积极的意义,而securitytoken的私密性(不可预测性原则),是防御CSRF攻击的基础。对于Web框架来说,可以自动地在所有涉及POST的代码中添加token,这些地方包括所有的form表单、所有的AjaxPOST请求等。完整的CSRF防御方案,对于Web框架来说有以下几处地方需要改动。

(1)在Session中绑定token。如果不能保存到服务器端Session中,则可以替代为保存到Cookie里。

(2)在form表单中自动填入token字段,比如<inputtype=hiddenname=”anti—csrf_token”< p="">

value="$token"/>。

(3)在Ajax请求中自动添加token,这可能需要已有的Ajax封装实现的支持。

(4)在服务器端对比POST提交参数的token与Session中绑定的token是否一致,以验证CSRF攻击。

在Rails中,要做到这一切非常简单,只需要在ApplicationController中增加一行即可:

它将根据secret和服务器端的随机因子自动生成token,并自动添加到所有form和由Rails生成的Ajax请求中。通过框架实现的这一功能大大简化了程序员的开发工作。在Django中也有类似的功能,但是配置稍微要复杂点。首先,将django.middleware.csrfCsrfViewMiddleware添加到MIDDLEWARECLASSES中。

然后,在form表单的模板中添加token。

接下来,确认在View层的函数中使用了django.core.context_processors.csrf,如果使用的是RequestContext,则默认已经使用了,否则需要手动添加。

这样就配置成功了,可以享受CSRF防御的效果了。在Ajax请求中,一般是插入一个包含了token的HTTP头使用HTTP头是为了防止token泄密,因为一般的无法获取到HTTP头的信息,但是在存在一些跨域漏洞时可能会出现例外。下面是一个在Ajax中添加自定义token的例子。

在SpringMVC以及一些其他的流行Web框架中,并没有直接提供针对CSRF的保护,因此这些功能需要自己实现。12.4HTTPHeaders管理在Web框架中,可以对HTTP头进行全局化的处理,因此一些基于HTTP头的安全方案可以很好地实施。比如针对HTTP返回头的CRLF注入(攻击原理细节请参考“注入攻击”一章),因为HTTP头实际上可以看成是key-value对,比如:

因此对抗CRLF的方案只需要在“value”中编码所有的YrYn即可。这里没有提到在“key”中编码\r\n,是因为让用户能够控制“key”是极其危险的事情,在任何情况下都不应该使其发生。类似的,针对30X返回号的HTTPResponse,浏览器将会跳转到Location指定的URL,攻击者往往利用此类功能实施钓鱼或诈骗。

因此,对于框架来说,管理好跳转目的地址是很有必要的。一般来说,可以在两个地方做这件事情:

(1)如果Web框架提供统一的跳转函数,则可以在跳转函数内部实现一个白名单,指定跳转地址只能在白名单中;

(2)另一种解决方式是控制HTTP的Location字段,限制Location的值只能是哪些地址,也能起到同样的效果,其本质还是白名单。有很多与安全相关的Headers,也可以统一在Web框架中配置。比如用来对抗Clickjacking的X-Frame-Options,需要在页面的HTTPResponse中添加:

Web框架可以封装此功能,并提供页面配置。该HTTP头有三个可选的值:SAMEORIGIN、DENY、ALLOW-FROMorigin,适用于各种不同的场景。在前面的章节中,还曾提到Cookie的HttpOnlyFlag,它能告诉浏览器不要让访问该Cookie,在Session劫持等问题上有着积极的意义,而且成本非常小。但并不是所有的Web服务器、Web容器、脚本语言提供的API都支持设置HttpOnlyCookie,所以很多时候需要由框架实现一个功能:对所有的Cookie默认添加HttpOnly,不需要此功能的Cookie则单独在配置文件中列出。这将是非常有用的一项安全措施,在框架中实现的好处就是不用担心会有遗漏。就HttpOnlyCookie来说,它要求在所有服务器端设置该Cookie的地方都必须加上,这可能意味着很多不同的业务和页面,只要一个地方有遗漏,就会成为短板。当网站的业务复杂时,登录入口可能就有数十个,兼顾所有Set-Cookie页面会非常麻烦,因此在框架中解决将成为最好的方案。一般来说,框架会提供一个统一的设置Cookie函数,HttpOnly的功能可以在此函数中实现;如果没有这样的函数,则需要统一在HTTP返回头中配置实现。12.5数据持久层与SQL注入使用ORM(Object/RelationMapping)框架对SQL注入是有积极意义的。我们知道对抗SQL注入的最佳方式就是用“预编译绑定变量”。在实际解决SQL注入时,还有一个难点就是应用复杂后,代码数量庞大,难以把可能存在SQL注入的地方不遗漏地找出来,而ORM框架为我们发现问题提供了一个便捷的途径。以ORM框架ibatis举例,它是基于sqlmap的,生成的SQL语句都结构化地写在XML文件中。ibatis支持动态SQL,可以在SQL语句中插入动态变量:$value$,如果用户能够控制这个变量,则会存在一个SQL注入的漏洞。

而静态变量#value#则是安全的,因此在使用ibatis时,只需要搜索所有的sqlmap文件中是否包含动态变量即可。当业务需要使用动态SQL时,可以作为特例处理,比如在上层的代码逻辑中针对该变量进行严格的控制,以保证不会发生注入问题。而在Django中,做法则更简单,Django提供的DatabaseAPI,默认已经将所有输入进行了SQL转义,比如:

其最终效果类似于:

使用Web框架提供的功能,在代码风格上更加统一,也更利于代码审计。12.6还能想到什么除了上面讲到的几点外,在框架中还能实现什么安全方案呢?其实选择是很多的,凡是在Web框架中可能实现的安全方案,只要对性能没有太大的损耗,都应该考虑实施。比如文件上传功能,如果应用实现有问题,可能就会成为严重的漏洞。若是由每个业务单独实现文件上传功能,其设计和代码都会存在差异,复杂情况也会导致安全问题难以控制。但如果在Web框架中能为文件上传功能提供一个足够安全的二方库或者函数(具体可参考“文件上传漏洞”一章),就可以为业务线的开发者解决很多问题,让程序员可以把精力和重点放在功能实现上。SpringSecurity为SpringMVC的用户提供了许多安全功能,比如基于URL的访问控制、加密方法、证书支持、OpenID支持等。但SpringSecurity尚缺乏诸如XSS、CSRF等问题的解决方案。在设计整体安全方案时,比较科学的方法是按照本书第1章中所列举的过程来进行——首先建立威胁模型,然后再判断哪些威胁是可以在框架中得到解决的。在设计Web框架安全解决方案时,还需要保存好安全检查的日志。在设计安全逻辑时也需要考虑到日志的记录比如发生XSS攻击时,可以记录下攻击者的IP、时间、UserAgent、目标URL、用户名等信息。这些日志,对于后期建立攻击事件分析、入侵分析都是有积极意义的。当然,开启日志也会造成一定的性能损失,因此在设计时,需要考虑日志记录行为的频繁程度,并尽可能避免误报。在设计Web框架安全时,还需要与时俱进。当新的威胁出现时,应当及时完成对应的防御方案,如此一个Web框架才具有生命力。而一些Oday漏洞,也有可能通过“虚拟补丁”的方式在框架层面解决,因为Web框架就像是一层外衣,为Web应用提供了足够的保护和控制力。

Web框架自身安全

前面几节讲的都是在Web框架中实现安全方案,但Web框架本身也可能会出现漏洞,只要是程序,就可能出现bug。但是开发框架由于其本身的特殊性,一般网站出于稳定的考虑不会对这个基础设施频繁升级,因此开发框架的漏洞可能不会得到及时的修补,但由此引发的后果却会很严重。下面讲到的几个漏洞,都是一些流行的Web开发框架曾经出现过的严重漏洞。研究这些案例,可以帮助我们更好地理解框架安全,在使用开发框架时更加的小心,同时让我们不要迷信于开发框架的权威。12.7.1Struts2命令执行漏洞2010年7月9日,安全研究者公布了Struts2-个远程执行代码的漏洞(CVE-2010-1870),严格来说,这其实是XWork的漏洞,因为Struts2的核心使用的是WebWork,而WebWork又是使用XWork来处理action的。这个漏洞的细节描述公布在exploit-db2上。

在这里简单摘述如下:

XWork通过getters/setters方法从HTTP的参数中获取对应action的名称,这个过程是基于OGNL(ObjectGraphNavigationLanguage)的。OGNL是怎么处理的呢?如下:

会被转换为:

这个过程是由Parameterslnterceptor调用ValueStack.setValue()完成的,它的参数是用户可控的,由HTTP参数传入。OGNL的功能较为强大,远程执行代码也正是利用了它的功能。

由于参数完全是用户可控的,所以XWork出于安全的目的,增加了两个方法用以阻止代码执行。

但这两个方法可以被覆盖,从而导致代码执行。

Parameterslnterceptor是不允许参数名称中有≠}的,因为OGNL中的许多预定义变量也是以#表示的。

可是攻击者在过去找到了这样的方法(bug编号XW-641):使用Yu0023来代替#,这是#的十六进制编码,从而构造出可以远程执行的攻击payload。

最终导致代码执行成功。12.7.2Struts2的问题补丁Struts2官方目前公布了几个安全补丁3:

blob.png

但深入其细节不难发现,补丁提交者对于安全的理解是非常粗浅的。以s2002的漏洞修补为例,这是一个XSS漏洞,发现者当时提交给官方的POC只是构造了SCRIPT标签。

我们看看当时官方是如何修补的:

blob.png

新增的修改代码:

blob.png

可以看到,只是简单地替换掉

相关推荐