An order-creation signature (visible to the payer in popup submit.php
URLs) could be replayed as a payment-success notification, with no
merchant key required. Two compounding defects:
- CanonicalizeReturnURL kept client-supplied query parameters, so a
return_url ending in '&trade_status=TRADE_SUCCESS' entered the signed
order string verbatim;
- the sign base string concatenates values unescaped, so a callback that
decodes return_url only up to the smuggled pair re-sorts to a
byte-identical sign base and passes verification.
Fixes:
- CanonicalizeReturnURL now drops client query parameters entirely; the
server builds its own signed set and the client query has no
legitimate use;
- VerifyNotification rejects any parameter outside the genuine async
notify set (pid/trade_no/out_trade_no/type/name/money/trade_status/
param/sign/sign_type), failing closed against the whole smuggling
class regardless of the injection point.
Regression tests cover the PoC payload, full order-URL replay, genuine
callback acceptance, unknown-parameter rejection (including empty
values), and the return_url smuggling strip. Genuinely paid orders that
an exotic upstream variant might reject are still recovered through the
existing upstream QueryOrder reconcile path.