Security context
High· 7.5GHSA-w68r-5p45-5rqp CVE-2020-24941CWE-863Published May 6, 2021

Improper Input Validation in Laravel

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

0 → fixed in 6.18.357.0.0 → fixed in 7.24.0

Details

An issue was discovered in Laravel before 6.18.35 and 7.x before 7.24.0. The $guarded property is mishandled in some situations involving requests with JSON column nesting expressions.

The fix

[6.x] Verify column names are actual columns when using

Taylor Otwell· Aug 7, 2020, 03:01 PM+404897d107775
src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php+28 3
@@ -27,6 +27,13 @@ trait GuardsAttributes
*/
protected static $unguarded = false;
+ /**
+ * The actual columns that exist on the database and can be guarded.
+ *
+ * @var array
+ */
+ protected static $guardableColumns = [];
+
/**
* Get the fillable attributes for the model.
*
@@ -164,12 +171,30 @@ public function isFillable($key)
*/
public function isGuarded($key)
{
- if (strpos($key, '->') !== false) {
- $key = Str::before($key, '->');
+ if (empty($this->getGuarded())) {
+ return false;
}
return $this->getGuarded() == ['*'] ||
- ! empty(preg_grep('/^'.preg_quote($key).'$/i', $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)]);
}
/**
tests/Database/DatabaseEloquentModelTest.php+12 1
@@ -10,6 +10,7 @@
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Connection;
use Illuminate\Database\ConnectionResolverInterface;
+use Illuminate\Database\ConnectionResolverInterface as Resolver;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\JsonEncodingException;
@@ -1014,11 +1015,21 @@ public function testUnderscorePropertiesAreNotFilled()
public function testGuarded()
{
$model = new EloquentModelStub;
+
+ EloquentModelStub::setConnectionResolver($resolver = m::mock(Resolver::class));
+ $resolver->shouldReceive('connection')->andReturn($connection = m::mock(stdClass::class));
+ $connection->shouldReceive('getSchemaBuilder->getColumnListing')->andReturn(['name', 'age', 'foo']);
+
$model->guard(['name', 'age']);
$model->fill(['name' => 'foo', 'age' => 'bar', 'foo' => 'bar']);
$this->assertFalse(isset($model->name));
$this->assertFalse(isset($model->age));
$this->assertSame('bar', $model->foo);
+
+ $model = new EloquentModelStub;
+ $model->guard(['name', 'age']);
+ $model->fill(['Foo' => 'bar']);
+ $this->assertFalse(isset($model->Foo));
}
public function testFillableOverridesGuarded()
@@ -2134,7 +2145,7 @@ public function getDates()
class EloquentModelSaveStub extends Model
{
protected $table = 'save_stub';
- protected $guarded = ['id'];
+ protected $guarded = [];
public function save(array $options = [])
{

References