php7.0 ctf,0CTF2018之ezDoor的全盘非预期解法

本文详细解析了一段涉及PHP文件上传、路径遍历及opcode cache利用的代码,探讨了如何绕过pathinfo限制,利用时间差攻击防止文件夹被删除,以及在不同PHP版本间进行opcode cache的解析和逆向。通过实际操作,作者展示了如何构造非预期解来完成挑战,包括修改库文件以适配不同PHP版本,并使用vld扩展辅助逆向工程,最终解密得到flag。
摘要由CSDN通过智能技术生成

case 'upload':

if (preg_match("/[^a-zA-Z0-9.\/]/", $name) ||

stristr(pathinfo($name)["extension"], "h")) {

break;

}

move_uploaded_file($_FILES['file']['tmp_name'], $name);

case 'shell':

ini_set("open_basedir", "/var/www/html/$dir:/var/www/html/flag");

include $dir . "index.php";

看到这几行代码,我们的思路就很明显了:绕过pathinfo限制,然后通过上传某个文件get shell。预期解法是上传一个opcache覆盖,而非预期解法是直接上传index.php。

怎么直接上传呢?我们只要传入index.php/.就可以绕过pathinfo的检测了。但这里有个坑,源自于move_uploaded_file。

我们看一下系统调用:

ae97948359637c71d939cca88d0975a4.png

对应move_uploaded_file的源码,简单来说,它就是首先尝试rename,如果失败,就使用copy。

if (VCWD_RENAME(path, new_path) == 0) {

successful = 1;

} else if (php_copy_file_ex(path, new_path, STREAM_DISABLE_OPEN_BASEDIR) == SUCCESS) {

VCWD_UNLINK(path);

successful = 1;

}

但是,如果文件已经存在的话,人家stat后就不继续打开文件了。于是返回失败。

3086438d397aad23e5aa6791a3014bbd.png

那这个问题怎么解决呢?当然是删掉它啊!看到题目中有以下代码

if(!file_exists($dir)){

mkdir($dir);

}

if(!file_exists($dir . "index.php")){

touch($dir . "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);

}

case 'reset':

clear($dir);

break;

……嗯,好像不太好删。删掉会连带着把文件夹删掉,重新访问时又会创建。注意到上传文件时存在任意路径上传漏洞,好,就拿你开刀。我们发现,删除文件夹方式是先scandir,再rmdir。如果我们能在它scandir以后再写入文件,就可以阻止文件夹被rmdir。同时,因为index.php肯定存在,所以它也会被删除。这就达成我们需要的效果。

准备两个IP,下称IP A与IP B。都先pwd拿到他们的路径。

然后,通过Burpsuite,让IP A往IP B的sandbox里写大量文件。

f578af4acefbc6a862533a524c1e77b5.png

在这同时,使用IP B访问reset,打时间差。如果顺利的话,index.php就能被删除成功。再通过IP 1上传Shell。

49ab1fbca25eb7437063bd962ca36cfa.png

就这样,Shell传上去了。

845267aaf0ca4ac54ce53398f1749cb4.png

通过base64_encode拿到文件后,发现是一个opcode cache。服务器是PHP 7.0,于是切换到7.0环境。使用此库:https://github.com/GoSecure/php7-opcache-override.git 。但会发现,解析不了。到底是怎么回事呢?

魔改一下这个库嘛,加点调试信息,明显就看到这个文件头哪里不太对劲。

b8982760a9f320df00e898e38bdb00fc.png

参照正确的Opcode格式,在Opcode头与 system_id 处加上一个00,问题解决,可以解析了。

f3aa5927ceffb6bf70a31ecfd6a0b3cb.png

但这个库解析出来的东西是正常人能看得下去的吗?JMP的地址不知道是什么,变量名全是None。

5fd99ed340e13bee5f0216a6062ec68b.png

这个时候,就要祭出大杀器 vld 了。我在之前的文章(https://blog.zsxsoft.com/post/30 )里提到过这个库。因此,通过以下参数,启动一个PHP服务器,并强制打开vld扩展。

b2550a3077ac226bf58fb4a33f798083.png

生成Opcache文件后,我们就知道,我们自己的服务器的 system_id 是多少了。将我们从Shell获取的 Opcache 里面的 system_id 替换成自己服务器上的。

381951f29b570159fb555a8511478a67.png

dba5406ab6580839a346a1787f328253.png

我们执行以下,发现替换成功。

2889ceb68662829d3403360f0377d9bb.png

回到php -S,看看vld给我们反馈了什么:

57096a1e430acb7de2e4ea3abd1b31a6.png

有变量名了!好看多了!没错,我就是用第一部分的预期解做第二部分的题。于是手工逆向之,出以下代码。

function encode ($string) {

$hex = '';

for ($i = 0; $i < strlen($string); ++$i) {

$tmp = dechex(ord($string[$i]));

if (strlen($tmp) == 1) {

$hex .= '0' . $tmp;

} else {

$hex .= $tmp;

}

}

return $hex;

}

function encrypt($pwd, $data) {

mt_srand(1337);

$cipher = '';

$pwd_length = strlen($pwd);

$data_length = strlen($data);

for ($i = 0; $i < $data_length; ++$i) {

$cipher .= chr((ord($data[$i]) ^ ord($pwd[$i % $pwd_length])) ^ mt_rand(0, 255));

}

return encode($cipher);

}

$flag = 'input_your_flag_here';

if (encrypt('this_is_a_very_secret_key', $flag) === '85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab') {

echo 'Congratulation! You got it!';

} else {

echo 'Wrong Answer';

}

解题脚本:

function decrypt($key) {

mt_srand(1337);

$f = [133, 185, 84, 252, 131, 128, 164, 102, 39, 110, 74, 72, 36, 157, 221, 74, 25, 159, 195, 78, 91, 6, 20, 100, 228, 41, 95, 197, 2, 12, 136, 191, 216, 84, 85, 25, 171];

$keyLength = strlen($key);

$flagLength = count($f);

for ($i = 0; $i < $flagLength; $i++) {

$flag[$i] = chr($f[$i] ^ mt_rand(0, 255) ^ ord($key[$i]));

print_r($flag[$i]);

}

}

flag{0pc4che_b4ckd00r_is_4_g0o6_ide4}

——然后这里又有一个大坑了。服务器上的PHP是7.0.8,于是我打算用Shell从服务器上取rand序列。但取不动。后来发现,本地的PHP 7.2可以直接跑动以上脚本。翻了下changelog,7.1起貌似改了PHP的mt_rand……

好了,这题的全盘非预期解做法结束。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值