CTF Web安全

[PWNHUB 公开赛 2018]傻 fufu 的工作日(上传差异绕过)

Posted on 2020-04-01,6 min read

访问index.php.bak得到index.php的源码

<?php /* PHP Encode by  http://Www.PHPJiaMi.Com/ */error_reporting(0);ini_set("display_errors", 0);if(!defined('jnggfmpt')){define('jnggfmpt',__FILE__);if(!function_exists("鯒仅熫")){functi.省略。。。。。。

可以看出这是个phpjiami的加密文件。得解密

这里直接看到了个脚本

https://vsalw.com/wp-content/uploads/phpjiami.zip

将加密文件放入encode目录下。然后访问index.php即可
index.php

<?php
if($_FILES) {
    include 'UploadFile.class.php';
    $dist = 'upload';
    $upload = new UploadFile($dist, 'upfile');
    $data = $upload->upload();
}
?>

UploadFile.class.php

<?php

class UploadFile {
    public $error = '';

    protected $field;
    protected $allow_ext;
    protected $allow_size;
    protected $dist_path;
    protected $new_path;

    function __construct($dist_path, $field='upfile', $new_name='random', $allow_ext=['gif', 'jpg', 'jpeg', 'png'], $allow_size=102400)
    {
        $this->field = $field;
        $this->allow_ext = $allow_ext;
        $this->allow_size = $allow_size;
        $this->dist_path = realpath($dist_path);

        if ($new_name === 'random') {
            $this->new_name = uniqid();
        } elseif (is_string($new_name)) {
            $this->new_name = $new_name;
        } else {
            $this->new_name = null;
        }
    }

    protected function codeToMessage($code) 
    { 
        switch ($code) {
            case UPLOAD_ERR_INI_SIZE: 
                $message = "The uploaded file exceeds the upload_max_filesize directive in php.ini"; 
                break; 
            case UPLOAD_ERR_FORM_SIZE: 
                $message = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form";
                break; 
            case UPLOAD_ERR_PARTIAL: 
                $message = "The uploaded file was only partially uploaded"; 
                break; 
            case UPLOAD_ERR_NO_FILE: 
                $message = "No file was uploaded"; 
                break; 
            case UPLOAD_ERR_NO_TMP_DIR: 
                $message = "Missing a temporary folder"; 
                break; 
            case UPLOAD_ERR_CANT_WRITE: 
                $message = "Failed to write file to disk"; 
                break; 
            case UPLOAD_ERR_EXTENSION: 
                $message = "File upload stopped by extension"; 
                break; 
            default: 
                $message = "Unknown upload error"; 
                break; 
        } 
        return $message; 
    } 

    protected function error($info)
    {
        $this->error = $info;
        return false;
    }

    public function upload()
    {
        if(empty($_FILES[$this->field])) {
            return $this->error('上传文件为空');
        }
        if(is_array($_FILES[$this->field]['error'])) {
            return $this->error('一次只能上传一个文件');
        }
        if($_FILES[$this->field]['error'] != UPLOAD_ERR_OK) {
            return $this->error($this->codeToMessage($_FILES[$this->field]['error']));
        }
        $filename = !empty($_POST[$this->field]) ? $_POST[$this->field] : $_FILES[$this->field]['name'];
        if(!is_array($filename)) {
            $filename = explode('.', $filename);
        }
        foreach ($filename as $name) {
            if(preg_match('#[<>:"/\\|?*.]#is', $name)) {
                return $this->error('文件名中包含非法字符');
            }
        }

        if($_FILES[$this->field]['size'] > $this->allow_size) {
            return $this->error('你上传的文件太大');
        }
        if(!in_array($filename[count($filename)-1], $this->allow_ext)) {
            return $this->error('只允许上传图片文件');
        }

        // 用.分割文件名,只保留首尾两个字符串,防御Apache解析漏洞
        $origin_name = current($filename);
        $ext = end($filename);
        $new_name = ($this->new_name ? $this->new_name : $origin_name) . '.' . $ext;
        $target_fullpath = $this->dist_path . DIRECTORY_SEPARATOR . $new_name;

        // 创建目录
        if(!is_dir($this->dist_path)) {
            mkdir($this->dist_path);
        }

        if(is_uploaded_file($_FILES[$this->field]['tmp_name']) && move_uploaded_file($_FILES[$this->field]['tmp_name'], $target_fullpath)) {
            // Success upload
        } elseif (rename($_FILES[$this->field]['tmp_name'], $target_fullpath)) {
            // Success upload
        } else {
            return $this->error('写入文件失败,可能是目标目录不可写');
        }

        return [
            'name' => $origin_name,
            'filename' => $new_name,
            'type' => $ext
        ];
    }
}

关键文件上传后缀名有关的代码就几行。

    public function upload()
    {
        if(empty($_FILES[$this->field])) {
            return $this->error('上传文件为空');
        }
        if(is_array($_FILES[$this->field]['error'])) {
            return $this->error('一次只能上传一个文件');
        }
        if($_FILES[$this->field]['error'] != UPLOAD_ERR_OK) {
            return $this->error($this->codeToMessage($_FILES[$this->field]['error']));
        }
        $filename = !empty($_POST[$this->field]) ? $_POST[$this->field] : $_FILES[$this->field]['name'];
        if(!is_array($filename)) {
            $filename = explode('.', $filename);
        }
        foreach ($filename as $name) {
            if(preg_match('#[<>:"/\\|?*.]#is', $name)) {
                return $this->error('文件名中包含非法字符');
            }
        }

        if($_FILES[$this->field]['size'] > $this->allow_size) {
            return $this->error('你上传的文件太大');
        }
        if(!in_array($filename[count($filename)-1], $this->allow_ext)) {
            return $this->error('只允许上传图片文件');
        }

        // 用.分割文件名,只保留首尾两个字符串,防御Apache解析漏洞
        $origin_name = current($filename);
        $ext = end($filename);
        $new_name = ($this->new_name ? $this->new_name : $origin_name) . '.' . $ext;
        $target_fullpath = $this->dist_path . DIRECTORY_SEPARATOR . $new_name;

这里用$_FILES来判断多文件上传。用$_POST来进行后缀名操作
$_POST['upfile']赋值为$filename。然后以点号分割。取count(数组)-1。然后取数组最后的一个元素当后缀名。
这里就存在一个差异。

当我post一个数组时。它并不算在$_FILES里面。只是单纯的作为post数据。那么自然可以绕过多文件上传
然后就是绕过后缀名了。
众所周知。我们可以以数组的方式提交参数。例如a[1]=test
php接受到的是array(1=>'test')
那么我们post一个数字。类似于array(1=>'jpg',2=>'php')
当进行count(数组)-1来判断文件扩展名时
会匹配到数组[1]。那么就是jpg。绕过判断
然后取数组中的最后一个元素当后缀名。取到的却是php

之后就拿flag了。。

下一篇: [NCTF2019]phar matches everything(phar反序列化)→