在学习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.-apache

然后通过镜像创建容器:

docker run -itd -p : --name php:7.0.-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 => =>

然后重启一下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

0ctf-ezdoor-复现分析的更多相关文章

  1. 路由器漏洞复现分析第三弹:DVRF INTRO题目分析

    这个项目的目的是来帮助人们学习X86_64之外其他架构环境,同时还帮助人们探索路由器固件里面的奥秘. 本文通过练习DVRF 中INTRO 部分的题目来学习下MIPS 结构下的各种内存攻击. DVRF: ...

  2. CVE-2021-3129:Laravel远程代码漏洞复现分析

    摘要:本文主要为大家带来CVE-2021-3129漏洞复现分析,为大家在日常工作中提供帮助. 本文分享自华为云社区<CVE-2021-3129 分析>,作者:Xuuuu . CVE-202 ...

  3. ref:spring-data-XMLBean XXE复现分析

    ref:https://blog.spoock.com/2018/05/16/cve-2018-1259/ 漏洞信息 看pivotal发布的漏洞信息如下 通过发布的漏洞信息可以知道,漏洞组件是在XML ...

  4. 路由器漏洞复现分析第二弹:CNVD-2018-01084

    1月17日,CNVD公开了D-LinkDIR 615/645/815 service.cgi远程命令执行漏洞(CNVD-2018-01084),freebuf上有前辈写了一篇漏洞复现和poc的文章(h ...

  5. Wordpress4.9.6 任意文件删除漏洞复现分析

    第一章 漏洞简介及危害分析 1.1漏洞介绍 WordPress可以说是当今最受欢迎的(我想说没有之一)基于PHP的开源CMS,其目前的全球用户高达数百万,并拥有超过4600万次的超高下载量.它是一个开 ...

  6. CVE-2020-7961 Liferay Portal 复现分析

    漏洞说明: Liferay是一个开源的Portal(认证)产品,提供对多个独立系统的内容集成,为企业信息.流程等的整合提供了一套完整的解决方案,和其他商业产品相比,Liferay有着很多优良的特性,而 ...

  7. java反序列化——apache-shiro复现分析

    本文首发于“合天智汇”公众号 作者:Fortheone 看了好久的文章才开始分析调试java的cc链,这个链算是java反序列化漏洞里的基础了.分析调试的shiro也是直接使用了cc链.首先先了解一些 ...

  8. 追洞小组 | fastjson1.2.24复现+分析

    出品|MS08067实验室(www.ms08067.com) 本文作者:爱吃芝士的小葵(Ms08067实验室追洞小组成员) 1.靶场搭建 2.漏洞复现 3.漏洞分析 4.漏洞修复 5.心得 靶场搭建 ...

  9. 泛微OA E-cology(CNVD-2019-32204)远程命令执行漏洞复现分析

    漏洞复现 影响版本: E-cology 7.0 E-cology 8.0 E-cology 8.1 E-cology 9.0   直接在网站根目录后加入组件访问路径 /weaver/bsh.servl ...

  10. struts2漏洞复现分析合集

    struts2漏洞复现合集 环境准备 tomcat安装 漏洞代码取自vulhub,使用idea进行远程调试 struts2远程调试 catalina.bat jpda start 开启debug模式, ...

随机推荐

  1. ORM框架之EntityFramework介绍

    ORM框架之EntityFramework介绍 1. 简介 大家好!我是高堂. 作为一位伪前端程序猿,我给大家介绍一下微软的自家的 ORM框架. ADO.NET Entity Framework 以下 ...

  2. DPDK latencystats库使用方案

    初始化 注意务必调用 rte_metrics_init /* init latency stats */ /* @TODO should we remove this in product env? ...

  3. [转载]linux的top命令中cpu信息的含义

    https://www.cnblogs.com/wjoyxt/p/4918742.html 原文很好,我就不摘录了.

  4. 关于工作单元模式——工作单元模式与EF结合的使用

    工作单元模式往往和仓储模式一起使用,本篇文章讲到的是工作单元模式和仓储模式一起用来在ef外面包一层,其实EF本身就是工作单元模式和仓储模式使用的经典例子,其中DbContext就是工作单元,而每个Db ...

  5. 函数——箭头函数&自执行函数(二)

    一.箭头函数是在es6中添加的一种规范,它相当于匿名函数,简化了函数的定义. 1.语法 a.function用var,let,cost来表示: b.参数要写在第一个等号后面:   参数有多个,需要加一 ...

  6. MySQL5.7 启动报错:initialize specified but the data directory has files in it. Aborting.

    $ vi /etc/my.cnf ## datadir=/var/lib/mysql, 这个是data保存目录,进入/var/lib/mysql后,查看到确实有数据. #解决方法:将/var/lib/ ...

  7. 原创js脚本实现百度网盘任意文件强制下载

    代码: //get file list data var data=require("system-core:context/context.js").instanceForSys ...

  8. JAVA语言程序设计课后习题----第七单元解析(仅供参考)

    1 本题水题,就是想让你理解继承的含义 public class Animaal { public double weight; public void eat(){ } } public class ...

  9. 【Java并发】线程通信

    一.概述 1.1 什么是多线程之间通讯? 1.2 案例 代码实现 解决线程安全问题 二.等待通知机制 2.1 示例 2.2 wait与sleep区别 三.Lock锁 3.1 概述 3.2 等待/通知机 ...

  10. go语言中regexp包中的函数和方法

    // regexp.go ------------------------------------------------------------ // 判断在 b 中能否找到正则表达式 patter ...