Information Security 7 min read

Analysis of a ThinkPHP 6.0 Deserialization Exploit Chain via LeagueFlysystem Cached Storage

This article analyzes a ThinkPHP 6.0 deserialization exploit chain that leverages LeagueFlysystem's cached storage classes, detailing the sequence from __destruct to write, showing how controllable parameters enable arbitrary file writes and providing a proof‑of‑concept demonstration.

Laravel Tech Community
Laravel Tech Community
Laravel Tech Community
Analysis of a ThinkPHP 6.0 Deserialization Exploit Chain via LeagueFlysystem Cached Storage

Recently a new ThinkPHP 6.0 exploit chain was discovered, focusing on the LeagueFlysystem cached storage classes.

The chain begins with the LeagueFlysystemCachedStorageAbstractCache class and its __destruct() method, which calls save() when the autosave property is false.

public function __destruct(){
    if (! $this->autosave) {
        $this->save();
    }
}

To trigger save() , the attacker sets $this->autosave = false and locates a concrete subclass that implements save() . In this case the Adapter class is used.

public function save(){
    echo "save执行!
";
    $config = new Config();
    $contents = $this->getForStorage(); // $contents is fully controllable
    if ($this->adapter->has($this->file)) {
        $this->adapter->update($this->file, $contents, $config);
    } else {
        $this->adapter->write($this->file, $contents, $config);
    }
}

The getForStorage() method returns a JSON‑encoded value built from the cache, which can be manipulated by the attacker.

public function getForStorage(){
    $cleaned = $this->cleanContents($this->cache);
    return json_encode([$cleaned, $this->complete, $this->expire]);
}

The cleanContents() function simply returns the supplied array after filtering, leaving the attacker‑controlled payload untouched.

public function cleanContents(array $contents){
    $cachedProperties = array_flip([
        'path','dirname','basename','extension','filename',
        'size','mimetype','visibility','timestamp','type',
    ]);
    foreach ($contents as $path => $object) {
        if (is_array($object)) {
            $contents[$path] = array_intersect_key($object, $cachedProperties);
        }
    }
    return $contents;
}

The has() method of LeagueFlysystemAdapterLocal checks file existence using file_exists() on a path that includes a configurable prefix.

public function has($path){
    $location = $this->applyPathPrefix($path);
    return file_exists($location);
}

The prefix is obtained from getPathPrefix() , which returns the $pathPrefix property that the attacker can control.

public function getPathPrefix(){
    return $this->pathPrefix;
}

If has() returns false, the exploit proceeds to write() , which ultimately calls file_put_contents() with attacker‑controlled $path and $contents .

public function write($path, $contents, Config $config){
    echo "进入write函数!
";
    $location = $this->applyPathPrefix($path);
    $this->ensureDirectory(dirname($location));
    if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) {
        return false;
    }
    $type = 'file';
    $result = compact('contents','type','size','path');
    if ($visibility = $config->get('visibility')) {
        $result['visibility'] = $visibility;
        $this->setVisibility($path, $visibility);
    }
    return $result;
}

The helper ensureDirectory() creates missing directories but does not interfere with the file‑write operation.

protected function ensureDirectory($root){
    if (!is_dir($root)) {
        $umask = umask(0);
        @mkdir($root, $this->permissionMap['dir']['public'], true);
        umask($umask);
        if (!is_dir($root)) {
            throw new Exception(sprintf('Impossible to create the root directory "%s".', $root));
        }
    }
}

By controlling the path prefix, the target file path, and the serialized payload, the attacker can write arbitrary PHP code (e.g., a web shell) to any location on the server, as demonstrated by the provided PoC that writes axin.php containing .

The proof‑of‑concept shows the serialized payload being sent, the chain executing, and the resulting file appearing on the filesystem, confirming successful exploitation.

PHPdeserializationinformation securityexploitfile writeLeagueFlysystem
Laravel Tech Community
Written by

Laravel Tech Community

Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.