Query Binding Exploitation
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
Details
### Description Laravel versions <6.20.12, <7.30.3 & <8.22.1 contain a query binding exploitation. If a request is crafted where a field that is normally a non-array value is an array, and that input is not validated or cast to its expected type before being passed to the query builder, an unexpected number of query bindings can be added to the query. In some situations, this will simply lead to no results being returned by the query builder; however, it is possible certain queries could be affected in a way that causes the query to return unexpected results. This vulnerability was discovered by Tim Groenevelt (tim.g@foodbyus.com). ### References - https://github.com/laravel/framework/pull/35865
The fix
limit expected bindings
src/Illuminate/Database/Query/Builder.php+12 −2
@@ -698,7 +698,7 @@ public function where($column, $operator = null, $value = null, $boolean = 'and');if (! $value instanceof Expression) {-$this->addBinding($value, 'where');+$this->addBinding(is_array($value) ? head($value) : $value, 'where');}return $this;@@ -1043,7 +1043,7 @@ public function whereBetween($column, array $values, $boolean = 'and', $not = fa$this->wheres[] = compact('type', 'column', 'values', 'boolean', 'not');-$this->addBinding($this->cleanBindings($values), 'where');+$this->addBinding(array_slice($this->cleanBindings($values), 0, 2), 'where');return $this;}@@ -1111,6 +1111,8 @@ public function whereDate($column, $operator, $value = null, $boolean = 'and')$value, $operator, func_num_args() === 2);+$value = is_array($value) ? head($value) : $value;+if ($value instanceof DateTimeInterface) {$value = $value->format('Y-m-d');}@@ -1150,6 +1152,8 @@ public function whereTime($column, $operator, $value = null, $boolean = 'and')$value, $operator, func_num_args() === 2);+$value = is_array($value) ? head($value) : $value;+if ($value instanceof DateTimeInterface) {$value = $value->format('H:i:s');}@@ -1189,6 +1193,8 @@ public function whereDay($column, $operator, $value = null, $boolean = 'and')$value, $operator, func_num_args() === 2);+$value = is_array($value) ? head($value) : $value;+if ($value instanceof DateTimeInterface) {$value = $value->format('d');}@@ -1232,6 +1238,8 @@ public function whereMonth($column, $operator, $value = null, $boolean = 'and')$value, $operator, func_num_args() === 2);+$value = is_array($value) ? head($value) : $value;+if ($value instanceof DateTimeInterface) {$value = $value->format('m');}@@ -1275,6 +1283,8 @@ public function whereYear($column, $operator, $value = null, $boolean = 'and')$value, $operator, func_num_args() === 2);+$value = is_array($value) ? head($value) : $value;+if ($value instanceof DateTimeInterface) {$value = $value->format('Y');}
tests/Database/DatabaseQueryBuilderTest.php+17 −17
@@ -301,24 +301,24 @@ public function testBasicWheres()public function testWheresWithArrayValue(){$builder = $this->getBuilder();-$builder->select('*')->from('users')->where('id', [12, 30]);+$builder->select('*')->from('users')->where('id', [12]);$this->assertSame('select * from "users" where "id" = ?', $builder->toSql());-$this->assertEquals([0 => 12, 1 => 30], $builder->getBindings());--$builder = $this->getBuilder();-$builder->select('*')->from('users')->where('id', '=', [12, 30]);-$this->assertSame('select * from "users" where "id" = ?', $builder->toSql());-$this->assertEquals([0 => 12, 1 => 30], $builder->getBindings());--$builder = $this->getBuilder();-$builder->select('*')->from('users')->where('id', '!=', [12, 30]);-$this->assertSame('select * from "users" where "id" != ?', $builder->toSql());-$this->assertEquals([0 => 12, 1 => 30], $builder->getBindings());--$builder = $this->getBuilder();-$builder->select('*')->from('users')->where('id', '<>', [12, 30]);-$this->assertSame('select * from "users" where "id" <> ?', $builder->toSql());-$this->assertEquals([0 => 12, 1 => 30], $builder->getBindings());+$this->assertEquals([0 => 12], $builder->getBindings());++// $builder = $this->getBuilder();+// $builder->select('*')->from('users')->where('id', '=', [12, 30]);+// $this->assertSame('select * from "users" where "id" = ?', $builder->toSql());+// $this->assertEquals([0 => 12, 1 => 30], $builder->getBindings());++// $builder = $this->getBuilder();+// $builder->select('*')->from('users')->where('id', '!=', [12, 30]);+// $this->assertSame('select * from "users" where "id" != ?', $builder->toSql());+// $this->assertEquals([0 => 12, 1 => 30], $builder->getBindings());++// $builder = $this->getBuilder();+// $builder->select('*')->from('users')->where('id', '<>', [12, 30]);+// $this->assertSame('select * from "users" where "id" <> ?', $builder->toSql());+// $this->assertEquals([0 => 12, 1 => 30], $builder->getBindings());}public function testMySqlWrappingProtectsQuotationMarks()src/Illuminate/Database/Query/Builder.php | 4 ++--1 file changed, 2 insertions(+), 2 deletions(-)
src/Illuminate/Database/Query/Builder.php+2 −2
@@ -1593,7 +1593,7 @@ public function whereJsonLength($column, $operator, $value = null, $boolean = 'a$this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');if (! $value instanceof Expression) {-$this->addBinding($value);+$this->addBinding((int) $value);}return $this;@@ -1742,7 +1742,7 @@ public function having($column, $operator = null, $value = null, $boolean = 'and$this->havings[] = compact('type', 'column', 'operator', 'value', 'boolean');if (! $value instanceof Expression) {-$this->addBinding($value, 'having');+$this->addBinding(is_array($value) ? head($value) : $value, 'having');}return $this;
References
- WEBhttps://github.com/laravel/framework/security/advisories/GHSA-3p32-j457-pg5x
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2021-21263
- WEBhttps://github.com/laravel/framework/pull/35865
- WEBhttps://blog.laravel.com/security-laravel-62011-7302-8221-released
- WEBhttps://blog.laravel.com/security-laravel-62012-7303-released
- WEBhttps://github.com/FriendsOfPHP/security-advisories/blob/master/illuminate/database/CVE-2021-21263.yaml
- WEBhttps://github.com/FriendsOfPHP/security-advisories/blob/master/laravel/framework/CVE-2021-21263.yaml
- WEBhttps://packagist.org/packages/illuminate/database
- WEBhttps://packagist.org/packages/laravel/framework