CVE-2021-3730 (PHP, published 2021-08-23T13: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/Budget/AvailableBudgetController.php
app/Repositories/Budget/AvailableBudgetRepository.php
app/Repositories/Budget/AvailableBudgetRepositoryInterface.php
app/Repositories/Budget/BudgetLimitRepository.php
public/v1/js/ff/budgets/index.js
resources/views/v1/budgets/index.twig
routes/web.php
Commit message: Fix https://huntr.dev/bounties/ea181323-51f8-46a2-a60f-6a401907feb7/


Vulnerable code:
    public function delete(AvailableBudget $availableBudget)
        $this->abRepository->destroyAvailableBudget($availableBudget);
        session()->flash('success', trans('firefly.deleted_ab'));
            throw new FireflyException('200004: Budget does not exist.'); 
            throw new FireflyException('200027: Budget limit already exists.'); 


//
//
// function drawSpentBar() {
//     "use strict";
//     if ($('.spentBar').length > 0) {
//         var overspent = spent > budgeted;
//         var pct;
//
//         if (overspent) {
//             // draw overspent bar
//             pct = (budgeted / spent) * 100;
//             $('.spentBar .progress-bar-warning').css('width', pct + '%');
//             $('.spentBar .progress-bar-danger').css('width', (100 - pct) + '%');
//         } else {
//             // draw normal bar:
//             pct = (spent / budgeted) * 100;
//             $('.spentBar .progress-bar-info').css('width', pct + '%');
//         }
//     }
// }
//
// function drawBudgetedBar() {
//     "use strict";
//
//     if ($('.budgetedBar').length > 0) {
//         var budgetedMuch = budgeted > available;
//
//         // recalculate percentage:
//
//         var pct;
//         if (budgetedMuch) {
//             // budgeted too much.
//             pct = (available / budgeted) * 100;
//             $('.budgetedBar .progress-bar-warning').css('width', pct + '%');
//             $('.budgetedBar .progress-bar-danger').css('width', (100 - pct) + '%');
//             $('.budgetedBar .progress-bar-info').css('width', 0);
//         } else {
//             pct = (budgeted / available) * 100;
//             $('.budgetedBar .progress-bar-warning').css('width', 0);
//             $('.budgetedBar .progress-bar-danger').css('width', 0);
//             $('.budgetedBar .progress-bar-info').css('width', pct + '%');
//         }
//
//         $('#budgetedAmount').html(currencySymbol + ' ' + budgeted.toFixed(2));
//     }
// }

// /**
//  *
//  * @param e
//  */
// function updateBudgetedAmounts(e) {
//     "use strict";
//     var target = $(e.target);
//     var id = target.data('id');
//     var leftCell = $('td[class$="left"][data-id="' + id + '"]');
//     var link = $('a[data-id="' + id + '"][class="budget-link"]');
//     var value = target.val();
//     var original = target.data('original');
//
//     // disable input
//     target.prop('disabled', true);
//
//     // replace link (for now)
//     link.attr('href', '#');
//
//     // replace "left" with spinner.
//     leftCell.empty().html('<i class="fa fa-fw fa-spin fa-spinner"></i>');
//
//     // send a post to Firefly to update the amount:
//     var newUri = budgetAmountUri.replace("REPLACE", id);
//
//     $.post(newUri, {amount: value, start: periodStart, end: periodEnd, _token: token}).done(function (data) {
//
//         // difference between new value and original value
//         var difference = value - original;
//
//         // update budgeted value
//         budgeted = budgeted + difference;
//
//         // fill in "left" value:
//
//
//         if (data.left_per_day !== null) {
//             leftCell.html(data.left + ' (' + data.left_per_day + ')');
//         } else {
//             leftCell.html(data.left);
//         }
//
//         // update "budgeted" input:
//         target.val(data.amount);
//
//         // enable thing again
//         target.prop('disabled', false);
//
//         // set new original value:
//         target.data('original', data.amount);
//
//         // run drawBudgetedBar() again:
//         drawBudgetedBar();
//
//         // update the link if relevant:
//         link.attr('href', 'budgets/show/' + id);
//         if (data.limit > 0) {
//             link.attr('href', 'budgets/show/' + id + '/' + data.limit);
//         }
//
//         // update the warning if relevant:
//         if (data.large_diff === true) {
//             $('span[class$="budget_warning"][data-id="' + id + '"]').html(data.warn_text).show();
//             console.log('Show warning for budget');
//         } else {
//             $('span[class$="budget_warning"][data-id="' + id + '"]').empty().hide();
//         }
//     });
// }

// /**
//  *
//  * @returns {boolean}
//  */
// function updateIncome() {
//     "use strict";
//     $('#defaultModal').empty().load(updateIncomeUri, function () {
//         $('#defaultModal').modal('show');
//     });
//
//     return false;
// }
                                        <a href="{{ route('available-budgets.delete', [budget.id]) }}" data-id="{{ budget.id }}"
                                           class="delete_ab btn btn-danger btn-xs"><span class="fa fa-trash"></span></a>
        Route::get('delete/{availableBudget}', ['uses' => 'Budget\AvailableBudgetController@delete', 'as' => 'delete']);

Fixed code:
    public function delete(Request $request)
        $id = (int)$request->get('id');
        if (0 !== $id) {
            $availableBudget = $this->abRepository->findById($id);
            if (null !== $availableBudget) {
                $this->abRepository->destroyAvailableBudget($availableBudget);
                session()->flash('success', trans('firefly.deleted_ab'));
            }
        }
    /**
     * @inheritDoc
     */
    public function findById(int $id): ?AvailableBudget
    {
        return $this->user->availableBudgets->find($id);
    }

    /**
     * @param int $id
     *
     * @return AvailableBudget|null
     */
    public function findById(int $id): ?AvailableBudget;


            throw new FireflyException('200004: Budget does not exist.');
            throw new FireflyException('200027: Budget limit already exists.');

    $('.delete_ab').on('click', deleteAvailableBudget);
function deleteAvailableBudget(e) {
    //
    e.preventDefault();
    var button = $(e.currentTarget);
    var abId = button.data('id');
    $.post(deleteABUrl, {_token: token, id: abId}).then(function () {
        // lame but it works.
        location.reload();
    });
    return false;
}
                                        <a href="#" data-id="{{ budget.id }}" class="delete_ab btn btn-danger btn-xs"><span class="fa fa-trash"></span></a>
        var deleteABUrl = "{{ route('available-budgets.delete') }}";
        Route::post('delete', ['uses' => 'Budget\AvailableBudgetController@delete', 'as' => 'delete']);