CVE-2021-29487: Other (NVD-CWE-Other)
value: octobercms in a CMS platform based on the Laravel PHP Framework. In affected versions of the october/system package an attacker can exploit this vulnerability to bypass authentication and takeover of and user account on an October CMS server. The vulnerability is exploitable by unauthenticated users via a specially crafted request. This only affects frontend users and the attacker must obtain a Laravel secret key for cookie encryption and signing in order to exploit this vulnerability. The issue has been patched in Build 472 and v1.1.5.
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
CVSS v3 base score: 7.4
Published: 2021-08-26T19:15Z
Repository: https://github.com/octobercms/library
Language: PHP
Files changed: src/Auth/Manager.php, src/Auth/Models/Role.php, src/Auth/Models/Throttle.php, src/Auth/Models/User.php
Commit date: 2021-04-01 11:18:07 +1100
Commit message: Backport code audit from 1.2

Fixes issue with permissions checking

Diff: diff --git a/src/Auth/Manager.php b/src/Auth/Manager.php
index a8dc26a2..479045ef 100644
--- a/src/Auth/Manager.php
+++ b/src/Auth/Manager.php
@@ -239,11 +239,11 @@ class Manager implements \Illuminate\Contracts\Auth\StatefulGuard
          * Check the hashed credentials match
          */
         foreach ($hashedCredentials as $credential => $value) {
             if (!$user->checkHashValue($credential, $value)) {
                 // Incorrect password
-                if ($credential == 'password') {
+                if ($credential === 'password') {
                     throw new AuthException(sprintf(
                         'A user was found to match all plain text credentials however hashed credential "%s" did not match.',
                         $credential
                     ));
                 }
diff --git a/src/Auth/Models/Role.php b/src/Auth/Models/Role.php
index a90b2ac7..4479c844 100644
--- a/src/Auth/Models/Role.php
+++ b/src/Auth/Models/Role.php
@@ -82,11 +82,11 @@ class Role extends Model
                     // Strip the '*' off the end of the permission.
                     $checkPermission = substr($permission, 0, -1);
 
                     // We will make sure that the merged permission does not
                     // exactly match our permission, but starts with it.
-                    if ($checkPermission != $rolePermission && starts_with($rolePermission, $checkPermission) && $value == 1) {
+                    if ($checkPermission != $rolePermission && starts_with($rolePermission, $checkPermission) && $value === 1) {
                         $matched = true;
                         break;
                     }
                 }
             }
@@ -100,11 +100,11 @@ class Role extends Model
                     // Strip the '*' off the start of the permission.
                     $checkPermission = substr($permission, 1);
 
                     // We will make sure that the merged permission does not
                     // exactly match our permission, but ends with it.
-                    if ($checkPermission != $rolePermission && ends_with($rolePermission, $checkPermission) && $value == 1) {
+                    if ($checkPermission != $rolePermission && ends_with($rolePermission, $checkPermission) && $value === 1) {
                         $matched = true;
                         break;
                     }
                 }
             }
@@ -119,18 +119,18 @@ class Role extends Model
                         // Strip the '*' off the end of the permission.
                         $checkGroupPermission = substr($rolePermission, 0, -1);
 
                         // We will make sure that the merged permission does not
                         // exactly match our permission, but starts with it.
-                        if ($checkGroupPermission != $permission && starts_with($permission, $checkGroupPermission) && $value == 1) {
+                        if ($checkGroupPermission != $permission && starts_with($permission, $checkGroupPermission) && $value === 1) {
                             $matched = true;
                             break;
                         }
                     }
                     // Otherwise, we'll fallback to standard permissions checking where
                     // we match that permissions explicitly exist.
-                    elseif ($permission == $rolePermission && $rolePermissions[$permission] == 1) {
+                    elseif ($permission === $rolePermission && $rolePermissions[$permission] === 1) {
                         $matched = true;
                         break;
                     }
                 }
             }
diff --git a/src/Auth/Models/Throttle.php b/src/Auth/Models/Throttle.php
index fbaf14b2..ba01a864 100644
--- a/src/Auth/Models/Throttle.php
+++ b/src/Auth/Models/Throttle.php
@@ -97,11 +97,11 @@ class Throttle extends Model
         // we do not need to do anything. Additionally,
         // if we are suspended, we are not going to do
         // anything either as clearing login attempts
         // makes us unsuspended. We need to manually
         // call unsuspend() in order to unsuspend.
-        if ($this->getLoginAttempts() == 0 or $this->is_suspended) {
+        if ($this->getLoginAttempts() === 0 or $this->is_suspended) {
             return;
         }
 
         $this->attempts = 0;
         $this->last_attempt_at = null;
diff --git a/src/Auth/Models/User.php b/src/Auth/Models/User.php
index db289f56..ebba418b 100644
--- a/src/Auth/Models/User.php
+++ b/src/Auth/Models/User.php
@@ -188,11 +188,11 @@ class User extends Model implements \Illuminate\Contracts\Auth\Authenticatable
     {
         if (!$persistCode || !$this->persist_code) {
             return false;
         }
 
-        return $persistCode == $this->persist_code;
+        return $persistCode === $this->persist_code;
     }
 
     //
     // Activation
     //
@@ -276,11 +276,11 @@ class User extends Model implements \Illuminate\Contracts\Auth\Authenticatable
     {
         if (!$resetCode || !$this->reset_password_code) {
             return false;
         }
 
-        return ($this->reset_password_code == $resetCode);
+        return ($this->reset_password_code === $resetCode);
     }
 
     /**
      * Attempts to reset a user's password by matching the reset code generated with the user's.
      * @param string $resetCode
@@ -384,11 +384,11 @@ class User extends Model implements \Illuminate\Contracts\Auth\Authenticatable
      * @return bool
      */
     public function inGroup($group)
     {
         foreach ($this->getGroups() as $_group) {
-            if ($_group->getKey() == $group->getKey()) {
+            if ($_group->getKey() === $group->getKey()) {
                 return true;
             }
         }
 
         return false;
@@ -479,11 +479,11 @@ class User extends Model implements \Illuminate\Contracts\Auth\Authenticatable
                     // Strip the '*' off the end of the permission.
                     $checkPermission = substr($permission, 0, -1);
 
                     // We will make sure that the merged permission does not
                     // exactly match our permission, but starts with it.
-                    if ($checkPermission != $mergedPermission && starts_with($mergedPermission, $checkPermission) && $value == 1) {
+                    if ($checkPermission != $mergedPermission && starts_with($mergedPermission, $checkPermission) && $value === 1) {
                         $matched = true;
                         break;
                     }
                 }
             }
@@ -494,11 +494,11 @@ class User extends Model implements \Illuminate\Contracts\Auth\Authenticatable
                     // Strip the '*' off the beginning of the permission.
                     $checkPermission = substr($permission, 1);
 
                     // We will make sure that the merged permission does not
                     // exactly match our permission, but ends with it.
-                    if ($checkPermission != $mergedPermission && ends_with($mergedPermission, $checkPermission) && $value == 1) {
+                    if ($checkPermission != $mergedPermission && ends_with($mergedPermission, $checkPermission) && $value === 1) {
                         $matched = true;
                         break;
                     }
                 }
             }
@@ -513,19 +513,19 @@ class User extends Model implements \Illuminate\Contracts\Auth\Authenticatable
                         // Strip the '*' off the end of the permission.
                         $checkMergedPermission = substr($mergedPermission, 0, -1);
 
                         // We will make sure that the merged permission does not
                         // exactly match our permission, but starts with it.
-                        if ($checkMergedPermission != $permission && starts_with($permission, $checkMergedPermission) && $value == 1) {
+                        if ($checkMergedPermission != $permission && starts_with($permission, $checkMergedPermission) && $value === 1) {
                             $matched = true;
                             break;
                         }
                     }
 
                     // Otherwise, we'll fallback to standard permissions checking where
                     // we match that permissions explicitly exist.
-                    elseif ($permission == $mergedPermission && $mergedPermissions[$permission] == 1) {
+                    elseif ($permission === $mergedPermission && $mergedPermissions[$permission] === 1) {
                         $matched = true;
                         break;
                     }
                 }
             }

Vulnerable code:                 if ($credential == 'password') {
                    if ($checkPermission != $rolePermission && starts_with($rolePermission, $checkPermission) && $value == 1) {
                    if ($checkPermission != $rolePermission && ends_with($rolePermission, $checkPermission) && $value == 1) {
                        if ($checkGroupPermission != $permission && starts_with($permission, $checkGroupPermission) && $value == 1) {
                    elseif ($permission == $rolePermission && $rolePermissions[$permission] == 1) {
        if ($this->getLoginAttempts() == 0 or $this->is_suspended) {
        return $persistCode == $this->persist_code;
        return ($this->reset_password_code == $resetCode);
            if ($_group->getKey() == $group->getKey()) {
                    if ($checkPermission != $mergedPermission && starts_with($mergedPermission, $checkPermission) && $value == 1) {
                    if ($checkPermission != $mergedPermission && ends_with($mergedPermission, $checkPermission) && $value == 1) {
                        if ($checkMergedPermission != $permission && starts_with($permission, $checkMergedPermission) && $value == 1) {
                    elseif ($permission == $mergedPermission && $mergedPermissions[$permission] == 1) {
Fixed code:                 if ($credential === 'password') {
                    if ($checkPermission != $rolePermission && starts_with($rolePermission, $checkPermission) && $value === 1) {
                    if ($checkPermission != $rolePermission && ends_with($rolePermission, $checkPermission) && $value === 1) {
                        if ($checkGroupPermission != $permission && starts_with($permission, $checkGroupPermission) && $value === 1) {
                    elseif ($permission === $rolePermission && $rolePermissions[$permission] === 1) {
        if ($this->getLoginAttempts() === 0 or $this->is_suspended) {
        return $persistCode === $this->persist_code;
        return ($this->reset_password_code === $resetCode);
            if ($_group->getKey() === $group->getKey()) {
                    if ($checkPermission != $mergedPermission && starts_with($mergedPermission, $checkPermission) && $value === 1) {
                    if ($checkPermission != $mergedPermission && ends_with($mergedPermission, $checkPermission) && $value === 1) {
                        if ($checkMergedPermission != $permission && starts_with($permission, $checkMergedPermission) && $value === 1) {
                    elseif ($permission === $mergedPermission && $mergedPermissions[$permission] === 1) {