Skip to content

security: restrict allowed_classes in ClosureJob::run() to prevent PHP Object Injection#41568

Open
XananasX7 wants to merge 1 commit into
owncloud:masterfrom
XananasX7:security/command-unserialize-allowed-classes
Open

security: restrict allowed_classes in ClosureJob::run() to prevent PHP Object Injection#41568
XananasX7 wants to merge 1 commit into
owncloud:masterfrom
XananasX7:security/command-unserialize-allowed-classes

Conversation

@XananasX7
Copy link
Copy Markdown

Summary

ClosureJob::run() calls \unserialize($serializedCallable) without an allowed_classes restriction. The serialized job payload is stored in the database; an attacker who can write to the job table (e.g. via SQL injection or a compromised admin account) can inject a PHP Object Injection payload that triggers a gadget chain during deserialization, potentially leading to Remote Code Execution.

Root Cause

// lib/private/Command/ClosureJob.php (before)
$serializedClosure = \unserialize($serializedCallable);

AsyncBus::push() always wraps closures in a Laravel\SerializableClosure\SerializableClosure (line 115 of AsyncBus.php). The class that should appear after deserialization is always SerializableClosure — no other class is legitimate here.

Fix

$serializedClosure = \unserialize($serializedCallable, ['allowed_classes' => [SerializableClosure::class]]);

This prevents instantiation of arbitrary gadget classes during deserialization without changing behaviour for any legitimate job payload.

Impact

CWE CWE-502: Deserialization of Untrusted Data
Vector Attacker writes to oc_jobs table → unserialize() → gadget chain → RCE
Precondition Write access to the ownCloud job table

No Behaviour Change

All legitimate ClosureJob entries are serialized SerializableClosure objects; restricting to exactly that class does not affect them.

…P Object Injection

ClosureJob::run() calls unserialize($serializedCallable) without an
allowed_classes restriction. The serialized closure is stored in the
database job queue (oc_jobs / oc_clndr_appt_queue etc.); an attacker who
can write to those tables could inject a gadget chain.

AsyncBus::push() always serializes closures as Laravel\SerializableClosure
objects (see AsyncBus.php line 115). Restricting to [SerializableClosure::class]
prevents instantiation of arbitrary gadget classes during deserialization
without changing behaviour for legitimate jobs.
@update-docs
Copy link
Copy Markdown

update-docs Bot commented May 31, 2026

Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would create a changelog item based on your changes.

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@XananasX7
Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants