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)
  • 不安全的反序列化
  • 使用含有已知漏洞的组件
  • 不足的日志记录和监控