一、XSS 简介
XSS(Cross-Site Script,跨站脚本攻击)
攻击方式如下图所示:
XSS 攻击根据不同的注入和攻击形式,通常划分为以下三种类型:
(1)反射型(非持久型)XSS
-
示意图:
-
特点:
① 依赖于服务器,服务端直接使用恶意脚本并返回结果页;
② 其攻击方式往往是通过诱导用户去点击一些带有恶意脚本参数的 URL而发起的;
③ 反射型 XSS 其实就是服务器对恶意的用户输入没有进行安全防范处理就直接使用到响应页面中,然后反射响应内容给用户,从而导致恶意代码在浏览器执行的一种 XSS 漏洞;
④ 事实上由于反射型 XSS 因为 url 特征所以很容易被防御。很多浏览器如 Chrome 都内置了 相应的 XSS Filter(过滤器),可以防止大部分反射型XSS攻击。
(2)存储型(持久型)XSS
-
示意图:
-
特点:
① 依赖于服务器,服务端存储恶意脚本并返回;
② 存储型 XSS 并不需要用户点击链接才能触发;
③ 持久型 XSS 其实就是对恶意的用户输入没有进行检测处理就保存在服务端的文件或者数据库中,并且取出恶意的数据时也没有做相关安全处理就返回响应,导致了存储的恶意脚本数据在浏览器中执行的一种 XSS 漏洞;
④ 无论是反射型 XSS 还是存储型 XSS,其本质都是 XSS 攻击。 XSS 攻击的本质就是由于浏览器执行攻击者插入的恶意的脚步数据所致。
(3)DOM-Basedx型 XSS
-
示意图:
-
特点:
① 不依赖于服务器,数据来源于客户端;
② 从效果来看 ,DOM-Based 型 XSS 也是需要诱导用户去打开相应恶意链接才能发送的 XSS 攻击;
③ DOM-Based 型 XSS 是由于客户端 JavaScript 脚本修改页面 DOM 结构时(修改文本、重绘、重排)引起浏览器 DOM 解析所造成的一种漏洞攻击;
④ 如果页面 JavaScript 脚本不存在着漏洞的话,则不会发送 DOM-Based 型 XSS 攻击。一般常触发的场景为:innerHTML
,outerHTML
,document.write
等。
二、XSS payload
- 能够实现XSS攻击的恶意脚本就是
XSS payload
-
主要类型如下:
① 窃取用户的cookie(可通过document.cookie
获取。例如:cookie是网站中表示用户登录态的重要信息,如果窃取cookie,便能模仿用户的登录态。)
② 识别用户浏览器(可通过navigator.userAgent
获取。根据不同浏览器使用不同的攻击方式)
③ 伪造请求(通过Ajax
、<img>
、<a>
、src
、form表单
等发起get/post
请求,从而实现XSS攻击)
④ XSS钓鱼(XSS payload + 钓鱼网站。例如,在评论区输入<a href="www.fish.com">王者荣耀周年活动</a>
,诱使用户点击后跳转到钓鱼网站输入用户名和密码,以此来盗取帐号)
三、XSS 防御
1. 给cookie设置httpOnly
- 设置 Cookie 的 httpOnly 属性时,会致使客户端脚本(即
JavaScript
)无法访问cookie的信息(例如在控制台打印document.cookie
无法获取到信息),只有与服务端交互的时候,http请求头才会带上该cookie的信息(控制台Network-包名-Headers-Request Headers-Cookie
可查看详细信息),从而减少 Cookie 被盗取的可能性; - 但由于网站本身可能也需要获取 Cookie 的信息,因此通常来说,我们只会在部分重要的 Cookie 上(如密码)设置 httpOnly 属性;
-
无法从根本上解决XSS,只是一种减少XSS伤害的措施。
2. 输入检查
(1)白名单
- 定义:判断输入格式,只允许通过特定格式的字符。
- 对于只允许用户提交纯文本的情况下。例如注册帐号时,使用白名单的形式检测邮箱的格式是否正确;
- 对于允许用户提交一些自定义 HTML 代码的情况下(富文本),如评论框、帖子的内容有图片、链接、表格等(需要通过 HTML 代码来实现)。使用白名单的形式(只可以使用指定的标签和属性)来实现输入检查。
(2)黑名单
-
定义:收到数据时过滤危险字符(各种标签与属性,如:
<script>
,<style>
,<iframe>
,onclick
,onerror
等),或者转义特殊字符(如:<
--><
,>
-->>
,&
-->&
,\
-->"
,"
-->"
,'
-->'
等) - 对于允许用户提交一些自定义 HTML 代码的情况下(富文本),可以使用黑名单来过滤与转义危险字符。
-
过滤与转义需前端与服务端配合使用,因为可以使用某些工具(如
Node
)绕过前端检测,直接发送数据给服务端。 - 参考代码如下:
// 正则获取危险标签
var REGEXP_TAG = /<(script|style|iframe)[^<>]*?>.*?<\/\1>/ig;
// 正则获取危险标签属性
var REGEXP_ATTR_NAME = /(onerror|onclick)=([\"\']?)([^\"\'>]*?)\2/ig;
/**
* 过滤函数
* @param {String} str
*/
function filter(str) {
return String(str)
.replace(REGEXP_TAG, '')
.replace(REGEXP_ATTR_NAME, '');
}
/**
* 转义 HTML 特殊字符
* @param {String} str
*/
function htmlEncode(str) {
return String(str)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
- 拓展
事实上本文所实现的输入检查逻辑并不完善,在真实的业务开发中,输入检查所做的工作更为全面和复杂。目前有许多十分完善通用的 XSS 防范的代码库,大家可以结合理解去阅读和试用下:
3. 输出检查
用户数据经过服务器处理被填充到 HTML 代码中,可能存在以下五个场景:
- HTML 标签中(用户输入将在 HTML 解析环境进行)
- HTML 属性中(用户输入将在 HTML 解析环境进行)
- script 标签中 (用户输入将在 JavaScript 解析环境进行)
- HTML 事件属性中(用户输入将在 JavaScript 解析环境进行)
- 地址栏中(用户输入将在 URL 解析环境进行)
如下图所示:($var表示用户数据)
因此,为了避免我们用户输入被当做代码来执行,我们需要对用户输入中存在的特殊字符做一些处理。处理规则如下所示:
- HTML 解析环境使用 HtmlEncode
function htmlEncode(str) {
return String(str)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
- JavaScript 解析环境使用 JavaScriptEncode
// 通常是使用"\"对特殊字符进行转义
// 实际情况比较复杂,此处为简单例子
function JavaScriptEncode(str) {
function encodeCharx(original) {
var thecharchar = original.charAt(0);
switch(thecharchar) {
case '\n': return "\\n"; break;
case '\r': return "\\r"; break;
case '\'': return "\\'"; break;
case '"': return "\\x22"; break;
case '\&': return "\\&"; break;
case '\\': return "\\\\"; break;
case '\t': return "\\t"; break;
case '/': return "\\x2F"; break;
case '<': return "\\x3C"; break;
case '>': return "\\x3E"; break;
default: return thecharchar;
}
}
var preescape = str;
var escaped = "";
for(var i=0; i < preescape.length; i++){
escaped = escaped + encodeCharx(preescape.charAt(i));
}
return escaped;
}
- URL 解析环境使用 URLEncode
// 直接使用encodeURI()编码就行
var url = encodeURI($test);