web应用安全之XSS攻击

Cross-Site Scripting

前言

在上一篇文章《web应用安全之SQL注入》中,本人从java的角度就Java web开发过程中SQL注入的问题简单表达了下自己的观点,本文将在上一文的基础上继续讲述web应用安全的另一个问题————XSS攻击。

什么是XSS攻击

XSS攻击,全称是“跨站点脚本攻击”(Cross Site Scripting),之所以缩写为XSS,主要是为了和“层叠样式表”(Cascading Style Sheets,CSS)区别开。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。

XSS攻击原理

XSS攻击原理

  • 攻击者对含有漏洞的服务器发起XSS攻击(注入JS代码);
  • 诱使受害者打开受到攻击的服务器URL;
  • 受害者在Web浏览器中打开URL,恶意脚本执行。

一个简单演示代码如下:
xss注入input
从上面的代码可以看出,输入框被非法放入了一段js代码,当浏览器解析到这段代码时,浏览器并不知道这些代码改变了原本程序的意图,会照做弹出一个信息框。
XSS攻击演示

XSS攻击的类型

常见的 XSS 攻击有三种:反射型、DOM-based 型、存储型。 其中反射型、DOM-based 型可以归类为非持久型 XSS 攻击,存储型归类为持久型 XSS 攻击。

反射型

用户在页面输入框中输入数据,通过 get 或者 post 方法向服务器端传递数据,输入的数据一般是放在 URL 的 query string 中,或者是 form 表单中,如果服务端没有对这些数据进行过滤、验证或者编码,直接将用户输入的数据呈现出来,就可能会造成反射型 XSS。

xss注入url

上面这个请求地址被非法注入了js代码,当name的参数值(脚本标记)被后端代码重新下发给前端时,脚本标记就会在前端被执行,从而触发反射型XSS。

DOM-based 型

DOM 是一个树形结构,攻击者可以通过写 js 代码来修改节点,对象和值。如果用户在客户端输入的数据包含了恶意的 JavaScript 脚本,而这些脚本没有经过适当的处理,那么应用程序就可能受到DOM-based XSS攻击。

本文在讲述XSS攻击原理时,演示了一个非法注入的HTML页面,如果在这个页面的基础上执行如下js,将会发生DOM-based XSS攻击。

1
2
3
var content = document.getElementById("content");
var board = document.getElementById("board");
board.innerHTML = text.value; //发生DOM-based XSS攻击

存储型

存储型XSS攻击也可以说是持久型XSS攻击,通常是因为服务器端将用户输入的恶意脚本没有经过验证就存储在数据库中,并且通过调用数据库的方式,将数据呈现在浏览器上,当页面被用户打开的时候执行,每当用户打开浏览器,恶意脚本就会执行。持久型的 XSS 攻击相比非持久型的危害性更大,因为每当用户打开页面,恶意脚本都会执行。
假如XSS攻击原理演示中的id为content的输入框内容被提交,如果后台没有做过滤处理,服务端将内容保存到数据库,当从后台再次取出数据在前端展示时,就会执行这些恶意攻击代码,并且这种攻击每次打开都会发生。

XSS的防御措施

编码

对用户输入的数据进行编码

HTML 编码

将不可信数据放入到 HTML 标签内(例如div、span等)的时候进行HTML编码

显示结果描述实体编号
空格&nbsp ;
<小于&lt ;
>大于&gt ;
&&amp ;
‘’引号&quot ;
1
2
3
4
5
6
7
8
9
function encodeForHTML(str, kwargs){
return ('' + str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;') // DEC=> &#60; HEX=> &#x3c; Entity=> &lt;
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;') // &apos; 不推荐,因为它不在HTML规范中
.replace(/\//g, '&#x2F;');
};

HTML Attribute 编码

将不可信数据放入 HTML 属性时(不含src、href、style 和事件处理属性),进行 HTML Attribute 编码,除了字母数字字符以外,使用 &#xHH;(或者可用的命名实体)格式来转义ASCII值小于256所有的字符​​​​​​​

1
2
3
4
5
6
7
8
9
10
11
function encodeForHTMLAttibute(str, kwargs){
let encoded = '';
for(let i = 0; i < str.length; i++) {
let ch = hex = str[i];
if (!/[A-Za-z0-9]/.test(str[i]) && str.charCodeAt(i) < 256) {
hex = '&#x' + ch.charCodeAt(0).toString(16) + ';';
}
encoded += hex;
}
return encoded;
};

JavaScript 编码

将不可信数据放入事件处理属性、JavaScirpt值时进行 JavaScript 编码,除字母数字字符外,使用\xHH格式转义ASCII码小于256的所有字符

1
2
3
4
5
6
7
8
9
10
11
function encodeForJavascript(str, kwargs) {
let encoded = '';
for(let i = 0; i < str.length; i++) {
let cc = hex = str[i];
if (!/[A-Za-z0-9]/.test(str[i]) && str.charCodeAt(i) < 256) {
hex = '\\x' + cc.charCodeAt().toString(16);
}
encoded += hex;
}
return encoded;
};

URL 编码

将不可信数据作为URL参数值时需要对参数进行encodeURIComponent编码

1
2
3
function encodeForURL(str, kwargs){
return encodeURIComponent(str);
};

CSS 编码

将不可信数据作为 CSS 时进行 CSS 编码,除了字母数字字符以外,使用\XXXXXX格式来转义ASCII值小于256的所有字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function encodeForCSS (attr, str, kwargs){
let encoded = '';
for (let i = 0; i < str.length; i++) {
let ch = str.charAt(i);
if (!ch.match(/[a-zA-Z0-9]/)) {
let hex = str.charCodeAt(i).toString(16);
let pad = '000000'.substr((hex.length));
encoded += '\\' + pad + hex;
} else {
encoded += ch;
}
}
return encoded;
};

许多 XSS 攻击的目的就是为了获取用户的 cookie,将重要的 cookie 标记为 http only,这样的话当浏览器向服务端发起请求时就会带上 cookie 字段,但是在脚本中却不能访问 cookie,这样就避免了 XSS 攻击利用 js 的 document.cookie获取 cookie。

使用 XSS Filter

在上一篇文章《web应用安全之SQL注入》中,讲SQL注入的防范与处理时,提到了自定义过滤规则防范SQL注入,同样的对于XSS我们也可以自定义过滤规则防范XSS攻击,我们只需要在重写getParameter方法中调用XSS的过滤规则即可,详情不在赘述。

附:2017 年公布了十大安全漏洞列表

  • 注入
  • 失效的身份认证
  • 敏感信息泄漏
  • XML 外部实体(XXE)
  • 失效的访问控制
  • 安全配置错误
  • 跨站脚本(XSS)
  • 不安全的反序列化
  • 使用含有已知漏洞的组件
  • 不足的日志记录和监控