网络安全:XSS漏洞

前面的几篇文写得有点快了,有些地方囫囵吞枣的划过去。等停下来一段时间,我会慢慢去修改和补充。

 

现在放慢脚步来一点点的写。

 

XSS(Cross Site Scripting),又称跨站脚本攻击。看英文,缩写应该是CSS,但是为了区别前端的CSS(Cascading Style Sheets)层叠样式表,改名为XSS。这也算是“晚辈”对“长辈”的尊敬吧,何况你还属于“暗”,人家在“明”,总不能抢名字抢的明目张胆。

 

XSS漏洞的存在的原因,和sql注入在本质上是相同的,都是因为没有对用户的输入进行检验和过滤,而产生攻击者可能嵌入恶意脚本(主要是JavaScript)的隐患。

 

XSS从攻击代码的工作方式上可以分为三类:1.反射型XSS(又称非持久型);2.存储型XSS(又称持久型);3.DOM型XSS(Document object model)。

 

我们会一一提到。

 

首先说反射型。我的五毛钱特效解释图:

                                 

用户在浏览器的输入数据会提交给服务器,服务器对数据进行处理,返回在页面上。如果服务器处理不当,就会出现问题。

 

理论归理论,还是直接把DVWA调出来。

 

这个界面里有一个输入框,还有一个提交按钮。右键查看下源码(Ctrl + u),使用我教你的英语阅读法,迅速定位主要内容(要学会用搜索Ctrl + f)。

 

<h1>Vulnerability: Reflected Cross Site Scripting (XSS)</h1>

	<div class="vulnerable_code_area">
		<form name="XSS" action="#" method="GET">
			<p>
				What's your name?
				<input type="text" name="name">
				<input type="submit" value="Submit">
			</p>

		</form>
		
	</div>

这时我们在输入框中随便输入点什么,点击提交。

页面显示出Hello XX,XX就是你的输入内容。这里的显示,就是讲解图中的最后一步。

 

打开源码:

                         

<h1>Vulnerability: Reflected Cross Site Scripting (XSS)</h1>

	<div class="vulnerable_code_area">
		<form name="XSS" action="#" method="GET">
			<p>
				What's your name?
				<input type="text" name="name">
				<input type="submit" value="Submit">
			</p>

		</form>
		<pre>Hello hack</pre>
	</div>

我们看到hack被嵌入到了界面中。

 

这时我们就可以猜想,服务器做的处理是什么呢?甚至不用想,就是把输入内容嵌入到网页上。

 

view source下:

<?php 

header ("X-XSS-Protection: 0"); 

// Is there any input? 
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { 
    // Feedback for end user 
    echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>'; 
} 

?> 

果不其然,if条件从参数中拿出name的值,直接echo出来。

 

头部的header函数,它是向用户发送最原始请求头的。不是本文的重要内容,我举个简单的例子,感兴趣的要学会去查(包括什么是请求头,请求头的作用是什么,header具体怎么使用等等)。

 

例如用户浏览器的默认设置,在加载数据的时候会进行缓存。如果服务器并不希望给你提供完服务后,还让你保存着它的数据,它的后台处理代码中就可能有这么一句:

header("Cache-Control: no-cache");
header("Pragma: no-cache");

如此就会强制浏览器不进行缓存。

 

而此例中的 X-XSS-Protection: 0 的功能是不启用XSS保护,如果值为1的话,即为启用XSS保护,这时XSS注入就没用了。

 

我们还没学怎么用XSS,就不能用了,太扯了。所以这里设了个零。(听不懂也没关系,它对本文的理解没什么影响,出来刷波存在感。)

 

我们通过输入猜到了服务器的处理功能,并且通过查看代码,也证实了我们的猜想。可以看到,后台没有对我们的输入进行任何过滤,就直接echo了出来。

 

这,就是XSS需要的。

 

试想,我们输入<script>alert("jerk")</script>,会发生什么结果?(默认你有html和js基础,不对标签基础功能进行讲解。)

 

 

Ohhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh,怎么会这样。

 

查看网页源码:

                      

服务器没对我们的输入进行过滤,直接将内容传给了浏览器,浏览器将<script></script>解析成脚本标签,alert当成js脚本代码执行了。

 

XSS原理就是这么简单,仅此而已。

 

太无聊了,我们将安全等级提高。不再LOW了:

                                   

 

同样的XSS脚本:

 

咦,脚本没有执行,还把内容显示了出来,虽然并没有标签。猜猜看后台干了什么?

 

对的,过滤"<script>":

<?php 

header ("X-XSS-Protection: 0"); 

// Is there any input? 
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { 
    // Get input 
    $name = str_replace( '<script>', '', $_GET[ 'name' ] ); 

    // Feedback for end user 
    echo "<pre>Hello ${name}</pre>"; 
} 

?> 

我们可以瞅见,多了一句str_replace。它将内容中的“<script>”去掉了(替换为空),我们的输入从<script>alert</script>变成了alert()</script>。

 

那为什么</script>没有显示出来呢?因为他会被浏览器当成标签,而不是显示内容。又因为浏览器没有找到</script>的前端<script>,标签没有闭合,所以<script>标签并没有起作用。alert被当做文本显示了出来。

 

查看网页源码:

                        

后半个的确还在,前半个没了。

 

那,怎么搞定他呢?其实方法有很多,你会接触到很多很多的绕过姿势,这里说两个最简单的。

 

1.调整大小写,后台中过滤的是“<script>”,字符串是不可变的,亲!你换成<SCRIPT>不就完了?

提交:

                  

完美。

 

调整大小写的方式很多,如somethingwithouttitle:

1.标题式调整:SomethingWithoutTitle

2.完全式调整:SOMETHINGWITHOUTTITLE

3.神经式调整:sOmeTHinGWithOutTiTle(随机调整)。

(上面这三个是我胡扯的,小声BB)

 

2.复写。这个方法在sql注入中有提到,例如你过滤script,我就在script中间任意位置再插入一个script。

                                                           

将script过滤后,我前后的内容仍旧是一个script。

 

构造XSS:<scr<script>ipt>alert("jerk")</script>

 

提交:

没问题。

 

唉,这个也没意思。再提高个难度:换成high。

          

 

这时你去尝试之前的所有方法,全部废掉。为什么呢?来看看后台怎么处理的:


<?php 

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 "<pre>Hello ${name}</pre>"; 
} 

?>

str_replace换成了preg_replace。正则匹配,看正则表达式:/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i

()内部为一个整体, 其中的.*               点代表任意字符,星表示将前一单位重复0或多次。最后的/i表示不区分大小写。

 

这属于正则内容,想深入了解的就需要你去STW和RTM了。

 

简单说,它过滤掉了所有包含<script的数据,无论你怎样复写,而且还不区分大小写。

 

跪了,没办法了,high就是high,玩不了。

 

其实不然,还是那句话,你的姿势不对。

 

<script>标签被过滤了,你就换个标签嘛。用<img>标签,在它的src中写脚本。

 

哈?img的src不是图片路径吗,还能写脚本?

 

这个可能不常见,我们常见的是在<a>标签的href写脚本的,比如:

<a href="javascript:void(0);" ></a>

<a href="javascript:;" ></a>

<a href="#" ></a>

这些可能大家常见,但是不晓得什么意思。其实他要达到的功能就是,当你点击这个超链接时,不做任何功能性处理,仅此而已。最后的那种href="#"的超链接,功能是“回到顶部”。

 

换句话说我们可以这样写:<a href="javascript:alert('jerk');"></a>,然后点击页面上的超链接来达到目的。img的src中也可以这样写。

 

那为什么要讲img呢,因为script被过滤了呀。href="javascript:alert('jerk');"里面的存在script会被过滤掉。

 

也就是说,我们的方法里不应该和script字符串有关系。

 

img标签里有个onerror函数,在src给的路径错误,图片显示不出来时,界面就会显示:

太丑了,为了处理这种情况,img有了onerror函数,当src有误时,执行处理函数,即onerror指定的函数。

 

这个功能太棒了,我们构造的XSS就故意给个无效的src,然后将脚本交给onerror。

 

构造:<img src=1 οnerrοr=alert("jerk") />

 

显然,src=1是个锤子语法,妥妥的有问题,嗳,巧了,要的就是有问题。

 

提交!!!

                       

秀~

 

还有其他方法吗?有!

 

利用body标签的onload函数。此函数在页面加载完成后立即执行指定的函数功能。

 

构造:<body οnlοad=alert("jerk")>,提交:

                          

结果没啥问题。

 

我倒是有个理解上的问题,本来页面的结构是:

<html>

<head></head>

<body></body>

</html>

 

现在结构是:

<html>

<head></head>

<body><body></body>

</html>

<body>中间有<body>,这个在浏览器解析的时候是怎么处理的?为何不会错误?我查了些资料,没找到原理性的解释,这个问题先留在这里吧,后面再补回来。如果有了解的小伙伴,欢迎评论指出,我会将您的评论贴进来。

 

全部说完,最后看看impossible安全级别的:

无论你怎样XSS,都不会有问题:

输入内容原封不动的给你输出了。怎么做到的呢?

 

view source:

<?php

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Get input
    $name = htmlspecialchars( $_GET[ 'name' ] );

    // Feedback for end user
    echo "<pre>Hello ${name}</pre>";
}

// Generate Anti-CSRF token
generateSessionToken();

?>

能迅速找到核心语句吗?

 

没错,就是:$name = htmlspecialchars( $_GET[ 'name' ] )

 

htmlspecialchars函数什么作用?我们从另一个角度出发。

 

在C语言中,输出是要进行格式化的,比如输出整数一,printf( "%d", 1)。输出换行,printf("\n")。

 

我们知道%和\在输出格式化中是有特殊用处的,那如果我想让它输出“%”和“\n”这个字符串怎么办呢?printf("%%"),printf("\\n")。即对转移字符本身进行处理。

 

类比,在html中<>不是会被解释成标签吗?那如果我想在网页上显示<>呢?这些有特殊作用的符号,我们叫做预留字符(像C中的百分号和转义符)。如果想要输出预留字符,必须使用字符实体,而这个过程,又叫“实体化”。

 

比如如果网页上显示<>,你的html中的实体字符为:&lt;&gt;

 

代码中&标记的,就是实体化字符。(你现在就可以查看源码,看看这里的<>,是什么东西。)

 

而htmlspecialchars这个函数,就是做实体化用的,它会将你输入的数据中预留字符全部转换,从而可以直接显示到界面上。不过仅此而已,但你的渗透也到此而已了,因为你所有的输入都没有被执行的机会了。

 

反射型,说完了。还剩下存储型和DOM型,我们下篇谈。XSS构造原理都是一样的,剩下的两个我们会很快的理解并解决掉它们。

 

 

 

 

后记:文章遗留问题测试。

 

<html>

<head></head>

<body onload=alert("first")>
<body onload=alert("second")></body>

</body>

</htlml>

外层body加载函数输出first,内层body加载函数输出second。

点击确定后,second并没有输出,也就是说,onload只执行一次。如果把外层去掉:

<html>

<head></head>

<body>
<body onload=alert("second")></body>

</body>

</htlml>

就输出了second。

如果内层body标签没闭合:

<html>

<head></head>

<body>

<body onload=alert("second")>

</body>

</htlml>

依旧可以输出:

一个标签,如果只有前标签(如<body>),大部分浏览器解析时会给你补充完整,它会认为你忘记了闭合。如果只有后标签(如</body>),浏览器没找到你的前标签,就认为你写错了,标签就会作废。这是浏览器的容错性,记住这个特性,在渗透中,也是可以被利用的点。

 

下一个问题,一个html文本中,有多个body是怎么回事?其实html规范是head和body是只允许有一个的,但是html的解析归浏览器管,也就是说,有没问题,浏览器说的算(测试的例子使用的浏览器是chrome)。

 

而大部分浏览器对多个body的处理是进行拼接,即将两个body的内容合并成一个显示。例:

<html>

<head></head>

<body>
This is first body

<body>Another</body>

</body>

</htlml>

内外层body写上不同的内容:

网页的显示丝毫不受影响。

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

猜你喜欢

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