CVE-2021-3900 (PHP, published 2021-10-27T18:15Z)
Description: value: firefly-iii is vulnerable to Cross-Site Request Forgery (CSRF)
Severity: MEDIUM (CVSS v3 base score: 6.5)
Weakness: CWE-352 \u2014 Cross-Site Request Forgery (CSRF)
The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
Repository: https://github.com/firefly-iii/firefly-iii
Files changed:
app/Http/Controllers/Rule/CreateController.php
app/Http/Controllers/RuleGroup/EditController.php
app/Repositories/RuleGroup/RuleGroupRepository.php
public/v1/js/ff/rules/index.js
resources/views/v1/rules/index.twig
routes/web.php
Commit message: Catch CSRF issues


Vulnerable code:
     * @return RedirectResponse
    public function duplicate(Rule $rule): RedirectResponse
        $newRule = $this->ruleRepos->duplicate($rule);

        session()->flash('success', trans('firefly.duplicated_rule', ['title' => $rule->title, 'newTitle' => $newRule->title]));
        return redirect(route('rules.index'));
     * Move a rule group down.
     * @param RuleGroup $ruleGroup
     * @return RedirectResponse|Redirector
    public function down(RuleGroup $ruleGroup)
        $maxOrder = $this->repository->maxOrder();
        $order    = (int)$ruleGroup->order;
        if ($order < $maxOrder) {
            $newOrder = $order + 1;
            $this->repository->setOrder($ruleGroup, $newOrder);

        return redirect(route('rules.index'));
    /**
     * 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'));
    }

        $this->user->ruleGroups()->where('active', false)->update(['order' => 0]);
            ->where('active', true)
                           ->where('active', true)
                                        <li><a href="{{ route('rule-groups.up',ruleGroup.id) }}"><span
                                        <li><a href="{{ route('rule-groups.down',ruleGroup.id) }}"><span
                                                <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>
        Route::get('duplicate/{rule}', ['uses' => 'Rule\CreateController@duplicate', 'as' => 'duplicate']);
        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']);

Fixed code:
use Illuminate\Http\JsonResponse;
     * @return JsonResponse
    public function duplicate(Request $request): JsonResponse
        $ruleId = (int)$request->get('id');
        $rule   = $this->ruleRepos->find($ruleId);
        if (null !== $rule) {
            $this->ruleRepos->duplicate($rule);
        }
        return new JsonResponse(['OK']);
use Illuminate\Http\JsonResponse;
     * Move a rule group in either direction.
     * @param Request $request
     * @return JsonResponse
    public function moveGroup(Request $request): JsonResponse
        $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 new JsonResponse(['OK']);

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;
}

      $('.move-group').click(moveRuleGroup);
      $('.duplicate-rule').click(duplicateRule);

                                        <li><a href="#" class="move-group" data-direction="up" data-id="{{ ruleGroup.id }}"><span
                                        <li><a href="#" class="move-group" data-direction="down" data-id="{{ ruleGroup.id }}"><span
                                                <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>
    <script type="text/javascript" nonce="{{ JS_NONCE }}">
        var moveRuleGroupUrl = '{{ route('rule-groups.move') }}';
        var duplicateRuleUrl = '{{ route('rules.duplicate') }}';
    </script>
        Route::post('duplicate', ['uses' => 'Rule\CreateController@duplicate', 'as' => 'duplicate']);
        // 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']);