Skip to content

Critical: invoice payment without ownership check + negative amounts — unlimited money creation and account draining #169

Description

@Pajt9whauht283as

Severity: Critical — invoice payment without ownership check + negative amounts → unlimited money creation and account draining

src/server/services/invoice/invoice.service.ts:88-131 (payInvoice) + src/server/services/invoice/invoice.controller.ts:53-58 (createInvoice):

// createInvoice — req.data passed straight through (fromIdentifier/toIdentifier/amount from client)
async createInvoice(req: Request<CreateInvoiceInput>) {
  const data = await this._InvoiceService.createInvoice(req.data);
  ...
}

// payInvoice — no check that fromAccountId belongs to req.source
const fromAccount = await this._accountDB.getAccountById(req.data.fromAccountId, t);
...
const amount = invoice.getDataValue('amount');
if (accountBalance < amount) { throw ... }   // negative amount always passes
await this._accountDB.transfer({ amount, fromAccount, toAccount, transaction: t });  // balance-amount / balance+amount

Exploits (any player):

  1. Money creation: create an invoice with amount: -5000, pay it with your own account → your balance increases by 5000 (balance - (-5000)), the payee loses 5000. Repeat for unlimited money.
  2. Account draining: payInvoice accepts any fromAccountId (sequential IDs, enumerable) — pay someone else's invoice using the VICTIM's account as the source → drain any account. Compare with handleInternalTransfer (transaction.service.ts:101) which uses getAuthorizedAccountByIdpayInvoice skips this entirely.

Same class of bug: handleExternalTransfer (transaction.service.ts:154) fetches fromAccount by req.data.fromAccountId with no ownership check → attacker adds their own external account, then transfers from the victim's account into it.

Also depositMoney (account.service.ts:407-460): no amount <= 0 check (withdraw has it at line 466) → DepositMoney with amount: -5000 on any accountId (no authorization check, unlike withdraw) → +5000 cash from nothing.

Verificat: transfer() (account.db.ts:93-94) applies -amount/+amount with no validation; createInvoice doesn't override identities server-side (compare createOnlineInvoice which sets from/toIdentifier from req.source).

Fix: createInvoice must set identities from req.source and require amount > 0; payInvoice/handleExternalTransfer/depositMoney must verify account ownership (getAuthorizedAccountById) and reject amount <= 0. I can share a reproduction script privately if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions