Cross-Site Scripting 简称为"CSS",为避免与前端叠成样式表的缩写"CSS"冲突,故又称XSS。一般XSS可以分为如下几种常见类型:
1.反射性XSS;
2.存储型XSS;
3.DOM型XSS;
XSS漏洞一直被评估为web漏洞中危害较大的漏洞,在OWASP TOP10的排名中一直属于前三的江湖地位。XSS是一种发生在前端浏览器端的漏洞,所以其危害的对象也是前端用户。
xss攻击,通常是指恶意攻击者往web页面里插入恶意的html代码,当用户浏览该页时,嵌入web里面的html代码会被执行,从而达到恶意攻击用户的目的。形成XSS漏洞的主要原因是程序对输入和输出没有做合适的处理,导致“精心构造”的字符输出在前端时被浏览器当作有效代码解析执行从而产生危害。
反射性xss又被称为非存储型xss,攻击者通常会通过URL参数传入恶意语句,并将带有恶意语句的URL发送给受害者(所以小朋友们不要乱点连接喔~),受害者想要打开这个网址变向服务器发送请求,服务器接到请求后便进行相应,从而实现攻击。由于我们的payload未经过一个存储的过程直接传到了用户的浏览页面上,所以也被称之为非存储型xss。
存储型XSS漏洞是因为服务器端将用户输入的恶意脚本没有通过任何验证就直接存储在数据库,并且每次通过调用数据库的方式,将该恶意脚本在没有被编码或转义的情况下将数据呈现在浏览器上。则该XSS跨站脚本攻击将一直存在,所以存储型XSS是持久化的。不管其他用户何时访问该页面,则恶意脚本就会被触发代码执行,用于盗取其他用户的私人信息,这种XSS比较危险,容易造成蠕虫,盗取Cookie。
攻击者在页面中插入xss代码,服务器将其存入数据库中,被攻击者在不知情的情况下访问了带有恶意脚本的页面,服务器将带有恶意脚本的页面返回给用户,浏览器解析页面从而实现攻击,并且数据返回至攻击者自己的恶意服务器中。
文档对象模型(Document Object Model),即大名鼎鼎的DOM。DOM可以被认为是一种通过将页面元素以对象的树形方式表现,以便由Javascript组织处理的实现方法。
简单的说,通过修改页面的DOM节点形成的xss,就是DOM based XSS。这里就要提及JS的极为重要的一个功能了,就是可以修改DOM节点。那么如果我们将反射型xss的payload用于修改DOM节点,那么就可以修改页面任意元素,多用于钓鱼。
DOM式xss触发方式与反射型xss漏洞相同,反射式xss的payload输出在HTML页面内,而DOM型payload并不在HTML页面内输出,但都需要用户主动点击才能触发,所以需要攻击者主动将包含
payload的URL发送给用户点击,用户在打开页面后运行恶意代码,黑客完成攻击。
if(isset($_GET['submit'])){
if(empty($_GET['message'])){
$html.="输入'kobe'试试-_-
";
}else{
if($_GET['message']=='kobe'){
$html.="愿你和{$_GET['message']}一样,永远年轻,永远热血沸腾!
";
}else{
$html.="who is {$_GET['message']},i don't care!
";
}
}
}
可见后端对message没有进行任何过滤,那我们直接输入,可是发现输入到一半卡住了,说明限制了text文本的长度,看一下它的前端源码
是这个maxlength在作祟,把它改成50
弹窗成功
首先让我们登录,查看网页源代码可知是admin+123456
登陆成功,问题还是那个问题,答案还是那个答案
![]() |
![]() |
查看后端代码
if(array_key_exists("message",$_POST) && $_POST['message']!=null){
$message=escape($link, $_POST['message']);
$query="insert into message(content,time) values('$message',now())";
$result=execute($link, $query);
if(mysqli_affected_rows($link)!=1){
$html.="数据库出现异常,提交失败!
";
}
}
判断message是否存在,并且 message不为空
进行了转义,防止sql注入
插数据库的时候,做了对应的转义,防止数据库的注入 ,把massage 插入到表中存储到数据库中
后面的一句代码$query="select * from message";
,将存储的留言都显示出来
虽然但是也是没有进行过滤的,还是直接
弹窗成功
之后你每次进入到这个板块,都会先一次弹窗才能进入正常页面,是持久化的体现,直到你把此条留言删除为止
查看源代码
<div id="xssd_main">
<script>
function domxss(){
var str = document.getElementById("text").value;
document.getElementById("dom").innerHTML = "what do you see?";
}
//试试:'>
//试试:' οnclick="alert('xss')">,闭合掉就行
script>
<input id="text" name="text" type="text" value="" />
<input id="button" type="button" value="click me!" onclick="domxss()" />
<div id="dom">div>
div>
(此处自动忽略那两句“试试”)getElementById 获取标签 id 为 text 的值传递给 str,str 通过字符串拼接到 a 标签中。可见这边出题者想要我们通过标签内引入JS脚本进行攻击。如果这样的话,那我们输入的内容都会被当作字符串处理,无法实现功能。说到这里,不免想起sql注入的字符型注入,有异曲同工之妙,当时我们是怎么让键入的值逃脱引号的呢?没错,就是闭合。
html5中双引号和单引号的区别:
1、单引号和双引号都可以作为字符串的开始符和关闭符,并且只能同一种单或者双引号来定义开始和结束;单引号之间的字符都被认为是字符,即使是转义符 \ 和变量符 $ ,例外的是 \ ’ 标识单引号。还记得这个 \ ’ 吗,在mysql里面讲过 \会将 ’ 转义为字符;
2、双引号之间的字符是需要php解析的,包括 $ ,\,{ 都保留了php赋予的特殊含义;
3、" " 双引号里面的字段会经过编译器解释,然后再当作HTML代码输出;而’ '单引号里面的不进行解释,直接输出。
例如:
$ abc=‘my name is tome’;
echo $ abc //结果是:my name is tom
echo ‘$ abc’ //结果是:abc
echo “$ abc” //结果是:my name is tom
简明扼要的说:单独html标签时,单引号和双引号没有区别
<html>
<body>
<input value="外双引号内双引号-错误" type="button" onclick="alert("OK");" /><br />
<input value="外单引号内单引号-错误" type="button" onclick='alert('OK');' /><br />
<input value="两个双引号-错误" type="button" onclick="alert(""OK"");" /><br />
<input value="两个单引号-错误" type="button" onclick="alert(''OK'');" /><br />
<input value="\+双引号-错误" type="button" onclick="alert(\"OK\");" /><br />
<input value="\+单引号-错误" type="button" onclick="alert(\'OK\');" /><br />
<input value="外双引号内单引号-OK" type="button" onclick="alert('OK');" /><br />
<input value="外单引号内双引号-OK" type="button" onclick='alert("OK");' /><br />
<br />
<input value = "HTML转义字符 "-ok" type = "button" onclick="alert( "ok" );" />
body>
html>
发现规律了吗,html标签中的单双引号的匹配并不遵循所谓的对称规则,我所理解的是就近原则。故我们常常单双引号交错使用。
那么,此处又该闭合什么?怎么闭合呢?
我们键入的内容会被字符化于 a 标签的 href 属性中,所以我们要闭合 < a >这个标签。而href这个属性用于规定链接的目标。而这个目标会被“自动”加上双引号(类比字符型注入,自动加上单引号),不用管,我们要闭合的是前面的 <’
自然而然想到用 '>进行闭合了,我们可以使用 '>
,这样 str 传到后端拼接,就成了
<a href=''>
what do you see?a>
再来试试这个' onclick="alert('xss')">
,(这边的alert引号可加可不加)这样 str 传到后端拼接,就成了
<a href='' onclick="alert('xss')">'>what do you see?a>
点击what do you see,便会触发弹窗
两种思路是不一样的,前者是闭合 a 标签,后者是将 href 链接的URL地址闭合,就是说都很巧妙
查看网页源代码
<div id="xssd_main">
<script>
function domxss(){
var str = window.location.search;
var txss = decodeURIComponent(str.split("text=")[1]);
var xss = txss.replace(/\+/g,' ');
// alert(xss);
document.getElementById("dom").innerHTML = "就让往事都随风,都随风吧";
}
//试试:'>
//试试:' οnclick="alert('xss')">,闭合掉就行
script>
<form method="get">
<input id="text" name="text" type="text" value="" />
<input id="submit" type="submit" value="请说出你的伤心往事"/>
form>
<div id="dom">div>
div>
和前面大同小异,过关答案也是一样的
一开始不知道盲打是什么意思,寻思着和盲注差不多,但是一通胡乱输入,啥也反馈不出来。也没注意看后端提示的去登录后台看看。查了一下才知道xss盲打是一种攻击场景,我们输出的payload不会在前端进行输出,当管理员查看时就会遭到xss攻击
if(array_key_exists("content",$_POST) && $_POST['content']!=null){
$content=escape($link, $_POST['content']);
$name=escape($link, $_POST['name']);
$time=$time=date('Y-m-d g:i:s');
$query="insert into xssblind(time,content,name) values('$time','$content','$name')";
$result=execute($link, $query);
if(mysqli_affected_rows($link)==1){
$html.="谢谢参与,阁下的看法我们已经收到!
";
}else {
$html.="ooo.提交出现异常,请重新提交
";
}
}
而后端也是没有经过过滤的,直接,然后登陆后台,地址/xssblind/admin_login.php,可见触发xss
可见提交记录也是被记录了下来
查看后端代码
if(isset($_GET['submit']) && $_GET['message'] != null){ //这里会使用正则对
成功弹窗
其他 payload 例如等也都是可以的
xss之hrmlspecialchars
查看后端代码
if(isset($_GET['submit'])){ if(empty($_GET['message'])){ $html.="
输入点啥吧!
"; }else { //使用了htmlspecialchars进行处理,是不是就没问题了呢,htmlspecialchars默认不对'处理 $message=htmlspecialchars($_GET['message']); $html1.="你的输入已经被记录:
"; //输入的内容被处理后输出到了input标签的value属性里面,试试:' οnclick='alert(111)' // $html2.=""; $html2.="{$message}"; } }htmlspecialchars()函数定义及用法
在php中,htmlspecialchars()函数是使用来把一些预定义的字符转换为HTML实体,返回转换后的新字符串,原字符串不变。如果 string 包含无效的编码,则返回一个空的字符串,除非设置了 ENT_IGNORE 或者 ENT_SUBSTITUTE 标志;
被转换的预定义的字符有:&:转换为& amp;
" :转换为& quot;
’ :转换为成为 ';
左尖括号 <:转换为<;
右尖括号 >:转换为>输入
看看
发现 " < > 都进行了html实体转码,但是没有对 ’ 进行实体转码,可以使用单引号构造 payload,可以输入' onclick='alert(/xss/)
(这边不要写成alert(‘xss’) ,引号匹配错误),这样后端就成了<a href="" onclick="alert('xss')">a>
还记得前面所说的就近匹配吗,这边就是第一个单引号闭合href属性的双引号,第二个单引号闭合href属性的闭合双引号
xss之href输出
查看后端代码
if(isset($_GET['submit'])){ if(empty($_GET['message'])){ $html.="
叫你输入个url,你咋不听?
"; } if($_GET['message'] == 'www.baidu.com'){ $html.="我靠,我真想不到你是这样的一个人
"; }else { //输出在a标签的href属性里面,可以使用javascript协议来执行js //防御:只允许http,https,其次在进行htmlspecialchars处理 $message=htmlspecialchars($_GET['message'],ENT_QUOTES); $html.=" 阁下自己输入的url还请自己点一下吧"; } }同样 message 也经过了 htmlspecialchars 的实体转义
输入看看
这次发现单引号也被转移掉了,那么是否还有其他方法可以调用JS脚本呢?
有的,在a标签的 href 属性里面,可以使用 javascript 协议来执行js,可以尝试使用伪协议绕过。具体参考:HTML中调用JavaScript的几种情况和规范写法此处输入
javascript:alert(/xss/)
,点击即可弹窗
同样,上一题也可以通过这种方式过关xss之js输出
后端代码没看出来个所以然,先输入
试试,看看前端源码
可以看到输入的代码被生成在js中并赋值给$ ms,我们的输入被带入到js中。
这里的❌提示的是Uncaught SyntaxError: Unexpected identifier
,意思是未捕获的语法错误,意外的标识符别人给出的解释是:
1.有可能是字符串类型的,但是并没有加双引号;
2.有的是没有加逗号 “,”仔细检查便好;
3.如果是jsp的话仔细检查下js中的声明,int与var不同;
4.有的是{}与()的区别。如:Code:$ {"#code"},改成 Code:$ ("#code")总之就是书写不规范
那么此处的问题是什么呢?我迟迟想不明白。
别人给出的答案是,前面的一个
用来闭合包含输入的 js 模块
究其原理,这其实最基础的动态加载引入 js 外部文件,具体表现是将所有需要的
反射型xss-medium
一眼源码
header ("X-XSS-Protection: 0"); // Is there any input? if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Get input $name = str_replace( '
![]()
反射型xss-high
一眼源
header ("X-XSS-Protection: 0"); // Is there any input? if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Get input $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] ); // Feedback for end user echo "
Hello ${name}"; } ?>可以看到对
标签进行了严格的过滤,但没有过滤别的标签,此时可以通过img、a 等标签干坏事,此处使用
弹窗成功存储型xss-low
name栏不能为空而且有长度限制,那就在message插入
发现成功弹窗,之后如果不删除提交的信息,每次进入此模块都会弹窗存储型xss-medium
操作跟反射型 medium 一样
存储型xss-high
操作跟反射型 high 一样
DOM型xss-low
后端啥也没有
# No protections, anything goes ?>
看看网页源代码
<div class="vulnerable_code_area"> <p>Please choose a language:p> <form name="XSS" method="GET"> <select name="default"> <script> if (document.location.href.indexOf("default=") >= 0) { var lang = document.location.href.substring(document.location.href.indexOf("default=")+8); document.write(" + decodeURI(lang) + ""); document.write(""); } document.write(""); document.write(""); document.write(""); document.write(""); script> select> <input type="submit" value="Select" /> form> div>
注意这一句
document.write("");
,句中 document.write 是 JavaScript 中对 document.open 所开启的文档流(document stream)操作的API方法,它能够直接在文档流中写入字符串,一旦文档流已经关闭,那 document.write 就会重新利用 document.open 打开新的文档流并写入,此时原来的文档流会被清空,已渲染好的页面就会被清除,浏览器将重新构建DOM并渲染新的页面,具体参考document.write详解而
decodeURI()
函数可对encodeURI()
函数编码过的 URI 进行解码,这就意味着,我们插入的 javascript 代码可以在 decodeURL(lang) 被执行
试试将 url 改为default=
成功了!DOM型xss-medium
观察后端代码
// Is there any input? if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) { $default = $_GET['default']; # Do not allow script tags if (stripos ($default, "
弹窗成功