zoukankan      html  css  js  c++  java
  • 0ctf-ezdoor-复现分析

    在学习opcache的时候,看到了这个题目,刚好有环境,就来复现一下,这个题目也让我学到了很多。

    创建镜像:

    docker build -t 0ctf-ezdoor .

    启动容器:

    docker run -itd -p 9010:80 --name 0ctf-ezdoor 0ctf-ezdoor

    源码如下:

    <?php
    
    error_reporting(0);
    
    $dir = 'sandbox/' . sha1($_SERVER['REMOTE_ADDR']) . '/';  //创建一个用户沙盒
    if(!file_exists($dir)){  
      mkdir($dir);
    }  //每次访问页面不存在该目录时都将重新创建,
    if(!file_exists($dir . "index.php")){
      touch($dir . "index.php"); //如果index.php不存在,则直接用touch创建
    } 
    function clear($dir) { if(!is_dir($dir)){ unlink($dir); return; } //如果不是目录,则直接删除 foreach (scandir($dir) as $file) { //如果是目录,则删除该目录下的所有文件 if (in_array($file, [".", ".."])) { continue; } unlink($dir . $file); } rmdir($dir); //然后删除目录 } switch ($_GET["action"] ?? "") { case 'pwd': echo $dir; //显示沙盒路径 break; case 'phpinfo': echo file_get_contents("phpinfo.txt"); //显示phpinfo信息 break; case 'reset': clear($dir); break; case 'time': echo time(); break; case 'upload': if (!isset($_GET["name"]) || !isset($_FILES['file'])) { break; } if($_FILES['file']['size'] > 100000) { clear($dir); break; } $name = $dir . $_GET["name"]; if (preg_match("/[^a-zA-Z0-9./]/", $name) || stristr(pathinfo($name)["extension"], "h")) { //取文件的后缀并且过滤了h,
    则所有php后缀都不能上传后面的stristr(pathinfo)是用来判断以“.”隔断后的字符串中是否含有“h”字符,在这里pathinfo是以字符串中最后一个“.”来进行隔断。
    break; } move_uploaded_file($_FILES['file']['tmp_name'], $name); $size = 0; foreach (scandir($dir) as $file) { if (in_array($file, [".", ".."])) { continue; } $size += filesize($dir . $file); } if ($size > 100000) { clear($dir); } break; case 'shell': ini_set("open_basedir", "/var/www/html/$dir:/var/www/html/flag"); include $dir . "index.php"; break; default: highlight_file(__FILE__); break; }

    最终包含的是$dir."index.php",并且无法上传php后缀,最后有include,那么应该是包含shell,所以我们如果能够通过上传覆盖index.php,就能够getshell,然后根据给出的flag路径去读flag

    此时首先读一下phpinfo,这个是个txt的phpinfo信息,并且里面删除了一些配置项, 在里面发现opcache是开启的,

    预期解法:

    opcache突破口

    A网站的网页index.php具有缓存文件index.php.bin
    而访问index.php的时候加载缓存index.php.bin
    倘若这时候具有上传,我们可以覆盖index.php.bin
    是不是就会加载我们的恶意文件了呢?
    题目中虽然过滤php类型的结尾,但是却未过滤bin的结尾

     

    通过opcache.file_cache可以看到opcache的存储路径信息在/tmp/cache下

    执行docker exec -it bash_name bash 进入docker容器发现实际上目录是cache/systemid,就是每个用户都会有一个id,来鉴别的

    所以我们的目的很明确,就是去覆盖此index.php.bin来上传我们自己的index.php.bin,那么当再次访问index.php.bin时实际上就是访问的我们的恶意index.php文件

    所以首先要知道服务器缓存文件的目录,计算一下服务器端的systemid,利用https://github.com/GoSecure/php7-opcache-override,因为代码需要指定一个phpinfo的页面,但是其最终解析出来进行计算的一些配置项才是最重要的,因此找到目标服务器的这些配置项

     

    php_version=7.0.28

    zend_extension_id=API320151012,NTS

    zend_bin_id由这两部分组成

    即zend_bin_id=BIN_SIZEOF_CHAR48888

    接下来利用公式计算一下就能得到:

    systemid=7badddeddbd076fe8352e80d8ddf3e73

     但是这个systemid计算出来的跟我在docker容器里面看到的名字不一样,这里查看一下php的版本,题目给的是7.0.28,但是此时我复现的时候从hub库拖过来的php7版本是7.0.33,因此这里计算systemid时要和题目的php版本一致,这里把php的版本改成7.0.33计算一下就行了,得到system_id为0b8bd94e9858e5d32d058dc0acf75014

     

    和我docker是相符的,说明没问题,此时已经得到了服务器端的opcache路径,那么下一步就是通过上传去覆盖此index.php.bin

    opcache文件生成

    首先要在本地搭一个根目标服务器一样的环境,所以pull一个环境下来:

    sudo docker pull php:7.0.33-apache

    然后通过镜像创建容器:

    docker run -itd -p 9010:80 --name php:7.0.33-apache opcache

    此时容器已经起来了,进入配置与服务器相同的路径

    docker exec -it opcache bash

    因为我们的index.php在用户的沙盒中,因此可以使用pwd先看看路径

     

    可以看到路径为sandbox/fac849dc498b60000e200f3f2a2712b54da39b92/,所以首先新建一个文件夹吧,然后开一个index.php,先看看能不能成功

    此时文件生成了,需要配置一下php.ini的opcache

    直接从docker hub拉来的镜像我发现里面没有加载php.ini,但是看到加载php.ini的路径为/usr/loca/etc/php,所以去这个目录看看,

    应该是给了两个ini,选定一个更名为php.ini让apache去加载,所以cp拷贝一份就行了,我配置了如下选项:

    zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20151012/opcache.so  //默认存在该扩展,只要把so文件引进来即可
    opcache.file_cache => /tmp/cache => /tmp/cache opcache.enable => On => On opcache.validate_timestamps => On => On opcache.file_cache_only => 1 => 1

    然后重启一下apache,这里不能用service apache2 restart,容器会断掉,因为容器相当于一个cmd环境,重启自己会断,这里用reload重新加载一下配置文件,此时刷新phpinfo看看

     

    opcache扩展也打开了,访问我们模拟环境的index.php试试生成bin文件:

     

    此时,因为目标服务器timestamps为on,因此我们不仅要置换bin里面的systemid还要置换一下timestamps

    运行以下代码就能获得最新的文件时间戳:

    import requests
    print requests.get('http://127.0.0.1:8585/index.php?action=time').content
    print requests.get('http://127.0.0.1:8585/index.php?action=reset').content
    print requests.get('http://127.0.0.1:8585/index.php?action=time').content

     

    此时只要用010修改一些systemid和timestamps,直接使用https://github.com/GoSecure/php7-opcache-override中提供的模板文件来帮助我们解析bin文件,此时就能看到要修改的两个字段

     修改以后保存,然后本地构造上传表单,进行上传,因为我们想要覆盖目标服务器的bin文件,那么路径必须与其相同才行,这里直接将$_GET['name']与沙盒路径拼接在了一起,没有对变量进行过滤

    所以此时确定路径可以进行路径穿越,可以穿越到任意目录,所以可以直接通过systemid来构造路径为:

    ../../../../../tmp/cache/0b8bd94e9858e5d32d058dc0acf75014/var/www/html/sandbox/fac849dc498b60000e200f3f2a2712b54da39b92/index.php.bin
    <html>
    <body>
     <form action="http://127.0.0.1:8585/?action=upload&name=../../../../../tmp/cache/0b8bd94e9858e5d32d058dc0acf75014/var/www/html/sandbox/fac849dc498b60000e200f3f2a2712b54da39b92/index.php.bin" method="post" enctype="multipart/form-data">
     <input type="file" name="file" />
     <input type="submit" value="upload" />
     </form>
    </body>
    </html>

    然后上传我们的bin文件,此时再访问action=shell,来触发index.php加载我们bin文件

     

    由上图可以看到此时已经成功加载了我们的bin文件,我们继续读一下/var/www/html和/var/www/html/flag目录,可以看到服务器做了限制,只能读到flag目录有个奇怪的文件,可以读一读它

     接下来读一下这个文件

    将其base64解码以后存到本地的flag.php.bin文件中,拖到010进行分析,发现解析出来其systemid出现了错误,对比一下正常的bin文件发现其头部少了一个字节00

    所以在其magic头部补充一个字节就行了,此时systemid还原正常,其它字段的值也正常了,接下来就要让bin文件还原成我们可以阅读的代码或者语言,做到这web部分结束,暂时不往下看了==

    非预期解: 

    1.通过条件竞争

    因为pathinfo会获取最后一个点之后的扩展,通过index.php/. 就可以绕过pathinfo,但是move_uploaded_file这个函数在调用stat检测到index.php存在时就不会进行覆盖,也就是我们能够上传但是却不能

    进行覆盖,但是这里关注这一段代码

    function clear($dir)
    {
      if(!is_dir($dir)){
        unlink($dir);
        return;
      } //如果不是目录,则直接删除
      foreach (scandir($dir) as $file) {  //如果是目录,则删除该目录下的所有文件
        if (in_array($file, [".", ".."])) {
          continue;
        }
        unlink($dir . $file);
      }
      rmdir($dir); //然后删除目录
    }

    删除时,先删除目录中的文件,再删除目录,那么我们知道如果目录里面有文件调用rmdir将无法删除,所以我们可以给要删除的目录传递大量没用的文件,那么在还在scandir的for循环结束时,原有的正常的index.php

    也被删除了,此时沙盒中有还有无用文件,不能删除此沙盒目录,因此我们可以再上传自己的index.php,然后以此来getshell

    2.绕过php底层文件操作函数

    x/../index.php/. 直接将路径修改为该路径,就可以覆盖原来的index.php,因为首先php调用tsrm_realpath去掉/.将其转换为一个标准路径,然后调用lstst获取文件属性,也就是判断文件存不存在,不存在将写文件,x/../index.php将绕过lstat让其认为index.php不存在所以重新写入,所以可以getshell

    参考:

     https://www.kingkk.com/2018/04/2018-0ctf-ezdoor%E5%88%86%E6%9E%90/#%E5%8F%A6%E4%B8%80%E7%A7%8D%E9%AA%9A%E6%93%8D%E4%BD%9C

    https://www.cdxy.me/?p=790

    http://pupiles.com/%E7%94%B1%E4%B8%80%E9%81%93ctf%E9%A2%98%E5%BC%95%E5%8F%91%E7%9A%84%E6%80%9D%E8%80%83.html

    https://lorexxar.cn/2016/05/27/opcache-jcfx/

    http://wonderkun.cc/index.html/?p=626

    https://skysec.top/2018/04/11/0ctf-ezdoor/#%E9%A2%84%E6%9C%9F%E8%A7%A3

    http://elssm.top/2018/05/04/2018-0ctf-ezdoor%E5%A4%8D%E7%8E%B0/

    https://altman.vip/2018/10/10/0ctf-Ezdoor/#%E6%89%A7%E8%A1%8C%E5%91%BD%E4%BB%A4

    https://www.angelwhu.com/blog/?p=438

  • 相关阅读:
    6-快捷键
    5(拓展)-页面内跳转
    4-html图片与链接
    3-html块-语义化的标签
    2-html标题、段落、换行与常用的字符实体
    1-html基本结构与编写规范
    BeautifulSoup模板简单应用-提取html指定数据(api_name/api_method/api_path,请求body/请求header/pagam参数)
    多线程执行sql报错处理
    jmeter配置分布式调度:远程启动其他机器实现多台pc一起并发
    5. openCV中常用函数学习
  • 原文地址:https://www.cnblogs.com/tr1ple/p/11194452.html
Copyright © 2011-2022 走看看