网络安全:DOM型XSS

前言:继《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%20οnerrοr=alert(%22jerk%22)%3E"></option></select><img src="1" οnerrοr="alert(&quot;jerk&quot;)"><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码字符集写脚本,那是不可理喻,也是不太现实的,好吧。

发布了53 篇原创文章 · 获赞 80 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_41500251/article/details/100567802