Forums Bug Reports Thread

Trade-feedback reputation poisoning: seller can forge a sale + negative feedback against any user via markSold buyer_username

Patrick Bass · Jun 6 · 9 · 1 Locked
[Major] [High Priority] [Bug Fixed] [Always Reproduces]
🚀 OP Jun 6, 2026 5:40pm

Area: mobieusMarket / BBS (audit p3) · Surface: mobieusMarket /market/{slug}/mark-sold + /market/{slug}/feedback · Dimension: security · Severity: major

OWASP A01 Broken Access Control / business-logic abuse. Any authenticated user can damage a victim's marketplace trade reputation: (1) create a free listing, (2) POST /market/{slug}/mark-sold with buyer_username=<victim>, (3) POST /market/{slug}/feedback with target_user_id=<victim's id> and rating=-1 plus a defamatory body. The victim never bought anything and never consented. The forged negative feedback shows on their public /market/user/<victim>/feedback page and decrements their positive-ratio reputation badge. There is no two-sided confirmation of a sale anywhere in the flow.

Evidence

MarketplaceController.php:825-846 markSold(): `$buyerUsername = trim($_POST['buyer_username'] ?? '');` then `$buyer = User::findByUsername($buyerUsername); if ($buyer) { $buyerId = (int) $buyer['id']; } ListingSale::record((int) $listing['id'], $buyerId);` — the seller names ANY existing username and a listing_sales row is inserted with that user's id, with no consent/verification the user ever transacted (ListingSale::record at ListingSale.php:26 just INSERTs buyer_id). Then submitFeedback (MarketplaceController.php:1444-1466) accepts the seller's `target_user_id` as long as it is in `ListingSale::buyerIdsFor` — which now contains the forged buyer — and writes `TradeFeedback::create(... $rating, 'seller', ...)` with rating as low as -1 (line 1480-1489). TradeFeedback::summaryFor (TradeFeedback.php:70-88) aggregates ALL rows WHERE to_user_id=victim into the negative counter, and TradeFeedback::receivedBy (line 52-67) renders them on the public page TradeFeedbackController@show (/market/user/{username}/feedback).

Suggested fix. Do not let a seller unilaterally bind an arbitrary buyer to a sale. Either require buyer-side confirmation before a listing_sales row is created (an offer/accept handshake), or restrict seller->buyer feedback to buyers who have themselves taken an action proving the transaction (e.g. the buyer must have initiated/confirmed the purchase). At minimum, do not surface seller-initiated feedback on the recipient's public reputation until the named buyer confirms the transaction.

Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.


Patrick Bass
@mobieus

🚀 Jun 7, 2026 5:15am

Resolved — fixed and deployed. Commit e23a8fec596a, shipped dev-first then to all tenants on 2026-06-06.

Verified the seller cannot bind an arbitrary buyer: markSold() (lines 836-884) forces $buyerId=null and reads no seller-supplied buyer, and ListingSale::record is only called there, so listing_sales.buyer_id never becomes non-NULL via seller action. submitFeedback() (lines 1419-1520) gates all feedback on an existing buyer_id IS NOT NULL sale row and restricts the seller path to a target_user_id that must appear in ListingSale::buyerIdsFor(), so seller->buyer feedback is impossible until a buyer-confirmed (handshake) sale binds the buyer. The remediation the fix prescribes is fully present and php -l passes; no edit was needed and adding one would risk regressing correct behavior.

Status: fixed. Thread closed and locked.


Patrick Bass
@mobieus

Log in or register to reply to this thread.