【基本功】 前端安全系列之一:如何防止XSS攻击?

640?wx_fmt=png

总第287篇

2018年 第79篇

【基本功】 前端安全系列之一:如何防止XSS攻击?_第1张图片

当当当当,我是美团技术团队的程序员鼓励师美美~“基本功”专栏又来新文章了,这次是一个系列,一起来学习前端安全的那些事。我们将不断梳理常见的前端安全问题以及对应的解决方案,希望可以帮助前端同学在日常开发中不断预防和修复安全漏洞,Enjoy Reading!


背景


随着互联网的高速发展,信息安全问题已经成为企业最为关注的焦点之一,而前端又是引发企业安全问题的高危据点。在移动互联网时代,前端人员除了传统的 XSS、CSRF 等安全问题之外,又时常遭遇网络劫持、非法调用 Hybrid API 等新型安全问题。当然,浏览器自身也在不断在进化和发展,不断引入 CSP、Same-Site Cookies 等新技术来增强安全性,但是仍存在很多潜在的威胁,这需要前端技术人员不断进行“查漏补缺”。

【基本功】 前端安全系列之一:如何防止XSS攻击?_第2张图片

前端安全

近几年,美团业务高速发展,前端随之面临很多安全挑战,因此积累了大量的实践经验。我们梳理了常见的前端安全问题以及对应的解决方案,将会做成一个系列,希望可以帮助前端同学在日常开发中不断预防和修复安全漏洞。本文是该系列的第一篇。

今天我们讲解一下 XSS ,主要包括:

  1. XSS 攻击的介绍

  2. XSS 攻击的分类

  3. XSS 攻击的预防和检测

  4. XSS 攻击的总结

  5. XSS 攻击案例


XSS攻击的介绍


在开始本文之前,我们先提出一个问题,请判断以下两个说法是否正确:


  1. XSS 防范是后端 RD (研发人员)的责任,后端 RD 应该在所有用户提交数据的接口,对敏感字符进行转义,才能进行下一步操作。

  2. 所有要插入到页面上的数据,都要通过一个敏感字符过滤函数的转义,过滤掉通用的敏感字符后,就可以插入到页面中。

如果你还不能确定答案,那么可以带着这些问题向下看,我们将逐步拆解问题。

XSS 漏洞的发生和修复


XSS 攻击是页面被注入了恶意的代码,为了更形象的介绍,我们用发生在小明同学身边的事例来进行说明。


一个案例


某天,公司需要一个搜索页面,根据 URL 参数决定关键词的内容。小明很快把页面写好并且上线。代码如下:


">
  您搜索的关键词是:<%= getParameter("keyword") %>

然而,在上线后不久,小明就接到了安全组发来的一个神秘链接:

http://xxx/search?keyword=">

小明带着一种不祥的预感点开了这个链接[请勿模仿,确认安全的链接才能点开]。果然,页面中弹出了写着"XSS"的对话框。

可恶,中招了!小明眉头一皱,发现了其中的奥秘:

当浏览器请求 http://xxx/search?keyword="> 时,服务端会解析出请求参数 keyword,得到 ">,拼接到 HTML 中返回给浏览器。形成了如下的 HTML:

">
  您搜索的关键词是:">

浏览器无法分辨出  是恶意代码,因而将其执行。

这里不仅仅 div 的内容被注入了,而且 input 的 value 属性也被注入, alert 会弹出两次。

面对这种情况,我们应该如何进行防范呢?

其实,这只是浏览器把用户的输入当成了脚本进行了执行。那么只要告诉浏览器这段内容是文本就可以了。

聪明的小明很快找到解决方法,把这个漏洞修复:

">
  您搜索的关键词是:<%= escapeHTML(getParameter("keyword")) %>

escapeHTML() 按照如下规则进行转义:

【基本功】 前端安全系列之一:如何防止XSS攻击?_第3张图片

经过了转义函数的处理后,最终浏览器接收到的响应为:

  您搜索的关键词是:"><script>alert('XSS');</script>

恶意代码都被转义,不再被浏览器执行,而且搜索词能够完美的在页面显示出来。

通过这个事件,小明学习到了如下知识:


注意特殊的 HTML 属性、JavaScript API


自从上次事件之后,小明会小心的把插入到页面中的数据进行转义。而且他还发现了大部分模板都带有的转义配置,让所有插入到页面中的数据都默认进行转义。这样就不怕不小心漏掉未转义的变量啦,于是小明的工作又渐渐变得轻松起来。

但是,作为导演的我,不可能让小明这么简单、开心地改 Bug 。

不久,小明又收到安全组的神秘链接:http://xxx/?redirect_to=javascript:alert('XSS')。小明不敢大意,赶忙点开页面。然而,页面并没有自动弹出万恶的“XSS”。

小明打开对应页面的源码,发现有以下内容:

">跳转...

这段代码,当攻击 URL 为 http://xxx/?redirect_to=javascript:alert('XSS'),服务端响应就成了:

跳转...

虽然代码不会立即执行,但一旦用户点击 a 标签时,浏览器会就会弹出alert('xss')

可恶,又失策了…

在这里,用户的数据并没有在位置上突破我们的限制,仍然是正确的 href 属性。但其内容并不是我们所预期的类型。

原来不仅仅是特殊字符,连 javascript: 这样的字符串如果出现在特定的位置也会引发 XSS 攻击。

小明眉头一皱,想到了解决办法:

// 禁止 URL 以 "javascript:" 开头xss = getParameter("redirect_to").startsWith('javascript:');if (!xss) {  ">    跳转...  } else {      跳转...  }

只要 URL 的开头不是 javascript:,就安全了吧?

安全组随手又扔了一个连接:http://xxx/?redirect_to=jAvascRipt:alert('XSS')

这也能执行?…..好吧,浏览器就是这么强大。

小明欲哭无泪,在判断 URL 开头是否为 javascript: 时,先把用户输入转成了小写,然后再进行比对。

不过,所谓“道高一尺,魔高一丈”。面对小明的防护策略,安全组就构造了这样一个连接:

http://xxx/?redirect_to=%20javascript:alert('XSS')

%20javascript:alert('XSS') 经过 URL 解析后变成 javascript:alert('XSS'),这个字符串以空格开头。这样攻击者可以绕过后端的关键词规则,又成功的完成了注入。

最终,小明选择了白名单的方法,彻底解决了这个漏洞:

// 根据项目情况进行过滤,禁止掉 "javascript:" 链接、非法 scheme 等allowSchemes = ["http", "https"];valid = isValid(getParameter("redirect_to"), allowSchemes);if (valid) {  ">    跳转...  } else {      跳转...  }

通过这个事件,小明学习到了如下知识:


根据上下文采用不同的转义规则


某天,小明为了加快网页的加载速度,把一个数据通过 JSON 的方式内联到 HTML 中:


插入 JSON 的地方不能使用 escapeHTML(),因为转义 " 后,JSON 格式会被破坏。

但安全组又发现有漏洞,原来这样内联 JSON 也是不安全的:


于是我们又要实现一个 escapeEmbedJSON() 函数,对内联 JSON 进行转义。转义规则如下:

【基本功】 前端安全系列之一:如何防止XSS攻击?_第4张图片


修复后的代码如下:

');">  click me&order=1#top">">'>  link

可见,HTML 的编码是十分复杂的,在不同的上下文里要使用相应的转义规则。

预防 DOM 型 XSS 攻击


DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。

在使用 .innerHTML.outerHTMLdocument.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent.setAttribute() 等。

如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTMLouterHTML 的 XSS 隐患。

DOM 中的内联事件监听器,如 locationonclickonerroronloadonmouseover 等, 标签的 href 属性,JavaScript 的 eval()setTimeout()setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。

1

如果项目中有用到这些的话,一定要避免在字符串中拼接不可信数据。

其他XSS防范措施


虽然在渲染页面和执行 JavaScript 时,通过谨慎的转义可以防止 XSS 的发生,但完全依靠开发的谨慎仍然是不够的。以下介绍一些通用的方案,可以降低 XSS 带来的风险和后果。


Content Security Policy


严格的 CSP 在 XSS 的防范中可以起到以下的作用:


关于 CSP 的详情,请关注前端安全系列后续的文章。

输入内容长度控制


对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度。


其他安全措施



XSS的检测

上述经历让小明收获颇丰,他也学会了如何去预防和修复 XSS 漏洞,在日常开发中也具备了相关的安全意识。但对于已经上线的代码,如何去检测其中有没有 XSS 漏洞呢?

经过一番搜索,小明找到了两个方法:

  1. 使用通用 XSS 攻击字符串手动检测 XSS 漏洞。

  2. 使用扫描工具自动检测 XSS 漏洞。

Unleashing an Ultimate XSS Polyglot一文中,小明发现了这么一个字符串:

jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//\x3csVg/\x3e

它能够检测到存在于 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等多种上下文中的 XSS 漏洞,也能检测 eval()setTimeout()setInterval()Function()innerHTMLdocument.write() 等 DOM 型 XSS 漏洞,并且能绕过一些 XSS 过滤器。

小明只要在网站的各输入框中提交这个字符串,或者把它拼接到 URL 参数上,就可以进行检测了。

http://xxx/search?keyword=jaVasCript%3A%2F*-%2F*%60%2F*%60%2F*%27%2F*%22%2F**%2F(%2F*%20*%2FoNcliCk%3Dalert()%20)%2F%2F%250D%250A%250d%250a%2F%2F%3C%2FstYle%2F%3C%2FtitLe%2F%3C%2FteXtarEa%2F%3C%2FscRipt%2F--!%3E%3CsVg%2F%3CsVg%2FoNloAd%3Dalert()%2F%2F%3E%3E

除了手动检测之外,还可以使用自动扫描工具寻找 XSS 漏洞,例如 ArachniMozilla HTTP Observatoryw3af 等。

XSS攻击的总结


我们回到最开始提出的问题,相信同学们已经有了答案:


1. XSS 防范是后端 RD 的责任,后端 RD 应该在所有用户提交数据的接口,对敏感字符进行转义,才能进行下一步操作。


不正确。因为:

防范存储型和反射型 XSS 是后端 RD 的责任。而 DOM 型 XSS 攻击不发生在后端,是前端 RD 的责任。防范 XSS 是需要后端 RD 和前端 RD 共同参与的系统工程。

转义应该在输出 HTML 时进行,而不是在提交用户输入时。

2. 所有要插入到页面上的数据,都要通过一个敏感字符过滤函数的转义,过滤掉通用的敏感字符后,就可以插入到页面了。

不正确。

不同的上下文,如 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等,所需要的转义规则不一致。

业务 RD 需要选取合适的转义库,并针对不同的上下文调用不同的转义规则。

整体的 XSS 防范是非常复杂和繁琐的,我们不仅需要在全部需要转义的位置,对数据进行对应的转义。而且要防止多余和错误的转义,避免正常的用户输入出现乱码。

虽然很难通过技术手段完全避免 XSS,但我们可以总结以下原则减少漏洞的产生:


XSS攻击案例


QQ 邮箱 m.exmail.qq.com 域名反射型 XSS 漏洞

攻击者发现 http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb 这个 URL 的参数 uindomain 未经转义直接输出到 HTML 中。

于是攻击者构建出一个 URL,并引导用户去点击:
http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb%26quot%3B%3Breturn+false%3B%26quot%3B%26lt%3B%2Fscript%26gt%3B%26lt%3Bscript%26gt%3Balert(document.cookie)%26lt%3B%2Fscript%26gt%3B

用户点击这个 URL 时,服务端取出 URL 参数,拼接到 HTML 响应中:

"+"...

浏览器接收到响应后就会执行 alert(document.cookie),攻击者通过 JavaScript 即可窃取当前用户在 QQ 邮箱域名下的 Cookie ,进而危害数据安全。

新浪微博名人堂反射型 XSS 漏洞


攻击者发现 http://weibo.com/pub/star/g/xyyyd 这个 URL 的内容未经过滤直接输出到 HTML 中。

于是攻击者构建出一个 URL,然后诱导用户去点击:

http://weibo.com/pub/star/g/xyyyd">

用户点击这个 URL 时,服务端取出请求 URL,拼接到 HTML 响应中:

  • ">按分类检索
  • 浏览器接收到响应后就会加载执行恶意脚本 //xxxx.cn/image/t.js,在恶意脚本中利用用户的登录状态进行关注、发微博、发私信等操作,发出的微博和私信可再带上攻击 URL,诱导更多人点击,不断放大攻击范围。这种窃用受害者身份发布恶意内容,层层放大攻击范围的方式,被称为“XSS 蠕虫”。

    XSS攻击扩展阅读:Automatic Context-Aware Escaping

    上文我们说到:

    1. 合适的 HTML 转义可以有效避免 XSS 漏洞。

    2. 完善的转义库需要针对上下文制定多种规则,例如 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等等。

    3. 业务 RD 需要根据每个插入点所处的上下文,选取不同的转义规则。

    通常,转义库是不能判断插入点上下文的(Not Context-Aware),实施转义规则的责任就落到了业务 RD 身上,需要每个业务 RD 都充分理解 XSS 的各种情况,并且需要保证每一个插入点使用了正确的转义规则。

    这种机制工作量大,全靠人工保证,很容易造成 XSS 漏洞,安全人员也很难发现隐患。

    2009年,Google 提出了一个概念叫做:Automatic Context-Aware Escaping

    所谓 Context-Aware,就是说模板引擎在解析模板字符串的时候,就解析模板语法,分析出每个插入点所处的上下文,据此自动选用不同的转义规则。这样就减轻了业务 RD 的工作负担,也减少了人为带来的疏漏。

    在一个支持 Automatic Context-Aware Escaping 的模板引擎里,业务 RD 可以这样定义模板,而无需手动实施转义规则:

              {{.title}}        {{.content}}  

    模板引擎经过解析后,得知三个插入点所处的上下文,自动选用相应的转义规则:

              {{.title | htmlescaper}}        {{.content | htmlescaper}}  

    目前已经支持 Automatic Context-Aware Escaping 的模板引擎有:


    课后作业:XSS攻击小游戏


    以下是几个 XSS 攻击小游戏,开发者在网站上故意留下了一些常见的 XSS 漏洞。玩家在网页上提交相应的输入,完成 XSS 攻击即可通关。

    在玩游戏的过程中,请各位读者仔细思考和回顾本文内容,加深对 XSS 攻击的理解。

    alert(1) to win
    prompt(1) to win
    XSS game

    参考文献



    下期预告

    前端安全系列文章将对 XSS、CSRF、网络劫持、Hybrid 安全等安全议题展开论述。下期我们要讨论的是 CSRF 攻击,敬请期待。

    作者介绍


    李阳,美团点评前端工程师。2016年加入美团点评,负责美团外卖 Hybrid 页面性能优化相关工作。


    扫码加入美团前端安全技术交流群,跟作者零距离交流。如群已满,请加美美同学的微信(微信号:MTDPtech01),回复:前端安全,美美会自动拉你进群。


    【基本功】 前端安全系列之一:如何防止XSS攻击?_第7张图片


    ----------  END  ----------


    也许你还想看


    互联网企业:如何建设数据安全体系?

    互联网公司数据安全保护新探索

    镣铐之舞:美团安全工程师Black Hat USA演讲


    640?wx_fmt=png


    你可能感兴趣的:(【基本功】 前端安全系列之一:如何防止XSS攻击?)