XSS漏洞的原理、利用、防御

简单的XSS入门~

Posted by BlackDn on April 21, 2021

“笑中闪动的星光,皆因世间的爱和善良。”

前言

这篇博客是基于上学期网络攻防课的实验报告来的
那时还没那么全面地认识 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 方法

1

因为是自己写的简单页面,所以后台肯定啥过滤验证都没有,看一下 XSS 攻击的效果
输入一段 JS 代码

<script>alert(1)</script>

可以看到,我们的 JS 代码没有被显示,而是被浏览器解释执行,出现了弹窗

2

我们来简单分析一下
我们可以看到,在回显的部分,并没有显示出我们刚才输入的内容,反之是产生了弹窗的效果
这就说明我们输入的<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

成功地将页面变为黑色

3

两种情况大同小异,同样是输入的字符串被解析为 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>

效果就很梦幻了
4

这 src 里的网站就可以是攻击者自己的恶意网站了,或者将 width 和 height 属性改为 0,从而达到黑链的效果

既然是存储型 XSS,我们就去后台数据库看看
可以发现,数据库中是老老实实把输入的内容存了下来。因此,每次后台加载页面发送给客户端的时候,都会将这里的内容作为代码解释
5

页面代码

因为是小组作业所以代码比较多,限于篇幅这里只给出核心代码

页面代码:

<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 将所有节点抽取,转化为树结构,引入父节点、子节点、兄弟节点等概念,这数据结构这块就不多说了
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 代码放入其中进行执行
7

当然了,想用这种方法进行攻击还是需要构造恶意 URL 并诱导受害者点击
这样进行的 XSS 攻击不会经过服务器后台,而是针对浏览器和已经返回的 HTML 页面进行攻击,因此称为基于 DOM 的 XSS

XSS 的防御

反射型和存储型 XSS 的防御

因为反射型和存储型 XSS 都要经过后台服务器,除了在客户端进行输入验证外,还有一个很方便有效的htmlspecialchars 方法就是利用方法在后台进行过滤
在获取输入的时候用这个方法,可以将输入的内容转换为 HTML 实体(比如<和>的实体为\&lt\; 和\&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、正则缺陷等
因此还需要有针对性地进行防御

  1. 对于写标签类的 DOM 型 XSS,输出到 HTML 中,例如:document.getEelementById(“id”) = innerHTML = “<img src=x onerror=alert(1)>“只需要将<和>实体化即可,即将<和>替换成\&lt\; 和\&g\t;
  2. 对于跳转类的 XSS,例如:location.href = “javascript:alert(1)”,将这个 url 创建成一个 a 标签,检查它的 host 是否合法,是否以指定的协议开头,否则视为非法跳转
  3. 对于 eval(string)类的 XSS,拒绝使用这种写法。如果实在要用,需要过滤非常多的字符,双引号,单引号,反引号,等于号,小括号,点,加减乘除,和一些特殊的字符

参考

  1. php XSS 攻击原理与防御
  2. 百度百科:XSS
  3. 反射型 xss 实战演示
  4. dvwa 中 利用反射型 xss 获取 cookie 进行会话的劫持演示
  5. 存储型 XSS 漏洞
  6. 什么是 DOM(文档对象模型)?
  7. DOM-XSS 攻击原理与防御
  8. JavaScript 伪协议
  9. 安全小课堂第 122 期【DOM-XSS 漏洞挖掘】