VYPR
Moderate severityNVD Advisory· Published Oct 27, 2021· Updated Aug 3, 2024

Cross-Site Request Forgery (CSRF) in firefly-iii/firefly-iii

CVE-2021-3900

Description

firefly-iii is vulnerable to Cross-Site Request Forgery (CSRF)

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
grumpydictator/firefly-iiiPackagist
<= 5.6.2

Affected products

1

Patches

1
c2c8c42ef319

Catch CSRF issues

https://github.com/firefly-iii/firefly-iiiJames ColeOct 23, 2021via ghsa
6 files changed · +76 46
  • app/Http/Controllers/Rule/CreateController.php+9 6 modified
    @@ -34,6 +34,7 @@
     use FireflyIII\Support\Http\Controllers\RuleManagement;
     use FireflyIII\Support\Search\SearchInterface;
     use Illuminate\Contracts\View\Factory;
    +use Illuminate\Http\JsonResponse;
     use Illuminate\Http\RedirectResponse;
     use Illuminate\Http\Request;
     use Illuminate\Routing\Redirector;
    @@ -237,15 +238,17 @@ public function createFromJournal(Request $request, TransactionJournal $journal)
         /**
          * @param Rule $rule
          *
    -     * @return RedirectResponse
    +     * @return JsonResponse
          */
    -    public function duplicate(Rule $rule): RedirectResponse
    +    public function duplicate(Request $request): JsonResponse
         {
    -        $newRule = $this->ruleRepos->duplicate($rule);
    -
    -        session()->flash('success', trans('firefly.duplicated_rule', ['title' => $rule->title, 'newTitle' => $newRule->title]));
    +        $ruleId = (int)$request->get('id');
    +        $rule   = $this->ruleRepos->find($ruleId);
    +        if (null !== $rule) {
    +            $this->ruleRepos->duplicate($rule);
    +        }
     
    -        return redirect(route('rules.index'));
    +        return new JsonResponse(['OK']);
         }
     
         /**
    
  • app/Http/Controllers/RuleGroup/EditController.php+26 30 modified
    @@ -28,6 +28,7 @@
     use FireflyIII\Models\RuleGroup;
     use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
     use Illuminate\Contracts\View\Factory;
    +use Illuminate\Http\JsonResponse;
     use Illuminate\Http\RedirectResponse;
     use Illuminate\Http\Request;
     use Illuminate\Routing\Redirector;
    @@ -62,24 +63,38 @@ function ($request, $next) {
         }
     
         /**
    -     * Move a rule group down.
    +     * Move a rule group in either direction.
          *
    -     * @param RuleGroup $ruleGroup
    +     * @param Request $request
          *
    -     * @return RedirectResponse|Redirector
    +     * @return JsonResponse
          */
    -    public function down(RuleGroup $ruleGroup)
    +    public function moveGroup(Request $request): JsonResponse
         {
    -        $maxOrder = $this->repository->maxOrder();
    -        $order    = (int)$ruleGroup->order;
    -        if ($order < $maxOrder) {
    -            $newOrder = $order + 1;
    -            $this->repository->setOrder($ruleGroup, $newOrder);
    +        $groupId = (int)$request->get('id');
    +        $ruleGroup= $this->repository->find($groupId);
    +        if(null !== $ruleGroup) {
    +            $direction = $request->get('direction');
    +            if('down' === $direction) {
    +                $maxOrder = $this->repository->maxOrder();
    +                $order    = (int)$ruleGroup->order;
    +                if ($order < $maxOrder) {
    +                    $newOrder = $order + 1;
    +                    $this->repository->setOrder($ruleGroup, $newOrder);
    +                }
    +            }
    +            if('up' === $direction) {
    +                $order = (int)$ruleGroup->order;
    +                if ($order > 1) {
    +                    $newOrder = $order - 1;
    +                    $this->repository->setOrder($ruleGroup, $newOrder);
    +                }
    +            }
             }
    -
    -        return redirect(route('rules.index'));
    +        return new JsonResponse(['OK']);
         }
     
    +
         /**
          * Edit a rule group.
          *
    @@ -106,25 +121,6 @@ public function edit(Request $request, RuleGroup $ruleGroup)
             return prefixView('rules.rule-group.edit', compact('ruleGroup', 'subTitle'));
         }
     
    -    /**
    -     * Move the rule group up.
    -     *
    -     * @param RuleGroup $ruleGroup
    -     *
    -     * @return RedirectResponse|Redirector
    -     *
    -     */
    -    public function up(RuleGroup $ruleGroup)
    -    {
    -        $order = (int)$ruleGroup->order;
    -        if ($order > 1) {
    -            $newOrder = $order - 1;
    -            $this->repository->setOrder($ruleGroup, $newOrder);
    -        }
    -
    -        return redirect(route('rules.index'));
    -    }
    -
         /**
          * Update the rule group.
          *
    
  • app/Repositories/RuleGroup/RuleGroupRepository.php+0 3 modified
    @@ -329,10 +329,8 @@ public function maxOrder(): int
          */
         public function resetOrder(): bool
         {
    -        $this->user->ruleGroups()->where('active', false)->update(['order' => 0]);
             $set   = $this->user
                 ->ruleGroups()
    -            ->where('active', true)
                 ->whereNull('deleted_at')
                 ->orderBy('order', 'ASC')
                 ->orderBy('title', 'DESC')
    @@ -363,7 +361,6 @@ public function resetRuleOrder(RuleGroup $ruleGroup): bool
         {
             $set   = $ruleGroup->rules()
                                ->orderBy('order', 'ASC')
    -                           ->where('active', true)
                                ->orderBy('title', 'DESC')
                                ->orderBy('updated_at', 'DESC')
                                ->get(['rules.*']);
    
  • public/v1/js/ff/rules/index.js+29 0 modified
    @@ -59,6 +59,32 @@ function readCookie(name) {
         return null;
     }
     
    +function moveRuleGroup(e) {
    +    let box = $(e.currentTarget);
    +    var direction = box.data('direction');
    +    var groupId = box.data('id');
    +
    +    $.post(moveRuleGroupUrl, {_token: token, direction: direction, id: groupId}).then(function () {
    +        location.reload();
    +    }).fail(function() {
    +        alert('I failed :(');
    +    });
    +
    +    return false;
    +}
    +
    +function duplicateRule(e) {
    +    let box = $(e.currentTarget);
    +    var ruleId = box.data('id');
    +    $.post(duplicateRuleUrl, {_token: token, id: ruleId}).then(function () {
    +        location.reload();
    +    }).fail(function() {
    +        alert('I failed :(');
    +    });
    +
    +    return false;
    +}
    +
     
     $(function () {
           "use strict";
    @@ -71,6 +97,9 @@ $(function () {
               }
           );
     
    +      $('.move-group').click(moveRuleGroup);
    +      $('.duplicate-rule').click(duplicateRule);
    +
           $('.rules-box').each(function (i, v) {
               var box = $(v);
               var groupId = box.data('group');
    
  • resources/views/v1/rules/index.twig+7 3 modified
    @@ -45,11 +45,11 @@
                                                         class="fa fa-fw fa-power-off"></span> {{ trans('firefly.apply_rule_group_selection', {title: ruleGroup.title}) }}
                                             </a></li>
                                         {% if ruleGroup.order > 1 %}
    -                                        <li><a href="{{ route('rule-groups.up',ruleGroup.id) }}"><span
    +                                        <li><a href="#" class="move-group" data-direction="up" data-id="{{ ruleGroup.id }}"><span
                                                             class="fa fa-fw fa-arrow-up"></span> {{ 'move_rule_group_up'|_ }}</a></li>
                                         {% endif %}
                                         {% if ruleGroup.order < ruleGroups|length %}
    -                                        <li><a href="{{ route('rule-groups.down',ruleGroup.id) }}"><span
    +                                        <li><a href="#" class="move-group" data-direction="down" data-id="{{ ruleGroup.id }}"><span
                                                             class="fa fa-fw fa-arrow-down"></span> {{ 'move_rule_group_down'|_ }}
                                                 </a></li>
                                         {% endif %}
    @@ -105,7 +105,7 @@
                                                     {% endif %}
     
                                                     {# duplicate rule #}
    -                                                <a href="{{ route('rules.duplicate',rule.id) }}" class="btn btn-default" title=" {{ trans('firefly.duplicate_rule', {title: rule.title}) }}"><span class="fa fa-fw fa-copy"></span></a>
    +                                                <a href="#" class="btn btn-default duplicate-rule" data-id="{{ rule.id }}" title=" {{ trans('firefly.duplicate_rule', {title: rule.title}) }}"><span class="fa fa-fw fa-copy"></span></a>
                                                 </div>
                                             </td>
                                             <td class="markdown">
    @@ -195,6 +195,10 @@
     
     {% endblock %}
     {% block scripts %}
    +    <script type="text/javascript" nonce="{{ JS_NONCE }}">
    +        var moveRuleGroupUrl = '{{ route('rule-groups.move') }}';
    +        var duplicateRuleUrl = '{{ route('rules.duplicate') }}';
    +    </script>
         <script type="text/javascript" src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script>
         <script type="text/javascript" src="v1/js/ff/rules/index.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script>
     {% endblock %}
    
  • routes/web.php+5 4 modified
    @@ -917,7 +917,7 @@ static function () {
             Route::get('create-from-bill/{bill}', ['uses' => 'Rule\CreateController@createFromBill', 'as' => 'create-from-bill']);
             Route::get('create-from-journal/{tj}', ['uses' => 'Rule\CreateController@createFromJournal', 'as' => 'create-from-journal']);
             Route::post('store', ['uses' => 'Rule\CreateController@store', 'as' => 'store']);
    -        Route::get('duplicate/{rule}', ['uses' => 'Rule\CreateController@duplicate', 'as' => 'duplicate']);
    +        Route::post('duplicate', ['uses' => 'Rule\CreateController@duplicate', 'as' => 'duplicate']);
     
             // delete controller
             Route::get('delete/{rule}', ['uses' => 'Rule\DeleteController@delete', 'as' => 'delete']);
    @@ -949,10 +949,11 @@ static function () {
             Route::get('create', ['uses' => 'RuleGroup\CreateController@create', 'as' => 'create']);
             Route::get('edit/{ruleGroup}', ['uses' => 'RuleGroup\EditController@edit', 'as' => 'edit']);
             Route::get('delete/{ruleGroup}', ['uses' => 'RuleGroup\DeleteController@delete', 'as' => 'delete']);
    -        Route::get('up/{ruleGroup}', ['uses' => 'RuleGroup\EditController@up', 'as' => 'up']);
    -        Route::get('down/{ruleGroup}', ['uses' => 'RuleGroup\EditController@down', 'as' => 'down']);
    -        Route::get('select/{ruleGroup}', ['uses' => 'RuleGroup\ExecutionController@selectTransactions', 'as' => 'select-transactions']);
     
    +        // new route to move rule groups:
    +        Route::post('move', ['uses' => 'RuleGroup\EditController@moveGroup', 'as' => 'move']);
    +
    +        Route::get('select/{ruleGroup}', ['uses' => 'RuleGroup\ExecutionController@selectTransactions', 'as' => 'select-transactions']);
             Route::post('store', ['uses' => 'RuleGroup\CreateController@store', 'as' => 'store']);
             Route::post('update/{ruleGroup}', ['uses' => 'RuleGroup\EditController@update', 'as' => 'update']);
             Route::post('destroy/{ruleGroup}', ['uses' => 'RuleGroup\DeleteController@destroy', 'as' => 'destroy']);
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.