[MRCTF2020]套娃

又是一篇php的学习

打开环境后什么发现也没有,F12里面发现了提示

扒下来后简单审计一下

<!--
//1st
$query = $_SERVER['QUERY_STRING'];

 if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
    die('Y0u are So cutE!');
}
 if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
    echo "you are going to the next ~";
}
!-->

 首先了解一个知识点:假如有url

http://localhost/aaa/index.php?p=222&q=333

结果:
$_SERVER['QUERY_STRING'] = "p=222&q=333";
$_SERVER['REQUEST_URI'] = "/aaa/index.php?p=222&q=333";
$_SERVER['SCRIPT_NAME'] = "/aaa/index.php";
$_SERVER['PHP_SELF'] = "/aaa/index.php";

由实例可知:

$_SERVER["QUERY_STRING"] 获取查询 语句,实例中可知,获取的是?后面的值
$_SERVER["REQUEST_URI"] 获取 http://localhost 后面的值,包括/
$_SERVER["SCRIPT_NAME"] 获取当前脚本的路径,如:index.php
$_SERVER["PHP_SELF"] 当前正在执行脚本的文件名

简单来说$_SERVER['QUERY_STRING']是获取url后面的参数以及值的,然后存在了query这个变量里面。

substr_count() 函数计算子串在字符串中出现的次数。%5f是_的url编码

第一个判断语句要达成的条件是传入的变量里面不能有_或者它的编码形式。

第二个判断语句要达成的条件是需要传入一个b_u_p_t的值不能强等于字符23333并且要进行正则判断字符为23333(^不在括号内是限定字符的开头要为2,$则是限制了结尾必须为3,中间的字符也是确定的)

那么就要开始绕过了,首先要了解php解析的一些语法规则利用PHP的字符串解析特性Bypass - FreeBuf网络安全行业门户

简单来说就是php解析的时候会将某些字符转换为下划线(包括空格)

所以第一个if就很好绕过,只需要传入

  • ?b%20u%20p%20t
  • ?b u p t
  • ?b.u.p.t

就可以了

而第二个if首先要注意是强等于,要比较变量的类型和值的,我开始做的时候就没注意到,一直想传一个23333a去绕过,但我去本地尝试的时候发现弱等于其实也不行的,弱等于只有在拿数字与字符串作比较,当传入的是字符串的时候可以绕过,简而言之如果是

if($_GET['a'] == 23333)

这样的话传入23333a就可以满足条件,但实际是

if($_GET['a'] == '23333')

这样的,弱等于也满足不了,因为他是字符串与字符串进行比较了,更何况这是强等于了。

接着看题,既然是强等于,又要求判断时候不能等于字符串23333,且又要正则判断的时候等于23333,就可以用%0a进行绕过,%0a是换行符的意思,在非多行模式下,$似乎会忽略在句尾的%0a。参考这篇绕过preg_match绕过总结 - MustaphaMond - 博客园 (cnblogs.com)那就可以构造payload:

  • ?b%20u%20p%20t=23333%0a
  • ?b u p t=23333%0a
  • ?b.u.p.t=23333%0a

当变量以GET形式通过url传入后端的时候,会先进行一次解析,也就是说传入的%0a进入后端时变成了\n,要注意GET传入后的变量到达后端后是以""的形式包起来的,也就是说\n会被解析为换行符。然后进行比较后不相等,在进行正则匹配时\n也会被解析为换行符,还是相当于23333,则绕过了限制。

又提示我们flag在secrettw.php里面,于是又看到了这个

这个其实是js的一种加密方式jsfuck。想深入了解可以看看

JSFuck 有趣的js加密 (360doc.com)

(12条消息) JSfuck原理解析一——基础原理_直接开车的博客-CSDN博客_jsfuck

他还说了ip不对,可能会修改ip。因为是js,所以直接拿到控制台回车执行看结果就行了。

叫post传一个Merak上去 ,那就随便传一个值看看结果

Flag is here~But how to get it? <?php 
error_reporting(0); 
include 'takeip.php';
ini_set('open_basedir','.'); 
include 'flag.php';

if(isset($_POST['Merak'])){ 
    highlight_file(__FILE__); 
    die(); 
} 


function change($v){ 
    $v = base64_decode($v); 
    $re = ''; 
    for($i=0;$i<strlen($v);$i++){ 
        $re .= chr ( ord ($v[$i]) + $i*2 ); 
    } 
    return $re; 
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission!  Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?> 

 可以简单的分析出secrettw.php的作用

ini_set 是为一个配置选项设置值,open_basedir 将PHP所能打开的文件限制在指定的目录树中,包括文件本身。当程序要使用例如fopen()或file_get_contents()打开一个文件时,这个文件的位置将会被检查。当文件在指定的目录树之外,程序将拒绝打开。这里就是只允许你去包含根目录里面的存在的文件,不让你去包含自己的一句话木马之类的。
首先用if(isset($_POST['Merak']))函数检测是否存在参数名为Merak的POST数据,如果不存在则执行下面的语句,如果存在则执行if中的highlight_file函数高亮显示源码

我们已经通过POST提交Merak知道了源码,后面就不用再提交POST了,不然会被highlight_file函数截断,继续看下面的语句,中间的change函数暂时不管,是转换字符用的,后面会提到
后面的$ip = getIp();应该是使用了头部的takeip.php中的函数来获取客户端ip,再将获取到的ip赋值给变量$ip,因为getIp并不是内置函数,是自定义的。
然后有两个if,第一个要让获取的ip等于127.0.0.1,第二个也是让获取的ip等于127.0.0.1并且以GET方式传入一个变量,他的内容要等于todat is a happy day。

第一个条件$ip === '127.0.0.1',这个很容易满足,只要让get_ip获取到的值为127.0.0.1就行了,一般只有XFF和Client-ip或者REMOTE-ADDR这几种方法,这里只有CLIENT-IP行得通,我们可以用burpsuite来提交

第二个条件如果是以post的方式可以用php://input去绕过,这里是用GET传,可以用data://text/plain;base64,去绕过,这样也避免了前面不允许传其他文件的过滤。base64加密todat is a happy day后是dG9kYXQgaXMgYSBoYXBweSBkYXk=。所以需要通过get提交一个名为2333的参数,值为data://text/plain;base64,dG9kYXQgaXMgYSBoYXBweSBkYXk=

最后就是要执行echo file_get_contents(change($_GET['file'])); 这个句子了,很明显只要让change过后的东西等于flag.php被读取后就可以echo出来了。

首先定义用法,然后将变量进行base64解码(这说明后面POST参数file的值必须先进行base64编码),然后通过一段for循环,(参数后面加上[]是把字符串当做数组看)这段for循环的作用是先依次将字符转换为ASCII码,再将ASCII码逐步+$i*2$i初始值为0,然后再转回字符,最后通过$re.相当于$re=$re.chr ( ord ($v[$i]) + $i*2 )把字符串重新拼接起来组成一个新的$re返回出去。
其中strlen函数作用是计算字符的数目,chr是把ASCII转成字符,ord是把字符转成ASCII数字

解密直接把中间+改成-就可以了,解密出来是这样的

再把它进行base64加密等于ZmpdYSZmXGI= 最后传入得到flag

 也可以看到takeip.php里面是这样的

 其实我本来还想用file=php://filter/read=convert.base64-encode/resource=./flag.php的方式去读取flag的,但是他用change解码后是这样的

有乱码的形式于是我就放弃了,想了想应该是有include包含的话没有回显更多会用这种方法,file_get_contents是读取文件内容的直接传入文件就好了。但理论上我觉得应该是可以的,他去把flag的内容以base64读取出来后echo出来,然后去解码应该也可以得到。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值