XSS跨站脚本攻击
前言
Web安全一直是老生常谈的一个问题,下面就讲解关于XSS的内容
关键词:XSS的由来、XSS类型、XSS预防手段、XSS检测。
XSS是什么?
Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击,攻击者通过在目标网站上面注入恶意的脚本,让其在用户的浏览器上面运行。利用这些脚本就能轻易获取到用户的cookie,sessionID等,产生进一步安全威胁。
在命名上为了和css区分,改为了xss
在处理输入的时候,以下内容不可信:
- 来自用户的 UGC 信息
- 来自第三方的链接
- URL 参数
- POST 参数
- Referer (可能来自不可信的来源)
- Cookie (可能来自其他子域注入)
XSS分类
根据攻击来源,可以分为存储型,反射型和DOM型
存储型
攻击流程:
- 攻击者将恶意代码提交到网站的数据库中
- 用户响应服务端的恶意代码
- 恶意代码窃取用户敏感信息发送到攻击者网站,或冒充用户行为执行操作。
常见场景:
论坛发帖,商品评论,用户私信
反射型
攻击流程:
- 攻击者构造出特殊的URL里面包含恶意代码
- 用户被诱导点击特殊URL,执行恶意操作。
反射型和存储型的区别在于,一个是存在URL里面,一个是存在数据库里面。
常见场景:
网站搜索,跳转。
需要用户主动去点击,所以攻击者常有多种手段诱导用户点击。
另外注意POST也可以诱发反射型XSS,不过需要构建表单提交界面所以少见。
DOM型
攻击流程:
- 攻击者构造出特殊的URL里面包含恶意代码
- 用户打开带有恶意代码的URL
- 浏览器执行恶意代码
DOM型和前两种的区别在于,DOM型的攻击是由浏览器端完成的,属于js自身的漏洞,其他两种都是服务器端的安全漏洞
XSS攻击预防
上述了解到,XSS攻击有两种要素
- 攻击者提交恶意代码
- 浏览器执行恶意代码
针对第一个要素,有以下的防范措施
输入过滤
前端过滤—>后端?
不可行 绕过过滤,直接构造请求就可以提交了
数据库输入过滤—>前端?
不一定可行。
eg 输入5<7
在写入数据库前被转义成5 < 7
问题是我们在提交阶段不清楚内容要输出到哪里。也就是意味着我们的内容
- 用户提交到前端和客户端,经过了
escapeHTML()
转义,客户端就乱码了 - 前端不同地方需要的编码也不一样
- 当
5 < 7
作为 HTML 拼接页面时,可以正常显示: - 当作为AJAX返回的时候,这个内容要经过处理才可以显示。
- 当
所以在输入侧是可以解决这个问题但是有乱码和不确定性的问题,导致工程量加大。
输入侧不行就要通过浏览器来防范
- 防止HTML注入
- 防止JS执行的时候执行恶意代码
预防存储型和反射型
存储型和反射型都是在服务端取出恶意代码后插入到HTML里,攻击者将数据内嵌到代码中,被浏览器执行
解决方法
- 纯前端渲染,数据和代码隔离
- 对HTML做充分转义
纯前端渲染
过程:
- 加载静态HTML,没有任何业务数据
- 浏览器执行HTML中的js代码
- 数据通过AJAX加载
关键在于,我们要告诉浏览器下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。
注意避免DOM型XSS 比如onload事件和herf中的javascript:xxx
在很多内部、管理系统中,采用纯前端渲染是非常合适的。但对于性能要求高,或有 SEO 需求的页面,我们仍然要面对拼接 HTML 的问题。
转义HTML
利用转义库来进行防护
预防DOM型XSS
不要用innerHTML
,document.write
系列代码
用textContent
,setAttribute
系列代码
在vue/react中且不使用v-html/dangerouslySetInnerHTML
功能,就在前端 render 阶段避免 innerHTML、outerHTML
的 XSS 隐患。
DOM 中的内联事件监听器,如 location
、onclick
、onerror
、onload
、onmouseover
等,<a>
标签的 href
属性,JavaScript 的 eval()
、setTimeout()
、setInterval()
等
都能进行xss攻击,注意防范字符串拼接
1 | <!-- 内联事件监听器中包含恶意代码 --> |
其他防范
以下是一些通用方案
Content Security Policy
严格的 CSP 在 XSS 的防范中可以起到以下的作用:
- 禁止加载外域代码,防止复杂的攻击逻辑。
- 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
- 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。
- 禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。
- 合理使用上报可以及时发现 XSS,利于尽快修复问题。
关于 CSP 的详情,请关注前端安全系列后续的文章。
输入内容长度控制
对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度。
其他安全措施
- HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。
- 验证码:防止脚本冒充用户提交危险操作。
XSS检测
- 使用通用 XSS 攻击字符串手动检测 XSS 漏洞。
- 使用扫描工具自动检测 XSS 漏洞。
在Unleashing an Ultimate XSS Polyglot一文中,小明发现了这么一个字符串:1
https://github.com/0xsobky/HackVault/wiki/Unleashing-an-Ultimate-XSS-Polyglot
它能够检测到存在于 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等多种上下文中的 XSS 漏洞,也能检测 eval()、setTimeout()、setInterval()、Function()、innerHTML、document.write()
等 DOM 型 XSS 漏洞,并且能绕过一些 XSS 过滤器。
小明只要在网站的各输入框中提交这个字符串,或者把它拼接到 URL 参数上,就可以进行检测了。
1 | 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 漏洞,例如 Arachni、Mozilla HTTP Observatory、w3af
等。
总结
我们回到最开始提出的问题,相信同学们已经有了答案:
XSS 防范是后端 RD 的责任,后端 RD 应该在所有用户提交数据的接口,对敏感字符进行转义,才能进行下一步操作。
不正确。因为:
防范存储型和反射型 XSS 是后端 RD 的责任。而 DOM 型 XSS 攻击不发生在后端,是前端 RD 的责任。防范 XSS 是需要后端 RD 和前端 RD 共同参与的系统工程。
转义应该在输出 HTML 时进行,而不是在提交用户输入时。
所有要插入到页面上的数据,都要通过一个敏感字符过滤函数的转义,过滤掉通用的敏感字符后,就可以插入到页面中。
不正确。
不同的上下文,如 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等,所需要的转义规则不一致。
业务 RD 需要选取合适的转义库,并针对不同的上下文调用不同的转义规则。
整体的 XSS 防范是非常复杂和繁琐的,我们不仅需要在全部需要转义的位置,对数据进行对应的转义。而且要防止多余和错误的转义,避免正常的用户输入出现乱码。
虽然很难通过技术手段完全避免 XSS,但我们可以总结以下原则减少漏洞的产生:
- 利用模板引擎
开启模板引擎自带的 HTML 转义功能。例如:
在 ejs 中,尽量使用<%= data %>
而不是<%- data %>
;
在 doT.js 中,尽量使用{{! data }` 而不是` {{= data }`; 在 FreeMarker 中,确保引擎版本高于 2.3.24,并且选择正确的 `freemarker.core.OutputFormat`。 * 避免内联事件 尽量不要使用` onLoad="onload('{{data}}')"
、onClick="go('{{action}}')"
这种拼接内联事件的写法。在 JavaScript 中通过.addEventlistener()
事件绑定会更安全。 - 避免拼接 HTML
前端采用拼接 HTML 的方法比较危险,如果框架允许,使用createElement、setAttribute
之类的方法实现。或者采用比较成熟的渲染框架,如 Vue/React 等。 - 时刻保持警惕
在插入位置为 DOM 属性、链接等位置时,要打起精神,严加防范。 - 增加攻击难度,降低攻击后果
通过 CSP、输入长度配置、接口安全措施等方法,增加攻击的难度,降低攻击的后果。 - 主动检测和发现
可使用 XSS 攻击字符串和自动扫描工具寻找潜在的 XSS 漏洞。