Improper Input Validation in Laravel
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
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 = []){