VYPR
High severity7.3NVD Advisory· Published Jun 7, 2026

CVE-2026-11462

CVE-2026-11462

Description

BeikeShop's Stripe plugin improperly authorizes requests by not verifying webhook signatures, allowing attackers to mark orders as paid without payment.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

BeikeShop's Stripe plugin improperly authorizes requests by not verifying webhook signatures, allowing attackers to mark orders as paid without payment.

Vulnerability

A vulnerability exists in the Stripe Plugin's StripeController.php file within BeikeShop versions up to and including 1.6.0. The callback function fails to validate the Stripe-Signature header before processing webhook events, leading to improper authorization [1, 2].

Exploitation

An unauthenticated, remote attacker can exploit this vulnerability by sending a forged charge.succeeded event to the /callback/stripe endpoint. The attacker needs to obtain a valid order_number and then send a POST request with a crafted JSON payload containing this order number, bypassing the need for actual payment [2].

Impact

Successful exploitation allows an attacker to mark arbitrary orders as paid without completing payment. This can lead to inventory deduction, manipulation of sales metrics, and the acquisition of goods or services without payment [2].

Mitigation

A patch is available via commit 6719e0fc690ea0a998452092862e0f0a17c65968, which implements proper webhook signature verification using Stripe\Webhook::constructEvent() [1]. It is recommended to update BeikeShop to a version that includes this fix. The exact patched version number is not explicitly stated, but the commit addresses the vulnerability in versions up to 1.6.0 [1, 2].

AI Insight generated on Jun 7, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

3

Patches

1
6719e0fc690e

fix: Fix Stripe Webhook signature verification vulnerability.

https://github.com/beikeshop/beikeshoppushuoJun 4, 2026via nvd-ref
8 files changed · +110 34
  • plugins/Stripe/Bootstrap.php+2 1 modified
    @@ -1,10 +1,11 @@
     <?php
    +
     /**
      * Bootstrap.php
      *
      * @copyright  2023 beikeshop.com - All Rights Reserved
      * @link       https://beikeshop.com
    - * @author     Edward Yang <yangjin@guangda.work>
    + * @author     guangda <service@guangda.work>
      * @created    2023-08-17 15:15:27
      * @modified   2023-08-17 15:15:27
      */
    
  • plugins/Stripe/columns.php+10 1 modified
    @@ -1,10 +1,11 @@
     <?php
    +
     /**
      * Stripe 字段
      *
      * @copyright  2022 beikeshop.com - All Rights Reserved
      * @link       https://beikeshop.com
    - * @author     Edward Yang <yangjin@guangda.work>
    + * @author     guangda <service@guangda.work>
      * @created    2022-06-29 21:16:23
      * @modified   2022-06-29 21:16:23
      */
    @@ -26,6 +27,14 @@
             'rules'       => 'required|min:32',
             'description' => '密钥(Secret key)',
         ],
    +    [
    +        'name'        => 'webhook_secret',
    +        'label_key'   => 'common.webhook_secret',
    +        'type'        => 'string',
    +        'required'    => true,
    +        'rules'       => 'required|min:32',
    +        'description_key' => 'common.webhook_secret_desc',
    +    ],
         [
             'name'        => 'test_mode',
             'label'       => '测试模式',
    
  • plugins/Stripe/config.json+1 1 modified
    @@ -13,6 +13,6 @@
         "icon": "/image/logo.png",
         "author": {
             "name": "成都光大网络科技有限公司",
    -        "email": "yangjin@guangda.work"
    +        "email": "service@guangda.work"
         }
     }
    
  • plugins/Stripe/Controllers/StripeController.php+28 5 modified
    @@ -1,10 +1,11 @@
     <?php
    +
     /**
      * StripeController.php
      *
      * @copyright  2022 beikeshop.com - All Rights Reserved
      * @link       https://beikeshop.com
    - * @author     Edward Yang <yangjin@guangda.work>
    + * @author     guangda <service@guangda.work>
      * @created    2022-08-08 15:58:36
      * @modified   2022-08-08 15:58:36
      */
    @@ -19,6 +20,8 @@
     use Illuminate\Http\Request;
     use Illuminate\Support\Facades\Log;
     use Plugin\Stripe\Services\StripeService;
    +use Stripe\Exception\SignatureVerificationException;
    +use Stripe\Webhook;
     
     class StripeController extends Controller
     {
    @@ -65,11 +68,27 @@ public function callback(Request $request): JsonResponse
             Log::info('====== Start Stripe Callback ======');
     
             try {
    -            $requestData = $request->all();
    +            $webhookSecret = plugin_setting('stripe.webhook_secret');
    +            if (empty($webhookSecret)) {
    +                Log::warning('Stripe webhook secret is not configured');
    +
    +                return json_fail('Stripe webhook secret is not configured', [], 500);
    +            }
    +
    +            $payload   = $request->getContent();
    +            $signature = $request->header('Stripe-Signature', '');
    +            if (empty($signature)) {
    +                Log::warning('Stripe callback missing signature header');
    +
    +                return json_fail('Invalid Stripe signature', [], 400);
    +            }
    +
    +            $event       = Webhook::constructEvent($payload, $signature, $webhookSecret);
    +            $requestData = $event->toArray();
                 Log::info('Request data: ' . json_encode($requestData));
     
    -            $type        = $requestData['type'];
    -            $orderNumber = $request['data']['object']['metadata']['order_number'] ?? '';
    +            $type        = $requestData['type'] ?? '';
    +            $orderNumber = $requestData['data']['object']['metadata']['order_number'] ?? '';
                 $order       = OrderRepo::getOrderByNumber($orderNumber);
     
                 Log::info('Request type: ' . $type);
    @@ -83,10 +102,14 @@ public function callback(Request $request): JsonResponse
     
                 return json_success(trans('Stripe::common.capture_fail'));
     
    +        } catch (SignatureVerificationException|\UnexpectedValueException $e) {
    +            Log::warning('Stripe signature verification failed: ' . $e->getMessage());
    +
    +            return json_fail('Invalid Stripe signature', [], 400);
             } catch (\Exception $e) {
                 Log::info('Stripe error: ' . $e->getMessage());
     
    -            return json_success($e->getMessage());
    +            return json_fail($e->getMessage(), [], 500);
             }
         }
     }
    
  • plugins/Stripe/Lang/en/common.php+21 1 modified
    @@ -1,16 +1,36 @@
     <?php
    +
     /**
      * dd.php
      *
      * @copyright  2022 beikeshop.com - All Rights Reserved
      * @link       https://beikeshop.com
    - * @author     Edward Yang <yangjin@guangda.work>
    + * @author     guangda <service@guangda.work>
      * @created    2022-07-28 16:19:06
      * @modified   2022-07-28 16:19:06
      */
     
     return [
         'publishable_key' => 'Publishable Key',
    +    'webhook_secret'  => 'Webhook Secret',
    +    'webhook_secret_desc' => <<<HTML
    +<div class="mt-2 rounded-3 border border-warning-subtle bg-light p-3">
    +  <div class="fw-bold mb-2">How to get this value</div>
    +  <div class="text-muted mb-2">This field must use the Stripe webhook signing secret that starts with <code>whsec_</code>. Do not use the API Secret Key such as <code>sk_test_...</code> or <code>sk_live_...</code>.</div>
    +  <div class="text-muted mb-2">Why this is required: BeikeShop uses this secret to verify that webhook requests really come from Stripe, preventing forged callback requests from marking unpaid orders as paid.</div>
    +  <div class="mb-1">Steps:</div>
    +  <ol class="mb-2 ps-3">
    +    <li>Log in to Stripe Dashboard.</li>
    +    <li>Open <strong>Developers</strong> -> <strong>Webhooks</strong>.</li>
    +    <li>Click your Webhook Endpoint. If you do not have one yet, create an Endpoint first.</li>
    +    <li>When creating the Endpoint, choose the event <code>checkout.session.completed</code>.</li>
    +    <li>After the Endpoint is created, open it and find <strong>Signing secret</strong>.</li>
    +    <li>Click <strong>Reveal</strong> to show the secret.</li>
    +    <li>Copy the displayed Signing Secret and paste it into this Webhook Secret field.</li>
    +  </ol>
    +  <div class="text-muted">Recommended callback URL: <code>https://your-domain.com/callback/stripe</code>. Use the webhook secret that matches the current environment endpoint.</div>
    +</div>
    +HTML,
     
         'title_info'      => 'Card information',
         'cardnum'         => 'Cardnum',
    
  • plugins/Stripe/Lang/zh_cn/common.php+22 1 modified
    @@ -1,16 +1,37 @@
     <?php
    +
     /**
      * dd.php
      *
      * @copyright  2022 beikeshop.com - All Rights Reserved
      * @link       https://beikeshop.com
    - * @author     Edward Yang <yangjin@guangda.work>
    + * @author     guangda <service@guangda.work>
      * @created    2022-07-28 16:19:06
      * @modified   2022-07-28 16:19:06
      */
     
     return [
         'publishable_key' => '公钥',
    +    'webhook_secret'  => 'Webhook 密钥',
    +    'webhook_secret_desc' => <<<HTML
    +<div class="mt-2 rounded-3 border border-warning-subtle bg-light p-3">
    +  <div class="fw-bold mb-2">如何获取这个值</div>
    +  <div class="text-muted mb-2">这里必须填写 Stripe Webhook 的签名密钥,通常以 <code>whsec_</code> 开头。不要填写 API 密钥,例如 <code>sk_test_...</code> 或 <code>sk_live_...</code>。</div>
    +  <div class="text-muted mb-2">为什么需要这个密钥:BeikeShop 会用它校验 Webhook 回调是否真的来自 Stripe,防止有人伪造支付成功回调,把未支付订单错误标记为已支付。</div>
    +  <div class="mb-1">具体步骤:</div>
    +  <ol class="mb-2 ps-3">
    +    <li>登录 Stripe Dashboard。</li>
    +    <li>进入 <strong>Developers(开发者)</strong> -> <strong>Webhooks</strong>。</li>
    +    <li>点击你的 Webhook Endpoint;如果还没有,就先创建一个 Endpoint。</li>
    +    <li>创建 Endpoint 时选择事件,可以搜索 <code>checkout.session.completed</code>,勾选后继续创建。</li>
    +    <li>创建完成后,点击刚创建的 Endpoint。</li>
    +    <li>找到 <strong>Signing secret</strong>。</li>
    +    <li>点击 <strong>Reveal</strong>(显示)。</li>
    +    <li>复制显示出来的 Signing Secret,然后粘贴到这里的 <strong>Webhook 密钥</strong> 输入框。</li>
    +  </ol>
    +  <div class="text-muted">建议在 Stripe 里把回调地址配置为 <code>https://你的域名/callback/stripe</code>,并确保测试环境和正式环境分别使用各自的 Webhook 密钥。</div>
    +</div>
    +HTML,
     
         'title_info'      => '卡信息',
         'cardnum'         => '卡号',
    
  • plugins/Stripe/Routes/shop.php+2 1 modified
    @@ -1,10 +1,11 @@
     <?php
    +
     /**
      * shop.php
      *
      * @copyright  2022 beikeshop.com - All Rights Reserved
      * @link       https://beikeshop.com
    - * @author     Edward Yang <yangjin@guangda.work>
    + * @author     guangda <service@guangda.work>
      * @created    2022-08-04 16:17:44
      * @modified   2022-08-04 16:17:44
      */
    
  • plugins/Stripe/Services/StripeService.php+24 23 modified
    @@ -1,10 +1,11 @@
     <?php
    +
     /**
      * StripeService.php
      *
      * @copyright  2022 beikeshop.com - All Rights Reserved
      * @link       https://beikeshop.com
    - * @author     Edward Yang <yangjin@guangda.work>
    + * @author     guangda <service@guangda.work>
      * @created    2022-08-08 16:09:21
      * @modified   2022-08-08 16:09:21
      */
    @@ -49,7 +50,7 @@ public function capture($creditCardData): bool
         {
             // web
             if (isset($creditCardData['token'])) {
    -            $tokenId = $creditCardData['token'] ?? '';
    +            $tokenId  = $creditCardData['token'] ?? '';
                 $currency = $this->order->currency_code;
     
                 if (! in_array($currency, self::ZERO_DECIMAL)) {
    @@ -74,32 +75,32 @@ public function capture($creditCardData): bool
                 $charge = $this->stripeClient->charges->create($stripeChargeParameters);
     
                 return $charge['paid'] && $charge['captured'];
    -        } else {
    -            // app
    -            // 从返回的 client_secret 中提取 payment_intent_id
    -            $clientSecret = $creditCardData['paymentIntent'] ?? '';
    -            if (empty($clientSecret)) {
    -                throw new \Exception('Invalid paymentIntent');
    -            }
    +        }
    +        // app
    +        // 从返回的 client_secret 中提取 payment_intent_id
    +        $clientSecret = $creditCardData['paymentIntent'] ?? '';
    +        if (empty($clientSecret)) {
    +            throw new \Exception('Invalid paymentIntent');
    +        }
     
    -            // 提取 payment_intent_id (前缀到第一个 '_secret' 之前的部分)
    -            $paymentIntentId = substr($clientSecret, 0, strpos($clientSecret, '_secret'));
    +        // 提取 payment_intent_id (前缀到第一个 '_secret' 之前的部分)
    +        $paymentIntentId = substr($clientSecret, 0, strpos($clientSecret, '_secret'));
     
    -            // 检查是否成功提取 payment_intent_id
    -            if (empty($paymentIntentId)) {
    -                throw new \Exception('Invalid paymentIntent ID');
    -            }
    -
    -            // 根据 PaymentIntent ID 查询支付状态
    -            $paymentIntent = $this->stripeClient->paymentIntents->retrieve($paymentIntentId);
    +        // 检查是否成功提取 payment_intent_id
    +        if (empty($paymentIntentId)) {
    +            throw new \Exception('Invalid paymentIntent ID');
    +        }
     
    -            // 检查支付状态
    -            if ($paymentIntent->status === 'succeeded') {
    -                return true;
    -            }
    +        // 根据 PaymentIntent ID 查询支付状态
    +        $paymentIntent = $this->stripeClient->paymentIntents->retrieve($paymentIntentId);
     
    -            return false;
    +        // 检查支付状态
    +        if ($paymentIntent->status === 'succeeded') {
    +            return true;
             }
    +
    +        return false;
    +
         }
     
         /**
    

Vulnerability mechanics

Root cause

"The Stripe webhook endpoint does not validate the signature of incoming requests."

Attack vector

A remote, unauthenticated attacker can send a forged POST request to the `/callback/stripe` endpoint. The attacker crafts a payload with `type: "charge.succeeded"` and includes a valid `order_number` in the metadata. This bypasses the intended payment verification process, allowing the attacker to mark arbitrary orders as paid without completing a transaction [ref_id=2].

Affected code

The vulnerability resides in the `StripeController::callback` method within the file `plugins/Stripe/Controllers/StripeController.php`. Prior to the patch, this method directly processed request data without validating the `Stripe-Signature` header, allowing for forged `charge.succeeded` events [ref_id=2].

What the fix does

The patch introduces signature verification for incoming Stripe webhook requests in the `StripeController::callback` method [patch_id=5161668]. It now retrieves the webhook secret from plugin settings and uses `Stripe\Webhook::constructEvent` to validate the request's `Stripe-Signature` header against the payload and secret. This ensures that only legitimate events from Stripe are processed, preventing forged payment success notifications.

Preconditions

  • inputAttacker needs a valid order number from the target system.
  • configThe Stripe plugin must be installed and configured, although the webhook secret itself is not validated before the fix.

Reproduction

curl -X POST https://target.com/callback/stripe \ -H "Content-Type: application/json" \ -d '{ \ "type": "charge.succeeded", \ "data": { \ "object": { \ "metadata": { \ "order_number": "2026051676232" \ } \ } \ } \ }'

Generated on Jun 7, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.