• 本文作者: 冷风
  • |
  • 2014年4月17日
  • |
  • 原创翻译
  • |

deDacota:通过自动化分离数据和代码防御服务端XSS漏洞

译者:天融信阿尔法实验室   宋君易

【原创翻译】

原文题目:deDacota: Toward Preventing Server-Side XSS via Automatic Code and Data Separation

原文地址:http://cs.ucsb.edu/~adoupe/static/dedacota-ccs2013.pdf

 

Web应用持续受到各种方式的攻击。跨站脚本漏洞是最流行的漏洞之一,产生原因是不被信任的恶意数据通过浏览器提交给应用,并且被解释为程序代码,最终被浏览器执行,造成攻击。本文介绍一种方法可以高效、自动重写应用代码,实现分离网页代码和内联JS脚本,应用CSP策略避免跨站脚本漏洞的产生。

一、跨站脚本漏洞:

跨站脚本攻击(Cross-site scripting,通常简称为XSS)发生在客户端,可被用于进行窃取隐私、钓鱼欺骗、偷取密码、传播恶意代码等攻击行为。恶意的攻击者将对客户端有危害的代码放到服务器上作为一个网页内容, 使得其他网站用户在观看此网页时,这些代码注入到了用户的浏览器中执行,导致用户受到攻击。一般而言,利用跨站脚本攻击,攻击者可窃会话COOKIE从而窃取网站用户的隐私,包括密码。

跨站漏洞从利用方式来说分为反射性漏洞和存储型漏洞,发生原因是服务端对用户输入的数据没有进行适当的过滤,导致发送给浏览器的网页携带恶意脚本,从而产生漏洞。从跨站脚本产生的位置不同,又可以分为客户端跨站漏洞和服务端跨站漏洞。客户端跨站漏洞的产生不依赖于用户提交到服务器的数据,也称之为DOM-XSS漏洞。而服务端跨站漏洞依赖于用户提交到服务器的数据,本文主要的研究内容就是如何通过分离网页代码和内联JS脚本,并应用CSP的方式阻止服务端XSS攻击。

二、内容安全策略(CSP)

为了缓解很大一部分潜在的跨站脚本问题,浏览器的扩展程序系统引入了内容安全策略(CSP)的一般概念。这将引入一些相当严格的策略,会使扩展程序在默认情况下更加安全,开发者可以创建并强制应用一些规则,管理网站允许加载的内容。

CSP 以白名单的机制对网站加载或执行的资源起作用。在网页中,这样的策略通过 HTTP 头信息或者 meta 元素定义。CSP虽然提供了强大的安全保护,但是他也造成了如下问题:Eval及相关函数被禁用、内嵌的JavaScript代码将不会执行、只能通过白名单来加载远程脚本。这些问题阻碍CSP的普及,如果要使用CSP技术保护自己的网站,开发者就不得不花费大量时间分离内嵌的JavaScript代码和做一些调整,本文研究的技术可以自动化分离网页代码和内联JS脚本,帮助网站支持CSP技术从而避免潜在的跨站攻击。

三、内联JS分离技术

内联JS分离技术包含三个主要步骤:

1、 静态分析网页代码的HTML输出。

2、 从HTML页面抽取出全部内联JavaScript代码。

3、 重写应用代码以便于适应分离的内联JavaScript代码。

这项分离技术也有一些局限性,比如不能应用于混淆、加密后的代码,只针对服务端XSS漏洞,对于基于DOM的XSS漏洞完全无效。所以这项技术对于XSS漏洞来说并不是万灵药,在与CSP策略结合后只解决大部分XSS的问题,减轻了开发人员的工作量,如果想要完全防御XSS漏洞,只需针对动态产生的JS代码和DOM-XSS做过滤和检测。

以ASP.NET网页表单页面为例。

1

图一: 简单的ASP.NET代码

首先需要确定应用程序是如何输出内容到页面:

<%%>之前的内容会直接输出到HTML页面,并且他们都是C#代码。

<%=代表这个C#代码是变量,在程序运行时其内容会被决定,并输出到HTML页面。

从图一中可以看出,第二行定义的是常量,第三行定义的是变量,在第七行username这个变量就被输出到HTML中,作为内联JS脚本执行。

2

图二:被C#编译器编译后的图一中代码。

在经过C#编译后的代码中,Write函数代替了<%%>标签作为输出函数,可以将内容写到页面当中。而var username代替了<%=标记,通过第八行的Wirit(this.username)将变量内容输出到页面当中。

在第一个阶段有两个关键步骤。第一个是确定将要输出到页面的内容。第二个是确定输出函数被调用的顺序。

我们使用points-to分析算法静态分析源代码来解决这个问题。简单的说points-to算法可以确定指针与变量地址的对应关系,最初被应用于C代码的静态分析当中。这个算法可以确定变量内存储的是常量字符串还是变量。通过它就可以判断Write函数将要输出的内容是常量还是变量,也就确定了那些地方是我们需要分离的数据。

为了确定Write函数被调用的顺序,我们使用了标准的控制流程图,可以准确确定函数调用顺序。这个控制流程图是一个无回路有向图(DAG),任何从根节点出发的叶子节点都代表一个可能的页面输出。

3

图三:程序控制流程图

图中每个实线圈出的节点都是直接输出常量到页面中,虚线圈出的节点是变量输出到节点,该节点值由程序动态决定。

在第二个阶段,我们使用之前生成的有向图精确地分离内联Javascript代码。从根节点出发到达的每个叶子节点都代表一个潜在的页面输出。我们使用一个递归算法递归全部的路径,对每个输出字符串进行识别,从而分离出Javascript代码。在这个过程中可能产生路径爆炸问题,因为即使一些很简单的代码也可能产生大量的逻辑路径,导致遍历失败。我们通过两种方法解决这个问题,第一个就是忽略注释代码。第二个是在每一对Javascript标记中处理不同路径,减少一次性处理的范围。当全部的内联Javascript代码都被识别之后,我们将结果传入下一个阶段——代码重写。

第三个阶段将全部内联Javascript代码与HTML代码分离,将他们存入外部Javascript文件中。原来写Javascript代码的地方被替换为<script src=”External.js”></script>这种形式。程序依照以下流程来重写应用:首先检查Wirte函数中要输出的内容,如果包含Javascript的开始标记,就使用SESSION标记指明这个地方含有内联Javascript代码,重写功能就会先移除这个Javascript标记然后将JS代码内容存入Session缓冲区,并且保持SESSION标记是唯一的,当程序读取到JS结束标记后,删除这个标记,一个内联JS就完整的与代码分离了。之后这个过程一直重复,直到内联JS代码全部被存入Session缓冲区为。最后对每个session缓冲区区hash值,作为外部JS文件名称,以便程序调用。

4

图四:重写之后的代码。

执行完这些步骤我们已经可以完全分离了内联JS脚本,如果其中的脚本仅仅是静态产生的JS脚本,那么网站在应用CSP后就可以完全防御XSS攻击的威胁。但是内联JS脚本中还有动态产生的脚本,将这些脚本仅仅分离到外部JS文件不能防御XSS攻击。比如针对图一中的代码。如果输入username=””;alert(‘xss’)//;那么最终在浏览器执行的脚本会是<script> var username=””;alert(‘xss’)//”;></script>,依然产生跨站威胁。这种跨站威胁应用我们的处理流程是不能完全根除的,因为这是服务端跨站漏洞。但是我们为了减轻动态脚本产生XSS漏洞的可能性,采取了两个处理方法。第一个方法是标记并记录动态脚本,方便人工评估动态产生的脚本是否包含漏洞。第二个方法是定制相应的过滤方法,对动态输出的内容进行转义、过滤。这两个方法只能减轻动态JS脚本产生XSS漏洞的可能性,但不能完全杜绝,这也就是为什么本文主要针对服务端XSS漏洞。

四、评估与实验

实验主要评估该方法的三个方面,第一个是抵御XSS攻击的效果,第二个是重写后的应用源代码是否会产生异常,第三个是重写应用代码对应用性能的影响。

实验选取了以下应用作为实验对象:

5

图五:选取的实验对象。

实验对象选取的条件主要是以下三点:

1、 应用程序开源。

2、 应用包含已知的XSS漏洞。

3、 应用代码没有经过混淆、加密。

因为ASP.NET开源项目很少,所以最后满足条件的项目只有这6个。这些项目有MVC框架的也有非MVC框架,并包含一定量的代码行数(LOC),满足我们的实验条件。

五、    实验结果:

当内联JS代码分离完成及应用了CSP策略后,我们手工的使用EXP对网站进行攻击,原始应用都产生了漏洞,但经过流程处理后的应用都防御了攻击。为了检测处理后的网站时候会产生错误,我们采取手工测试的方法,对每个页面的功能进行检测,最后发现没有任何错误产生。在图六中我们可以看到不同应用的JS分布。安全动态JS指程序识别出动态生成的JS输出内容并进行清理转义,已经安全。不安全的动态JS是我们不能正确识别的,仅进行记录并分离到外部JS文件中。图中(4)标记代表在ASP.NET框架编译程序时会自动产生4个JS脚本,这是我们不可控的元素,不作考虑。最后我们测试了应用性能,结果显示内联JS代码分离可根据内联JS代码使用数量的多少,适当减小页面代码,加速页面加载速度。对于内联JS使用较少的页面会产生反效果,但是不会产生较大影响。

6

图六:内联JS分离完成后的文件分布。

7

图七:内联JS分离完成后页面大小及响应时间对比。

六、总结与不足:

这项技术主要通过结合CSP策略来防御XSS攻击,根据我们的实验结果,该项技术已经达到一定的实际应用水平,可以抵御服务端XSS攻击并且不影响程序的执行结果和网站效率。

这项分离技术也存在一些局限性,比如不能应用于混淆、加密后的代码,只针对服务端XSS漏洞,对于基于DOM的XSS漏洞完全无效。虽然这个方法已经基本可以应用到实际场景当中,但是程序获得内联JS的方法还需要持续改进。目前对于复杂的框架模板比如PHP经常使用的框架Thinkphp,不修改原有程序就识别内联JS脚本存在困难。但是只要解决了输出是如何被应用创建的,和怎样重写应用这两个问题,那么这项技术就可以应用于该框架。对于不同的框架需要不同的定制,对框架通用性较差。

Written by 冷风

天融信阿尔法实验室攻防研究