I was reporter #11 for a WPForms PayPal webhook vulnerability

unknownhad2 pts0 comments

Reporter 11: 10 people found the WPForms PayPal bug before me (CVE-2026-4986) :: Himanshu Anand :: Security & Other Notes

Reporter 11: 10 people found the WPForms PayPal bug before me (CVE-2026-4986)

2026-07-22 ::

Himanshu Anand

:: 19 min read (4044 words)

#security

#wordpress

#webhooks

#paypal

#disclosure

#vulnerability-management

#blog

Table of Contents

TLDR⌗

WPForms Lite is a WordPress form plugin with 5 million plus active installations . Public advisory data identifies versions 1.10.0.1 through 1.10.0.4 as affected by CVE-2026-4986 : the PayPal Commerce webhook processed incoming events without first verifying that PayPal actually sent them.

In my local lab, a forged event could change the state of a matching payment record. A forged PAYMENT.CAPTURE.COMPLETED event could mark a matching pending transaction as completed and trigger downstream payment-completed actions. A forged PAYMENT.CAPTURE.DENIED event could mark a matching transaction as denied.

That is the kind of bug where the database starts telling a story that PayPal never actually told.

WPForms fixed the issue in 1.10.0.5 . The 5 million plus number is the plugin’s reach, not a claim that 5 million sites were exploitable. Exposure depended on running an affected version and using the PayPal Commerce path. Please do not turn an install count into a casualty count. Cybersecurity already has enough dramatic arithmetic.

I independently found and reported the issue, but I was not the first researcher. The public advisory credits Sudhanshu Chauhan of RedHunt Labs , and the response to my submission told me I was reporter number 11 .

I was very late to the party. The snacks were gone. The CVE was already being prepared. Ten researchers were inside asking whether anyone had validated the webhook signature.

where this post fits⌗

If you read my earlier post on the death of the 90 day disclosure policy, you saw story one: I found a bug, sent it in and the triage team replied, “you are reporter eleven”.

I left the technical details vague because I did not want to accidentally turn my blog into a free exploit feed. Rude to users, generous to attackers. Bad trade.

The issue has now been patched, the public records exist and users have had time to update. So this is the technical epilogue I promised: the bug, the patch, the duplicate reports and the uncomfortable question hiding behind reporter number 11.

I am calling it an epilogue because another article in this series already called itself the “final” post. Apparently my blog series has the same relationship with the word final that software projects have with final_v2_really_final.zip.

the story (the one I hinted at)⌗

Late April 2026. I was poking at WordPress plugins and looking specifically at payment integrations.

Payment code is interesting audit territory because the security cost of a bug can be real money, the attack surface is often public and developers have to glue several third-party systems together without dropping one tiny, extremely important check on the floor.

WPForms is one of the biggest form plugins on WordPress, so I assumed the code would be well audited.

Mostly.

Mostly is doing a lot of work in that sentence.

I opened the PayPal Commerce integration folder and searched for permission_callback. In a WordPress REST route, that is the part that answers a useful question: “Who is allowed to hit this endpoint?”

Within about 90 seconds, I found this:

register_rest_route(<br>Helpers::get_webhook_endpoint_data()['namespace'],<br>'/' . Helpers::get_webhook_endpoint_data()['route'],<br>'methods' => $methods,<br>'callback' => [ $this, 'dispatch_paypal_webhooks_payload' ],<br>'show_in_index' => false,<br>'permission_callback' => '__return_true',<br>);

permission_callback => '__return_true' means the endpoint is publicly reachable. That is not automatically a vulnerability. Webhook endpoints have to be public. PayPal cannot log in to your WordPress site, complete a CAPTCHA and whisper &ldquo;please process my payment&rdquo; into wp-admin.

The security question is what happens after the request arrives.

I followed the callback. The handler read the raw request body, decoded the JSON, checked the event type against an allowlist and sent the payload to the relevant event handler.

In the affected versions, I could not find the step that established that the request had actually come from PayPal before the event was processed.

No validation of PayPal&rsquo;s transmission signature. No verification callback to PayPal before processing. No meaningful rejection path for a completely unsigned request.

The endpoint was basically saying:

If you can spell PAYMENT.CAPTURE.COMPLETED, please come in. The money room is to your left.

I built a curl request and tested it against a local installation I controlled. It worked on the first attempt. The matching payment record changed state and the payment-completed side effects ran.

Well, cool in the very...

paypal payment reporter wpforms wordpress event

Related Articles