Welcome Guest, Not a member yet? Register   Sign In
How does $allowedFields protect against mass assignment vulnerabilities?
#1

(This post was last modified: 09-03-2024, 08:10 AM by libsys. Edit Reason: style )

I thought I understood $allowedFields until recently, when I tried to insert a field not listed by assigning it manually to my entity.
Turns out that it is discarded, even though I assigned it manually, ej:

PHP Code:
$obj->protectedField 'value'

and then

PHP Code:
$objModel->insert($obj); 

It seems that I can't set any field unless it's on the $allowedFields list. This will only protect fields that are automatically handled by the database or inserted bypassing $allowedFields.

Could you please clarify whether I've misunderstood or if there's something I'm missing?
Reply
#2

Why do you modify the $allowedFields after declaring it in the Model?
As far as I know, an entity's attributes should be immutable, or if you want to make transformations, they should be done through a setter/getter.
Reply
#3

(This post was last modified: 09-05-2024, 12:02 AM by kenjis.)

See https://cheatsheetseries.owasp.org/cheat...Sheet.html

(09-03-2024, 08:06 AM)libsys Wrote: It seems that I can't set any field unless it's on the $allowedFields list.

Yes. Only $allowedFields can be set.
Reply
#4

(This post was last modified: 11 hours ago by libsys. Edit Reason: is > if )

(09-04-2024, 08:52 AM)Renta Ardhana Wrote: Why do you modify the $allowedFields after declaring it in the Model?

I was not modifying $allowedFields, I said that to exemplify the unique ways I could have stuff protected from mass assignment vulnerabilities while using $allowedFields.

(09-04-2024, 08:52 AM)Renta Ardhana Wrote: As far as I know, an entity's attributes should be immutable, or if you want to make transformations, they should be done through a setter/getter.

Even if I had immutable entities, I would need to list every field in $allowedFields. Even if I modify stuff with setters, I still need to put those fields in $allowedFields.

IMHO the current approach is problematic because there's no easy way to explicitly insert protected data in the database, ever, no matter if massively assigned or not. Other frameworks (such as Laravel) discard attributes only on mass assignment (but not on manual assignment).

When using CI4 entities, currently there's no way to know if a field has been massively assigned or individually. In order to actually have the protection I expected, now I am indeed modifying the model's $allowedFields when setting fields manually in entities. Here is what I'm doing:

BaseModel.php
PHP Code:
<?php

declare(strict_types=1);

namespace 
App\Models;

use 
App\Entities\BaseEntity;
use 
CodeIgniter\Model;

abstract class 
BaseModel extends Model
{
    public function insert($row nullbool $returnID true)
    {
        return $this->allowEntityManuallyAssigned(
            $row,
            fn () => parent::insert($row$returnID)
        );
    }

    public function update($id null$row null): bool
    
{
        return $this->allowEntityManuallyAssigned(
            $row,
            fn () => parent::update($id$row)
        );
    }

    private function allowEntityManuallyAssigned(mixed $row, callable $callback): mixed
    
{
        if ($row instanceof BaseEntity) {
            $originalAllowedFields $this->allowedFields;
            $this->allowedFields  array_unique(array_merge(
                $originalAllowedFields,
                $row->getManuallyAssigned()
            ));
        }

        $result $callback();

        if ($row instanceof BaseEntity) {
            $this->allowedFields $originalAllowedFields;
        }

        return $result;
    }


BaseEntity.php
PHP Code:
<?php

declare(strict_types=1);

namespace 
App\Entities;

use 
CodeIgniter\Entity\Entity;

abstract class 
BaseEntity extends Entity
{
    protected array $manuallyAssigned = [];

    protected bool $isMassAssignment false;

    public function __set(string $key$value null)
    {
        parent::__set($key$value);

        if (! $this->isMassAssignment && ! in_array($key$this->manuallyAssignedtrue)) {
            $this->manuallyAssigned[] = $key;
        }
    }

    public function fill(?array $data null): static
    {
        $this->isMassAssignment true;

        parent::fill($data);

        $this->isMassAssignment false;

        return $this;
    }

    public function getManuallyAssigned(): array
    {
        return $this->manuallyAssigned;
    }


Usage examples:
PHP Code:
// 1
$entity = new SomeEntity(['allowedField' => 'value''notAllowedField' => 'value']);
$entity $model->find($model->insert($obj)); // $entity->notAllowedField == null

// 2
$entity = new SomeEntity();
$entity->fill(['allowedField' => 'value''notAllowedField' => 'value']);
$entity $model->find($model->insert($obj));  // $entity->notAllowedField == null

// 3
$entity = new SomeEntity();
$entity->allowedField 'value';
$entity->notAllowedField 'value';
$entity $model->find($model->insert($obj));  // $entity->notAllowedField == 'value' 
Reply
#5

@libsys Your customization is interesting. 
But in my opinion, if an Entity knows assignable fields, you don't need to use allowedFields in CI4 Model.
It seems you can add protection in your Entity (e.g., to add $fillable in the Entity) and disable the protection in Model.
That will be simpler.
Reply
#6

(This post was last modified: 11 hours ago by libsys. Edit Reason: clarification )

(09-06-2024, 04:56 PM)kenjis Wrote: It seems you can add protection in your Entity (e.g., to add $fillable in the Entity) and disable the protection in Model.
That will be simpler.

That would be neat, but it doesn't seem to be that simple, because models assign attributes massively at the time of instantiating an entity.

This means I would still need to add every field to $fillable if I expect to retrieve entities using models.
Reply




Theme © iAndrew 2016 - Forum software by © MyBB