惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家
Recent Announcements
Recent Announcements
B
Blog
D
Docker
V
V2EX
GbyAI
GbyAI
L
LangChain Blog
博客园 - Franky
U
Unit 42
T
The Blog of Author Tim Ferriss
A
About on SuperTechFans
博客园 - 【当耐特】
Google DeepMind News
Google DeepMind News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Vercel News
Vercel News
博客园_首页
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
Y
Y Combinator Blog
量子位
Blog — PlanetScale
Blog — PlanetScale
罗磊的独立博客

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Authenticating a Webhook Isn't Validating It: A Payment-B...
Muni Nitish Kumar Yaddala · 2026-06-18 · via DEV Community

Muni Nitish Kumar Yaddala

If your app receives webhooks (Stripe, PayPal, GitHub, a payment IPN, anything), there is a subtle bug class that keeps shipping to production. A recent WordPress CVE is a perfect, minimal teaching example, so let's use it to make sure none of us write it.

The pattern (this is the part to remember)

Authenticating a webhook  =  "this message really came from the provider"
Validating  a webhook     =  "the data in this message matches what I expect"

Doing the first WITHOUT the second is how money walks out the door.

The real bug, briefly

CVE-2026-9189, in the Contact Form 7 PayPal and Stripe Add-on (version 2.4.9 and older), authenticated PayPal's IPN correctly (it posted back with cmd=_notify-validate and required VERIFIED), then completed an order using an attacker-controlled invoice value, without checking the amount, currency, or recipient.

The invoice is attacker-controlled, so the attacker does not tamper with a signed message. They make a tiny real payment with the invoice set to a high-value pending order. PayPal genuinely verifies that payment, and the plugin marks the expensive order paid. Unauthenticated. CVSS 5.3, CWE-345.

Attacker pays $1, invoice = order #99 (worth $2,000)
   ->  PayPal sends a GENUINE IPN
   ->  plugin: "is this real?"  ->  VERIFIED   (amount never compared)
   ->  order #99 marked PAID.   $1 for a $2,000 order.

Broken vs. fixed

Broken (authenticity checked, data ignored):

// IPN endpoint open to everyone
function cf7pp_paypal_ipn_auth() {
    return true;
}

// Handler: verifies the message is from PayPal, then trusts the payload
$response = wp_remote_post($paypal_post_url, $args);     // _notify-validate
if (strtolower($response['body']) === 'verified') {
    // attacker controls $data['invoice']; amount never checked:
    cf7pp_complete_payment($data['invoice'], 'completed', $data['txn_id']);
}

Fixed (validate the business data against your stored order):

if (strtolower($response['body']) === 'verified') {
    $order = get_order($data['invoice']);   // load the pending order

    // 1) amount + currency must match what you charged
    if (!hash_equals((string)$order->amount,   (string)$data['mc_gross']) ||
        $order->currency !== $data['mc_currency']) {
        return bail('amount/currency mismatch');
    }
    // 2) the money must have gone to YOU
    if (strcasecmp($order->receiver_email, $data['receiver_email']) !== 0) {
        return bail('wrong recipient');
    }
    // 3) idempotency: ignore replays of an already-processed txn
    if (already_processed($data['txn_id'])) {
        return ok('duplicate ignored');
    }
    complete_payment($order->id, 'completed', $data['txn_id']);
}

The webhook validation checklist

Whenever you handle a payment or webhook callback, do all of these, not just the first:

  • [ ] Authenticate the message (signature, provider postback, shared secret).
  • [ ] Match the amount and currency to the order you created.
  • [ ] Verify the recipient or account is you.
  • [ ] Bind to the order with a server-side value the sender cannot freely set. Do not trust a raw invoice or order_id from the payload as the only link.
  • [ ] Enforce idempotency on the transaction id to defeat replays.
  • [ ] Keep TLS verification ON for any postback (sslverify => true).
  • [ ] Fail closed. If anything does not match, do nothing.

Are you running this plugin?

If you maintain a site using this add-on at 2.4.9 or older to take PayPal payments, update past 2.4.9 now, or disable the PayPal path until you can. Every unpaid order in pending status is a valid target.

Takeaway

The plugin did the hard-looking part (provider authentication) and skipped the easy-looking part (does the money match?). The easy-looking part is the one that protects your revenue. Authenticate the messenger, then always check the message.

Full technical write-up and references: see the canonical post on my blog.
Discovered and responsibly disclosed by Muni Nitish Kumar Yaddala. CVE-2026-9189.