跨站脚本(XSS, Cross Site Script)攻击指的是,攻击者可让某网站执行一段非法脚本。这种状况很常见,好比提交一个表单用于修改用户名,咱们能够在文本框中输入一些特殊字符,好比 <, >, ', "
等,检查一下用户名是否正确修改了。javascript
XSS 必定是由用户的输入引发的,不管是提交表单、仍是点击连接(参数)的方式,只要是对用户的输入不作任何转义就写到数据库,或者写到 html
,js
中,就颇有可能出错。举两个例子。css
假设须要显示一个新闻标题的列表,服务端渲染的话,用 jade
来实现的话也许是这样的html
h1 娱乐速递 ul each val in newslist li= val
newslist
就当是 ['新闻1', '新闻2', ...] 这样格式的数组,若是直接把内容迭代渲染到 html
上的话,一旦某个新闻标题有特殊字符,好比标题中刚好包含一个 <p>
标签,那么它就不会显示出来。前端
另外一个例子,用户在写博客,先不考虑实时保存吧,如今就仅仅须要预览一下,那么可能的代码就是java
var preview = document.getElementById('#preview'), title = document.getElementById('#blog-title'), content = document.getElementById('#blog-content'); preview.innerHTML = '<h1>' + title.value + '</h1>' + '<pre>' + content.value + '</pre>';
这里一样是把用户的输入直接显示在了 html
上,若是用户的输入中,正好输入了 </h1>
,把 <h1>
标签提早结束,而后再输入 <script>...</script>
就能够直接执行 js
代码了。数据库
XSS 的发生至少须要一个条件,就是这些非法的脚本必须得在浏览器中解析。数组
从一个请求发出开始,到浏览器显示内容,与 XSS 相关的有三个地方:URL、HTML、JavaScript。至于后台方面,它分两个功能,一个是将数据写到数据库,这时候也要对数据进行转义,但不是XSS的范畴,它更可能是防止数据破坏 SQL 语句的结构;另外一个是从数据库读取数据,直接生成 HTML 或者以 JSON 的方式传给前端,这些数据都必须转义后才能显示到浏览器中。浏览器
HTML 自己是一个文本文档,但在浏览器中却能够显现得花样百出,是由于不少字符对于浏览器来讲是有特殊含义的,好比在 <script>
中的内容,浏览器会作一些动画等等。那么对这些特殊字符进行转义,就意味着让浏览器对待它们的时候,就像普通字符同样,好比 ≶script>
这段文字在浏览器中就会正常显示为 <script>
。动画
当咱们在代码中生成 HTML 时,必定要注意,变量是否转义了。像这种网站
el.innerHTML = title.value;
就是很是危险的。由于输入框的内容来源于用户,而用户的输入是不可靠的。不管是前端仍是后台,必定要有一个相似于 escapeHTML 的方法,而后在代码中这样使用
el.innerHTML = escapeHTML(title.value);
这边贴一段简单的用来转义 HTML 的 JavaScript 方法
function encodeHTML (a) { return String(a) .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); };
那么有哪些字符须要转义呢?这里列了一些常见的。
" --> "
# --> #
$ --> $
& --> &
' --> '
( --> (
) --> )
; --> ;
< --> <
> --> >
在 escapeHTML 方法中,我使用了别名的方式转义,由于它比较容易记一点。不管是别名仍是十六进制,它们表示的含义都是同样的,好比 &
和 &
都表示 &
符号。想要看更具体的列表能够参考这个网站。
在浏览器收到 HTML 以后,首先会对全部的内容进行解码,它会把全部能识别的编码符号,解码成字面值。好比有
<p>my name is: <a href="http://www.jchen.cc">名一</a></p>
通过浏览器解码就变成
<p>my name is: <a href="http://www.jchen.cc">名一</a></p>
这里要说的是,浏览器只会对两个地方解码,一个是标签的内容(即 textContent,除了 <script>
和 <style>
标签),另外一个是标签的属性值。对于属性名是不会解码的。
早些时候,服务端还不支持在 URL 中直接传输 Unicode,好比 http://jchen.cc/find?q=你好
这样的地址,服务端没法识别“你好”这个值,因此必须编码以后进行传输。
那么对于 URL,咱们只须要对参数的值进行编码就能够了。好比上面这个连接,编码以后就是 http://jchen.cc/find?q=%E4%BD%A0%E5%A5%BD
。
若是对整个 URL 编码,那么连接就无效了。
编码的方式很简单,浏览器提供了全局的 encodeURI
方法,调用以后就能够实现转义了。
有一点很重要,encodeURI
是不会转义 :
, /
, ?
, &
, =
这些在 URL 中有特殊含义的字符的,那么若是有个参数正好包含了这些字符,就不会转义,好比
encodeURI('http://jchen.cc/login?name=名一&from=http://other.com'); // -> http://jchen.cc/login?name=%E5%90%8D%E4%B8%80&from=http://other.com
from 参数的值并无转义,这时候,就须要用到另外一个方法 encodeURIComponent
var param = encodeURIComponent('http://other.com'); encodeURI('http://jchen.cc/login?name=名一&from=') + param; // -> http://jchen.cc/login?name=%E5%90%8D%E4%B8%80&from=http%3A%2F%2Fother.com
因此结论就是,若是要对整个 URL 进行转义,使用 encodeURI
,若是对参数的值进行转义,使用 encodeURIComponent
。
当动态生成的连接地址须要赋值给 href 或者 src 属性时,须要对这些地址进行 URL 转义。固然,若是服务端支持在 URL 中包含 UTF-8 的字符的话,其实不转义也不会错,这就是为何咱们平时不会太注意对表单和 URL 参数进行转义的缘由,由于服务端表现良好。
JS 中的转义都是经过反斜杠完成,有三种类型,以 '
和 "
为例
直接反斜杠 --> \'\"
十六进制 --> \x22\x27
Unicode --> \u0022\u0027
通常状况下能够直接经过反斜杠转义,但有些字符咱们不知道怎么输入,很常见的好比 Web Font,在 CSS 中能够看到相似这样的代码
.glyphicon-home::before { content: ""; }
那个 content 中的值能够经过十六进制或者 Unicode 的方式来代替。
JS 转义通常用于显示用户输入的时候,好比用户输入了反斜杠,须要显示时,就必须 alert('\\');
。
当浏览器进行绘制时,首先会对 HTML 进行解码,而后是 URL,最后是执行 JS 时对它进行解码。
如今考虑这三种编码同时存在的状况
<a href="javascript: alert('\<http://jchen.cc/find?q=%E4%BD%A0%E5%A5%BD\>');">click</a>
首先是 HTML 解码,结果为
<a href="javascript: alert('\<http://jchen.cc/find?q=%E4%BD%A0%E5%A5%BD\>');">click</a>
而后是 URL 解码,结果为
<a href="javascript: alert('\<http://jchen.cc/find?q=你好\>');">click</a>
最后是 JS 解码,结果为
<a href="javascript: alert('<http://jchen.cc/find?q=你好>');">click</a>
单击连接后,应该会出现一个弹窗,内容是 <http://jchen.cc/find?q=你好>
。
本文更多的是介绍如何防止XSS的发生,而不是它的危害。核心就是用适当的方法对 HTML, JS 进行转义。