“笑中闪动的星光,皆因世间的爱和善良。”
前言
这篇博客是基于上学期网络攻防课的实验报告来的
那时还没那么全面地认识 XSS,只是复现了下存储型 XSS 的漏洞
然后想啊,都写了报告了,那就顺便写个博客吧
然后就一直拖到现在…而且没想到一写就花了一整天时间
主要是 DOM XSS 当初没了解过,比较生疏…
XSS 漏洞的原理、利用、防御
跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表(CSS,Cascading Style Sheet)混淆,简写为 XSS。
通常是由于前后端对用户输入没有进行严密的验证、筛选,从而导致用户可以输入 HTML、JS 等代码,而浏览器或服务器将其误认为是代码而解释执行,从而产生危害,包括盗取 cookie、恶意留言、执行一些机器脚本(加好友发消息之类的)等。
XSS 主要包括三种,即反射型 XSS(Reflected XSS),存储型 XSS(Non-persistent XSS),基于 DOM 的 XSS(Dom-based XSS)
反射型 XSS(Reflected XSS)
反射型 XSS 也称非持久性 XSS(Non-persistent)或参数型 XSS,他最大的特点是攻击者输入的 payload不在服务端保存,输入恶意代码、访问恶意 URL 后立刻执行,因此称为反射型
最典型的反射型 XSS 就是通过 URL 构造恶意链接,诱使潜在受害者点击 URL,从而达到攻击的目的。
通常这些 URL 中含有 JavaScript 代码,这些 JS 代码没有被服务器过滤,因此被浏览器认为是 JS 代码而进行解释和执行
反射型 XSS 的初步利用
JS 弹窗
首先自己搭一个简单的页面(我命名为 xss.php,源码在后面)
服务器:XP 虚拟机,APMServ
访问者:Kali 虚拟机,FireFox 浏览器
这是一个简单的搜索框,实际上没有后台显示搜索结果,就是回显一下输入的文字而已
可以从网址看到用的是 GET 方法
因为是自己写的简单页面,所以后台肯定啥过滤验证都没有,看一下 XSS 攻击的效果
输入一段 JS 代码
<script>alert(1)</script>
可以看到,我们的 JS 代码没有被显示,而是被浏览器解释执行,出现了弹窗
我们来简单分析一下
我们可以看到,在回显的部分,并没有显示出我们刚才输入的内容,反之是产生了弹窗的效果
这就说明我们输入的<script>片段并没有被解析为输入的字符串内容,而是被浏览器错认是 JS 代码执行了
再看网址部分
发现网址后面的参数内容和我们输入的有些差距,这是因为在这里我们输入的某些字符经过了 URL 编码(比如反斜杠”"被编码为%2F)
这里我们看到只有反斜杠”"被编码了,尖括号啥的都没有被编码,这是浏览器的问题,不理他
为了保险起见,我们把输入内容全部进行 URL 编码,构造出的 URL 就是所谓的恶意 URL
输入内容:<script>alert(1)</script>
恶意URL:http://192.168.245.129:8089/security/xss.php?search=%3Cscript%3Ealert(1)%3C%2Fscript%3E
将这个 URL 发给受害人,诱导人家点击这个 URL,就可以使其收到攻击了
也就是俗称的钓鱼
CSS 改背景
我们可以换一个 CSS 代码,将页面的背景变为黑色
顺便给出 URL 编码后的内容
输入:<style>body{background:#000}</style>
恶意URL:http://192.168.245.129:8089/security/xss.php?search=%3Cstyle%3Ebody%7Bbackground%3A%23000%7D%3C%2Fstyle%3E
成功地将页面变为黑色
两种情况大同小异,同样是输入的字符串被解析为 CSS 代码而执行,页面上并没有回显(全黑的也看不到 ww)
反射型 XSS 小结
现在来看概念,就更好理解了
不管输入的是什么,本质上都是前后端对输入的内容没有进行筛选过滤,从而让浏览器误认为输入内容是程序代码而执行
而上面两种就是典型的反射型 XSS,只有用户在点击 URL 的时候会受害,不点就没事,同时后台也没有对输入内容(攻击代码)的保存,这点可以到时候和存储型 XSS 一起比较
同时这也是为什么搜索框这些地方更容易收到反射型 XSS 的攻击,他们通常是用 GET 方法传参,并且能够进行回显,所以在这里构造恶意 URL 来攻击其他人再合适不过了
上面这两种方法看起来没什么危害,刷新一下就没了。更正经严重的危害其实有很多,因为实现起来比较麻烦就懒得搞了
在“参考 3:反射型 xss 实战演示”和“参考 4:dvwa 中 利用反射型 xss 获取 cookie 进行会话的劫持演示”中有利用反射型 XSS 窃取用户 cookie 的例子,有兴趣可以看看或者去实现一下
基本思路是在自己的服务器下有一个打印 cookie 的程序
然后利用反射型 XSS 访问自己的程序,并将用户的 cookie 作为参数传入(比如用 document.cookie)
以此来窃取用户 cookie
页面代码
下面是随便写的 xss.php 的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XSS</title>
</head>
<body>
<form action="" method="GET">
<h4>BlackDn搜索网</h4>
<input name="search" type="text" placeholder="请输入关键词">
<button>搜索</button>
</form>
<?php
if($_REQUEST["search"] != null ) {
$input = $_REQUEST["search"];
echo "以下是关于 ".$_GET['search']."的搜索结果";
} else {
echo "请输入搜索关键词";
}
?>
</body>
</html>
存储型 XSS(Non-persistent XSS)
存储型 XSS 是相对于反射型 XSS 而言的,他们的本质都是一样的,浏览器误认为输入内容为代码而解释执行
唯一的区别就是存储型 XSS 的输入内容是保存在服务器(数据库)的, 特别是留言板、评论区这种地方
因此实际上危害是一直存在的,并不像反射型 XSS 一样,得用户点击才会产生危害
我感觉存储型 XSS 是最好理解的了…
存储型 XSS 的初步利用
因为之前网建课和小伙伴一起做过一个网站,就直接把里面的留言板功能拿来用了,嘻嘻
当然,alert 弹窗啥的都是可以用的,这里就不做测试了,大家可以自己试试
这里利用下<iframe>标签,让留言板显示一个小窗口
<iframe src="http://www.baidu.com" width=500 height=100></iframe>
效果就很梦幻了
这 src 里的网站就可以是攻击者自己的恶意网站了,或者将 width 和 height 属性改为 0,从而达到黑链的效果
既然是存储型 XSS,我们就去后台数据库看看
可以发现,数据库中是老老实实把输入的内容存了下来。因此,每次后台加载页面发送给客户端的时候,都会将这里的内容作为代码解释
页面代码
因为是小组作业所以代码比较多,限于篇幅这里只给出核心代码
页面代码:
<body>
<div class="top-title">我要留言</div>
<div class="mainSend">
<form action="./database/insert_board.php" method="POST">
<div class="title-box">
<p class="title">留言标题</p>
<input class="sendtitle" name="title" placeholder="请输入标题"></input>
</div>
<div class="content-box">
<p class="title">留言内容</p>
<textarea class="sendcon" name="content" placeholder="请输入内容"></textarea>
</div>
<div>
<button class="sendBtn">发布</button>
</div>
</form>
</div>
</body>
输入后插入数据库代码:
<?php
include('../function/check_login.php');
include("connect_database.php");
$sql="INSERT INTO board(`id` ,`title` ,`content`, `name`, `time`) VALUES (NULL, \"$_POST[title]\", \"$_POST[content]\", \"$_SESSION[name]\", NOW())";
if($conn->query($sql) === TRUE) {
echo "<script>";
echo "alert(\"发布成功\"";
echo "</script>";
echo "<meta http-equiv=\"refresh\" content=\"0; url=../board.php\"/>";
}
$conn->close();
?>
加载页面时查找数据库代码:
<?php
include("./database/connect_database.php");
$sql = "SELECT * from board ORDER BY id DESC";
$result = $conn->query($sql);
while($row=mysqli_fetch_assoc($result)) {
echo "<div class=\"mainBolg\">";
echo "<p class=\"blogtitle\">".$row['title']."</p>";
echo "<p class=\"blogcon\">".$row['content']."</p>";
echo "<footer class=\"info\">";
echo "<div class=\"poster\">发布人:".$row['name']."</div>";
echo "<div class=\"post-time\">发布时间: ".$row['time']."</div>";
echo "</footer>";
echo "</div>";
}
mysqli_free_result($result);
$conn->close();
include("./html/post_board.html");
?>
基于 DOM 的 XSS(Dom-based XSS)
反射型 XSS 和存储型 XSS 两两相对,为什么还有个基于 DOM 的 XSS 呢?
事实上,不管是反射型 XSS 还是存储型 XSS,他们都有一个共同点,就是我们输入的恶意代码都先发送到后台,再返回到浏览器(客户端)
比如反射型我们模拟了一个搜索页面,先传到后台再返回到浏览器进行回显;存储型就是先将输入内容存到后台数据库,每次访问页面都会从数据库取出显示
而基于 DOM 的 XSS 则不同,它不用将恶意代码传输到服务器再返回,而是利用DOM 文档直接执行恶意代码
利用方法和反射型 XSS 很类似,都是构造恶意 URL 让用户点击(钓鱼),但还是有一个区别的
基于 DOM 的 XSS 构造的 URL 参数不用发送到服务器端,而是通过 URL 中的参数以及 DOM 文档中的方法(多是 JS)来进行攻击,可以达到绕过 WAF(Web Application Firewall,网站应用级入侵防御系统)、躲避服务端的检测等效果
DOM 文档
先简单说下 DOM 文档
DOM(Document Object Model),文档对象模型,是 W3C 组织推荐的处理可扩展标志语言的标准编程接口
在 HTML DOM 中,所有的东西都被定义为节点,每个页面、每个标签、每个元素属性、节点内容都是节点
每个节点都有自己的属性、方法,在 JS 中就可以调用,从而动态获得属性或内容的值
随后,DOM 将所有节点抽取,转化为树结构,引入父节点、子节点、兄弟节点等概念,这数据结构这块就不多说了
听起来很抽象,对吧?确实抽象,我也看不懂
我们不如这样理解,在 JS 中,DOM 将一个页面看作一个对象,从而我们能够调用这个对象的一些方法来快速、动态地取值或赋值
比如,document就表示当前页面,document.cookie返回的就是当前页面的用户 cookie,document.write就是在当前页面(HTML 文件)写入,document.getElementById就是拿到当前页面特定 id 的标签元素
当然这些都要在 JS 中实现
所以简单来说,DOM 就是面向对象在 JS 中的应用,或许不准确但和便于理解
关于 DOM 的详细内容可以看看参考 6:什么是 DOM(文档对象模型)?,写的不错的,详细易懂
井号(#)跳转中的 DOM XSS
这个例子取自参考 7:DOM-XSS 攻击原理与防御,这篇博客是真不戳
页面跳转常用 # 实现,我也有几篇博客在实现页面内,标题间跳转的时候也会用到(写到这我回头试了下我那几篇博客页面,很遗憾并没有这个漏洞)
自己写一个试一试,因为页面代码很短就直接放这里了
<html>
<head>
<title> DOM-XSS </title>
</head>
<body>
<h2>为了不让页面太空白所以写一句话在这</h2>
<script>
var hash = location.hash;
if(hash){
var url = hash.substring(1);
location.href = url;
}
</script>
</body>
</html>
逻辑很简单, 先用location.hash得到#号之后的内容,再用location.href根据得到的内容进行页面的跳转
我们利用 JS 伪代码构造恶意 URL,在后面追加以下代码
#javascript:alert(1)
浏览器会将伪代码 javascript:之后的代码用javascript 的解释器运行,因此可以将 JS 代码放入其中进行执行
当然了,想用这种方法进行攻击还是需要构造恶意 URL 并诱导受害者点击
这样进行的 XSS 攻击不会经过服务器后台,而是针对浏览器和已经返回的 HTML 页面进行攻击,因此称为基于 DOM 的 XSS
XSS 的防御
反射型和存储型 XSS 的防御
因为反射型和存储型 XSS 都要经过后台服务器,除了在客户端进行输入验证外,还有一个很方便有效的htmlspecialchars 方法就是利用方法在后台进行过滤
在获取输入的时候用这个方法,可以将输入的内容转换为 HTML 实体(比如<和>的实体为\<\; 和\&g\t;)
这样就不会被浏览器解析为 JS 代码而进行执行了
比如在上面存储型 XSS 的栗子 🌰 中:和>
<?php
include('../function/check_login.php');
include("connect_database.php");
//XSS 加固
$_POST['title']=htmlspecialchars($_POST['title']); //XSS加固
$_POST['content']=htmlspecialchars($_POST['content']); //XSS加固
$sql="INSERT INTO board(`id` ,`title` ,`content`, `name`, `time`) VALUES (NULL, \"$_POST[title]\", \"$_POST[content]\", \"$_SESSION[name]\", NOW())";
if($conn->query($sql) === TRUE) {
echo "<script>";
echo "alert(\"发布成功\"";
echo "</script>";
echo "<meta http-equiv=\"refresh\" content=\"0; url=../board.php\"/>";
}
$conn->close();
?>
基于 DOM 的 XSS 的防御
基于 DOM 的 XSS 虽然实现方法单一,都是构造恶意 URL,但场景繁多,包括跳转、eval、正则缺陷等
因此还需要有针对性地进行防御
- 对于写标签类的 DOM 型 XSS,输出到 HTML 中,例如:document.getEelementById(“id”) = innerHTML = “<img src=x onerror=alert(1)>“只需要将<和>实体化即可,即将<和>替换成\<\; 和\&g\t;和>和>
- 对于跳转类的 XSS,例如:location.href = “javascript:alert(1)”,将这个 url 创建成一个 a 标签,检查它的 host 是否合法,是否以指定的协议开头,否则视为非法跳转
- 对于 eval(string)类的 XSS,拒绝使用这种写法。如果实在要用,需要过滤非常多的字符,双引号,单引号,反引号,等于号,小括号,点,加减乘除,和一些特殊的字符