From 21d86327b905135953f7005949a50521484a45d8 Mon Sep 17 00:00:00 2001 From: FreeScout Date: Fri, 22 Sep 2023 04:52:43 -0700 Subject: [PATCH] Fix Guard bypass in Eloquent models in laravel/framework --- composer.json | 4 + .../Eloquent/Concerns/GuardsAttributes.php | 226 ++++++++++++++++ .../Illuminate/Database/Eloquent/Model.php | 3 +- .../src/Illuminate/Queue/Listener.php | 248 ++++++++++++++++++ .../src/Illuminate/Support/Collection.php | 2 +- 5 files changed, 481 insertions(+), 2 deletions(-) create mode 100644 overrides/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php create mode 100644 overrides/laravel/framework/src/Illuminate/Queue/Listener.php diff --git a/composer.json b/composer.json index 01cf7ed6..f6ab7f95 100644 --- a/composer.json +++ b/composer.json @@ -125,8 +125,10 @@ "Illuminate\\Support\\": "overrides/laravel/framework/src/Illuminate/Support/", "Illuminate\\Http\\": "overrides/laravel/framework/src/Illuminate/Http/", "Illuminate\\Database\\Eloquent\\": "overrides/laravel/framework/src/Illuminate/Database/Eloquent/", + "Illuminate\\Database\\Eloquent\\Concerns\\": "overrides/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/", "Illuminate\\Pagination\\": "overrides/laravel/framework/src/Illuminate/Pagination/", "Illuminate\\Session\\": "overrides/laravel/framework/src/Illuminate/Session/", + "Illuminate\\Queue\\": "overrides/laravel/framework/src/Illuminate/Queue/", "Carbon\\": "overrides/nesbot/carbon/src/Carbon/", "Illuminate\\Cache\\": "overrides/laravel/framework/src/Illuminate/Cache/", "Illuminate\\Config\\": "overrides/laravel/framework/src/Illuminate/Config/", @@ -220,11 +222,13 @@ "vendor/symfony/http-foundation/FileBag.php", "vendor/symfony/http-foundation/Request.php", "vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php", + "vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php", "vendor/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php", "vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php", "vendor/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php", "vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php", "vendor/laravel/framework/src/Illuminate/Session/FileSessionHandler.php", + "vendor/laravel/framework/src/Illuminate/Queue/Listener.php", "vendor/nesbot/carbon/src/Carbon/Carbon.php", "vendor/laravel/framework/src/Illuminate/Cache/Repository.php", "vendor/laravel/framework/src/Illuminate/Config/Repository.php", diff --git a/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php b/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php new file mode 100644 index 00000000..e1f2ced4 --- /dev/null +++ b/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php @@ -0,0 +1,226 @@ +fillable; + } + + /** + * Set the fillable attributes for the model. + * + * @param array $fillable + * @return $this + */ + public function fillable(array $fillable) + { + $this->fillable = $fillable; + + return $this; + } + + /** + * Get the guarded attributes for the model. + * + * @return array + */ + public function getGuarded() + { + return $this->guarded; + } + + /** + * Set the guarded attributes for the model. + * + * @param array $guarded + * @return $this + */ + public function guard(array $guarded) + { + $this->guarded = $guarded; + + return $this; + } + + /** + * Disable all mass assignable restrictions. + * + * @param bool $state + * @return void + */ + public static function unguard($state = true) + { + static::$unguarded = $state; + } + + /** + * Enable the mass assignment restrictions. + * + * @return void + */ + public static function reguard() + { + static::$unguarded = false; + } + + /** + * Determine if current state is "unguarded". + * + * @return bool + */ + public static function isUnguarded() + { + return static::$unguarded; + } + + /** + * Run the given callable while being unguarded. + * + * @param callable $callback + * @return mixed + */ + public static function unguarded(callable $callback) + { + if (static::$unguarded) { + return $callback(); + } + + static::unguard(); + + try { + return $callback(); + } finally { + static::reguard(); + } + } + + /** + * Determine if the given attribute may be mass assigned. + * + * @param string $key + * @return bool + */ + public function isFillable($key) + { + if (static::$unguarded) { + return true; + } + + // If the key is in the "fillable" array, we can of course assume that it's + // a fillable attribute. Otherwise, we will check the guarded array when + // we need to determine if the attribute is black-listed on the model. + if (in_array($key, $this->getFillable())) { + return true; + } + + // If the attribute is explicitly listed in the "guarded" array then we can + // return false immediately. This means this attribute is definitely not + // fillable and there is no point in going any further in this method. + if ($this->isGuarded($key)) { + return false; + } + + return empty($this->getFillable()) && + strpos($key, '.') === false && + ! Str::startsWith($key, '_'); + } + + /** + * Determine if the given key is guarded. + * + * @param string $key + * @return bool + */ + public function isGuarded($key) + { + //return in_array($key, $this->getGuarded()) || $this->getGuarded() == ['*']; + + if (empty($this->getGuarded())) { + return false; + } + + return $this->getGuarded() == ['*'] || + ! empty(preg_grep('/^'.preg_quote($key).'$/i', $this->getGuarded())) || + ! $this->isGuardableColumn($key); + } + + /** + * Determine if the given column is a valid, guardable column. + * + * @param string $key + * @return bool + */ + protected function isGuardableColumn($key) + { + if (! isset(static::$guardableColumns[get_class($this)])) { + static::$guardableColumns[get_class($this)] = $this->getConnection() + ->getSchemaBuilder() + ->getColumnListing($this->getTable()); + } + + return in_array($key, static::$guardableColumns[get_class($this)]); + } + + /** + * Determine if the model is totally guarded. + * + * @return bool + */ + public function totallyGuarded() + { + return count($this->getFillable()) == 0 && $this->getGuarded() == ['*']; + } + + /** + * Get the fillable attributes of a given array. + * + * @param array $attributes + * @return array + */ + protected function fillableFromArray(array $attributes) + { + if (count($this->getFillable()) > 0 && ! static::$unguarded) { + return array_intersect_key($attributes, array_flip($this->getFillable())); + } + + return $attributes; + } +} diff --git a/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Model.php b/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Model.php index 7199c7c5..4cb49958 100644 --- a/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Model.php +++ b/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Model.php @@ -272,7 +272,8 @@ abstract class Model implements ArrayAccess, Arrayable, Jsonable, JsonSerializab */ protected function removeTableFromKey($key) { - return Str::contains($key, '.') ? last(explode('.', $key)) : $key; + //return Str::contains($key, '.') ? last(explode('.', $key)) : $key; + return $key; } /** diff --git a/overrides/laravel/framework/src/Illuminate/Queue/Listener.php b/overrides/laravel/framework/src/Illuminate/Queue/Listener.php new file mode 100644 index 00000000..2e55ea98 --- /dev/null +++ b/overrides/laravel/framework/src/Illuminate/Queue/Listener.php @@ -0,0 +1,248 @@ +commandPath = $commandPath; + $this->workerCommand = $this->buildCommandTemplate(); + } + + /** + * Build the environment specific worker command. + * + * @return string + */ + protected function buildCommandTemplate() + { + $command = 'queue:work %s --once --queue=%s --delay=%s --memory=%s --sleep=%s --tries=%s'; + + return "{$this->phpBinary()} {$this->artisanBinary()} {$command}"; + } + + /** + * Get the PHP binary. + * + * @return string + */ + protected function phpBinary() + { + return ProcessUtils::escapeArgument( + (new PhpExecutableFinder)->find(false) + ); + } + + /** + * Get the Artisan binary. + * + * @return string + */ + protected function artisanBinary() + { + return defined('ARTISAN_BINARY') + ? ProcessUtils::escapeArgument(ARTISAN_BINARY) + : 'artisan'; + } + + /** + * Listen to the given queue connection. + * + * @param string $connection + * @param string $queue + * @param \Illuminate\Queue\ListenerOptions $options + * @return void + */ + public function listen($connection, $queue, ListenerOptions $options) + { + $process = $this->makeProcess($connection, $queue, $options); + + while (true) { + $this->runProcess($process, $options->memory); + } + } + + /** + * Create a new Symfony process for the worker. + * + * @param string $connection + * @param string $queue + * @param \Illuminate\Queue\ListenerOptions $options + * @return \Symfony\Component\Process\Process + */ + public function makeProcess($connection, $queue, ListenerOptions $options) + { + $command = $this->workerCommand; + + // If the environment is set, we will append it to the command string so the + // workers will run under the specified environment. Otherwise, they will + // just run under the production environment which is not always right. + if (isset($options->environment)) { + $command = $this->addEnvironment($command, $options); + } + + // Next, we will just format out the worker commands with all of the various + // options available for the command. This will produce the final command + // line that we will pass into a Symfony process object for processing. + $command = $this->formatCommand( + $command, $connection, $queue, $options + ); + + return new Process( + $command, $this->commandPath, null, null, $options->timeout + ); + } + + /** + * Add the environment option to the given command. + * + * @param string $command + * @param \Illuminate\Queue\ListenerOptions $options + * @return string + */ + protected function addEnvironment($command, ListenerOptions $options) + { + return $command.' --env='.ProcessUtils::escapeArgument($options->environment); + } + + /** + * Format the given command with the listener options. + * + * @param string $command + * @param string $connection + * @param string $queue + * @param \Illuminate\Queue\ListenerOptions $options + * @return string + */ + protected function formatCommand($command, $connection, $queue, ListenerOptions $options) + { + return sprintf( + $command, + ProcessUtils::escapeArgument($connection), + ProcessUtils::escapeArgument($queue), + $options->delay, $options->memory, + $options->sleep, $options->maxTries + ); + } + + /** + * Run the given process. + * + * @param \Symfony\Component\Process\Process $process + * @param int $memory + * @return void + */ + public function runProcess(Process $process, $memory) + { + $process->run(function ($type, $line) { + $this->handleWorkerOutput($type, $line); + }); + + // Once we have run the job we'll go check if the memory limit has been exceeded + // for the script. If it has, we will kill this script so the process manager + // will restart this with a clean slate of memory automatically on exiting. + if ($this->memoryExceeded($memory)) { + $this->stop(); + } + } + + /** + * Handle output from the worker process. + * + * @param int $type + * @param string $line + * @return void + */ + protected function handleWorkerOutput($type, $line) + { + if (isset($this->outputHandler)) { + call_user_func($this->outputHandler, $type, $line); + } + } + + /** + * Determine if the memory limit has been exceeded. + * + * @param int $memoryLimit + * @return bool + */ + public function memoryExceeded($memoryLimit) + { + return (memory_get_usage() / 1024 / 1024) >= $memoryLimit; + } + + /** + * Stop listening and bail out of the script. + * + * @return void + */ + public function stop() + { + exit; + } + + /** + * Set the output handler callback. + * + * @param \Closure $outputHandler + * @return void + */ + public function setOutputHandler(Closure $outputHandler) + { + $this->outputHandler = $outputHandler; + } +} diff --git a/overrides/laravel/framework/src/Illuminate/Support/Collection.php b/overrides/laravel/framework/src/Illuminate/Support/Collection.php index 35e44dbe..3046137f 100644 --- a/overrides/laravel/framework/src/Illuminate/Support/Collection.php +++ b/overrides/laravel/framework/src/Illuminate/Support/Collection.php @@ -275,7 +275,7 @@ class Collection implements ArrayAccess, Arrayable, Countable, IteratorAggregate call_user_func_array([$this, 'dump'], $args); - die(1); + exit(1); } /**