Summary
The admin settings update endpoint accepted a fully qualified class name
directly from user-supplied request input and used it for dynamic static
method calls and object instantiation without any allowlist validation.
An authenticated admin-level user could supply an arbitrary class name
available in the Composer autoloader, potentially triggering unintended
constructor or magic method execution.
Details
In app/Http/Controllers/Admin/SettingsController.php, the update()
method read settings_class directly from the HTTP request and passed
it to new $settings_class() and $settings_class::getValidations()
without verifying that the provided value corresponds to a legitimate
settings class:
// Vulnerable code (before fix)
$settings_class = (string) request()->get('settings_class');
if (method_exists($settings_class, 'getValidations')) {
$validations = $settings_class::getValidations();
}
$settingsClass = new $settings_class();
Because PHP resolves class names against the Composer autoloader at
runtime, any autoloadable class in the application or its dependencies
could be instantiated. Depending on the classes available in the
dependency tree, this can trigger unintended side effects through
constructors or magic methods (__construct, __toString, __wakeup),
following a PHP object injection / gadget chain pattern.
There was no allowlist, interface check, or category-to-class binding
in place prior to the fix.
PoC
- Log in as a user with admin settings write permission.
- Send the following POST request with a valid CSRF token:
POST /admin/settings HTTP/1.1
Host: <panel_url>
Cookie: <admin_session_cookie>
Content-Type: application/x-www-form-urlencoded
_token=<csrf_token>&category=general&settings_class=Some\Autoloadable\VendorClass
- Observe that the server accepts the request and attempts to instantiate
the provided class rather than rejecting it as invalid.
Expected behavior: request is rejected unless settings_class matches
the server-side resolved class for the given category.
Observed behavior: the value from the request is used directly for
static calls and object instantiation.
Impact
An authenticated attacker with admin settings access can force the
application to instantiate arbitrary autoloadable classes. The practical
impact depends on the classes available in the Composer dependency tree:
- In the worst case - if a suitable gadget chain exists among
installed dependencies, this can lead to remote code execution,
arbitrary file read/write, or server-side request forgery
- In all cases - unintended constructor side effects can cause
application instability or information disclosure
Risk is elevated in environments with broad admin delegation or where
admin credentials may be compromised.
Remediation
The fix introduces a server-side category-to-class map built by scanning
app/Settings and extension settings classes, filtered to only include
subclasses of Spatie\LaravelSettings\Settings. The settings_class
value from the request is then validated against this map before any
instantiation occurs:
$category = strtolower((string) $request->input('category'));
$settingsClassMap = $this->getSettingsCategoryClassMap();
if (!isset($settingsClassMap[$category])) {
abort(400, 'Invalid settings category.');
}
$resolvedSettingsClass = $settingsClassMap[$category];
$requestedSettingsClass = (string) $request->input('settings_class');
if ($requestedSettingsClass !== $resolvedSettingsClass) {
abort(400, 'Invalid settings class.');
}
This ensures that only legitimate settings classes can be instantiated,
regardless of what the request contains.
Summary
The admin settings update endpoint accepted a fully qualified class name
directly from user-supplied request input and used it for dynamic static
method calls and object instantiation without any allowlist validation.
An authenticated admin-level user could supply an arbitrary class name
available in the Composer autoloader, potentially triggering unintended
constructor or magic method execution.
Details
In
app/Http/Controllers/Admin/SettingsController.php, theupdate()method read
settings_classdirectly from the HTTP request and passedit to
new $settings_class()and$settings_class::getValidations()without verifying that the provided value corresponds to a legitimate
settings class:
Because PHP resolves class names against the Composer autoloader at
runtime, any autoloadable class in the application or its dependencies
could be instantiated. Depending on the classes available in the
dependency tree, this can trigger unintended side effects through
constructors or magic methods (
__construct,__toString,__wakeup),following a PHP object injection / gadget chain pattern.
There was no allowlist, interface check, or category-to-class binding
in place prior to the fix.
PoC
the provided class rather than rejecting it as invalid.
Expected behavior: request is rejected unless
settings_classmatchesthe server-side resolved class for the given category.
Observed behavior: the value from the request is used directly for
static calls and object instantiation.
Impact
An authenticated attacker with admin settings access can force the
application to instantiate arbitrary autoloadable classes. The practical
impact depends on the classes available in the Composer dependency tree:
installed dependencies, this can lead to remote code execution,
arbitrary file read/write, or server-side request forgery
application instability or information disclosure
Risk is elevated in environments with broad admin delegation or where
admin credentials may be compromised.
Remediation
The fix introduces a server-side category-to-class map built by scanning
app/Settingsand extension settings classes, filtered to only includesubclasses of
Spatie\LaravelSettings\Settings. Thesettings_classvalue from the request is then validated against this map before any
instantiation occurs:
This ensures that only legitimate settings classes can be instantiated,
regardless of what the request contains.