CVE-2026-52767
YesWiki: Unauthenticated ActivityPub Signature-Verification Bypass via `!openssl_verify(...)` accepting `int(-1)`
YesWiki is a wiki system written in PHP. From version 4.6.2 to before version 4.6.6, HttpSignatureService::verifySignature() checks the result of PHP's openssl_verify() with a loose boolean negation - if (!openssl_verify(...)) { throw ... }. PHP's openssl_verify has four possible return values: 1, 0, -1, and "false". The -1 row is the bypass: PHP's truthiness rules make -1 a truthy value, so !(-1) === false, the throw is skipped, and the controller proceeds to processActivity(). Any condition that makes OpenSSL's EVP_VerifyFinal() return -1 triggers the bypass. The reachable consequence is the controller silently treats a failed verification as success and processes the attacker's payload. This issue has been patched in version 4.6.6.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- High
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
8.2
YesWiki: Unauthenticated ActivityPub Signature-Verification Bypass via `!openssl_verify(...)` accepting `int(-1)`
YesWiki is a wiki system written in PHP. From version 4.6.2 to before version 4.6.6, HttpSignatureService::verifySignature() checks the result of PHP's openssl_verify() with a loose boolean negation - if (!openssl_verify(...)) { throw ... }. PHP's openssl_verify has four possible return values: 1, 0, -1, and "false". The -1 row is the bypass: PHP's truthiness rules make -1 a truthy value, so !(-1) === false, the throw is skipped, and the controller proceeds to processActivity(). Any condition that makes OpenSSL's EVP_VerifyFinal() return -1 triggers the bypass. The reachable consequence is the controller silently treats a failed verification as success and processes the attacker's payload. This issue has been patched in version 4.6.6.
- Record
CVE-2026-52767- CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L - Weakness
- CWE-347
- Updated
- 2026-11-02
EUVDEUVD-2026-71800
8.2
YesWiki is a wiki system written in PHP. From version 4.6.2 to before version 4.6.6, HttpSignatureService::verifySignature() checks the result of PHP's openssl_verify() with a loose boolean negation - if (!openssl_verify(...)) { throw ... }. PHP's openssl_verify has four possible return values: 1, 0, -1, and "false". The -1 row is the bypass: PHP's truthiness rules make -1 a truthy value, so !(-1) === false, the throw is skipped, and the controller proceeds to processActivity(). Any condition that makes OpenSSL's EVP_VerifyFinal() return -1 triggers the bypass. The reachable consequence is the controller silently treats a failed verification as success and processes the attacker's payload. This issue has been patched in version 4.6.6.
- Record
EUVD-2026-71800- CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-mv28-wj57-f57g
8.2
YesWiki Vulnerable to Unauthenticated ActivityPub Signature-Verification Bypass via `!openssl_verify(...)` accepting `int(-1)`
Summary
HttpSignatureService::verifySignature() checks the result of PHP's openssl_verify() with a loose boolean negation - if (!openssl_verify(...)) { throw ... }. PHP's openssl_verify has four possible return values:
| return | meaning | !return |
| ------ | ------------------------------------------------ | --------- |
| 1 | signature is valid | false |
| 0 | signature is invalid | true ✓ |
| -1 | the verify call itself failed (internal error) | false ❌ |
| false| input rejected by PHP's argument validation | true ✓ |
The -1 row is the bypass: PHP's truthiness rules make -1 a truthy value, so !(-1) === false, the throw is skipped, and the controller proceeds to processActivity(). Any condition that makes OpenSSL's EVP_VerifyFinal() return -1 triggers the bypass.
The two practical paths to -1 we are aware of:
- DSA / EC public key with an RSA-only algorithm.
openssl_verify(..., $dsaKey, "RSA-SHA256")returnsint(-1)on PHP 8.3 + OpenSSL 3.x. This is the path the PoC uses; it works against an unmodifiedphp:8.3-apachelab and against any deployment using the runtime stack YesWiki's own docker image ships. - Older PHP + older OpenSSL where any unrecognised digest name returned
-1rather thanfalse. The reporting research mentions this path; on current stacksfalseis returned instead and the throw fires correctly. The DSA path replaces it.
The reachable consequence is the same in both cases - the controller silently treats a failed verification as success and processes the attacker's payload.
Details
Affected component
- File:
tools/bazar/services/HttpSignatureService.php - Method:
HttpSignatureService::verifySignature(Request $request) - Sink: line 130
// tools/bazar/services/HttpSignatureService.php (v4.6.5 = origin/doryphore-dev HEAD)
public function verifySignature(Request $request) {
... // [Signature parse,
// outbound key fetch — see the SSRF advisory]
$actorPublicKey = openssl_get_publickey($actor['publicKey']['publicKeyPem']);
...
if (!openssl_verify( // (a) LOOSE BOOLEAN CHECK
join("\n", $sigParts),
base64_decode($sigConf['signature']),
$actorPublicKey,
strtoupper($sigConf['algorithm'])
)) {
throw new Exception('Signature verification failed'); // (b) skipped when openssl_verify == -1
}
if ($request->headers->get('Digest') !== $this->getDigest($request->getContent())) {
throw new Exception('Digest mismatch'); // (c) still enforced — easy to satisfy
}
}
The inbox controller calls verifySignature() and then runs processActivity($activity, $form), which is what actually mutates state.
End-to-end attack chain
A single unauthenticated POST per operation. No session, no CSRF, no real signature.
-
Stand up an actor document that the attacker controls — any public web server (or webhook receiver) that returns a JSON body with the shape:
{ "id": "<exact URL the server will GET>", "publicKey": { "id": "<same URL>", "publicKeyPem": "<DSA public key in PEM form>" } } -
Send a Create / Update / Delete activity to
POST /api/forms/{enabled-form-id}/actor/inbox:POST /?api/forms/2/actor/inbox HTTP/1.1 Host: target.example Content-Type: application/activity+json Date: <RFC1123 date> Digest: SHA-256=<base64(sha256(body))> Signature: keyId="<actor URL>",algorithm="RSA-SHA256",headers="(request-target) host date digest content-type",signature="anVuaw==" …
- Record
GHSA-mv28-wj57-f57g- CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L - Weakness
- CWE-347
- Updated
- 2026-11-02
NVD
8.2
YesWiki is a wiki system written in PHP. From version 4.6.2 to before version 4.6.6, HttpSignatureService::verifySignature() checks the result of PHP's openssl_verify() with a loose boolean negation - if (!openssl_verify(...)) { throw ... }. PHP's openssl_verify has four possible return values: 1, 0, -1, and "false". The -1 row is the bypass: PHP's truthiness rules make -1 a truthy value, so !(-1) === false, the throw is skipped, and the controller proceeds to processActivity(). Any condition that makes OpenSSL's EVP_VerifyFinal() return -1 triggers the bypass. The reachable consequence is the controller silently treats a failed verification as success and processes the attacker's payload. This issue has been patched in version 4.6.6.
- Record
CVE-2026-52767- CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L - Weakness
- CWE-347
- Updated
- 2026-11-02
OSV
8.2
YesWiki: Unauthenticated ActivityPub Signature-Verification Bypass via `!openssl_verify(...)` accepting `int(-1)`
YesWiki is a wiki system written in PHP. From version 4.6.2 to before version 4.6.6, HttpSignatureService::verifySignature() checks the result of PHP's openssl_verify() with a loose boolean negation - if (!openssl_verify(...)) { throw ... }. PHP's openssl_verify has four possible return values: 1, 0, -1, and "false". The -1 row is the bypass: PHP's truthiness rules make -1 a truthy value, so !(-1) === false, the throw is skipped, and the controller proceeds to processActivity(). Any condition that makes OpenSSL's EVP_VerifyFinal() return -1 triggers the bypass. The reachable consequence is the controller silently treats a failed verification as success and processes the attacker's payload. This issue has been patched in version 4.6.6.
- Record
CVE-2026-52767- CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L - Weakness
- CWE-347
- Updated
- 2026-11-02
OSVGHSA-mv28-wj57-f57g
8.2
YesWiki Vulnerable to Unauthenticated ActivityPub Signature-Verification Bypass via `!openssl_verify(...)` accepting `int(-1)`
Summary
HttpSignatureService::verifySignature() checks the result of PHP's openssl_verify() with a loose boolean negation - if (!openssl_verify(...)) { throw ... }. PHP's openssl_verify has four possible return values:
| return | meaning | !return |
| ------ | ------------------------------------------------ | --------- |
| 1 | signature is valid | false |
| 0 | signature is invalid | true ✓ |
| -1 | the verify call itself failed (internal error) | false ❌ |
| false| input rejected by PHP's argument validation | true ✓ |
The -1 row is the bypass: PHP's truthiness rules make -1 a truthy value, so !(-1) === false, the throw is skipped, and the controller proceeds to processActivity(). Any condition that makes OpenSSL's EVP_VerifyFinal() return -1 triggers the bypass.
The two practical paths to -1 we are aware of:
- DSA / EC public key with an RSA-only algorithm.
openssl_verify(..., $dsaKey, "RSA-SHA256")returnsint(-1)on PHP 8.3 + OpenSSL 3.x. This is the path the PoC uses; it works against an unmodifiedphp:8.3-apachelab and against any deployment using the runtime stack YesWiki's own docker image ships. - Older PHP + older OpenSSL where any unrecognised digest name returned
-1rather thanfalse. The reporting research mentions this path; on current stacksfalseis returned instead and the throw fires correctly. The DSA path replaces it.
The reachable consequence is the same in both cases - the controller silently treats a failed verification as success and processes the attacker's payload.
Details
Affected component
- File:
tools/bazar/services/HttpSignatureService.php - Method:
HttpSignatureService::verifySignature(Request $request) - Sink: line 130
// tools/bazar/services/HttpSignatureService.php (v4.6.5 = origin/doryphore-dev HEAD)
public function verifySignature(Request $request) {
... // [Signature parse,
// outbound key fetch — see the SSRF advisory]
$actorPublicKey = openssl_get_publickey($actor['publicKey']['publicKeyPem']);
...
if (!openssl_verify( // (a) LOOSE BOOLEAN CHECK
join("\n", $sigParts),
base64_decode($sigConf['signature']),
$actorPublicKey,
strtoupper($sigConf['algorithm'])
)) {
throw new Exception('Signature verification failed'); // (b) skipped when openssl_verify == -1
}
if ($request->headers->get('Digest') !== $this->getDigest($request->getContent())) {
throw new Exception('Digest mismatch'); // (c) still enforced — easy to satisfy
}
}
The inbox controller calls verifySignature() and then runs processActivity($activity, $form), which is what actually mutates state.
End-to-end attack chain
A single unauthenticated POST per operation. No session, no CSRF, no real signature.
-
Stand up an actor document that the attacker controls — any public web server (or webhook receiver) that returns a JSON body with the shape:
{ "id": "<exact URL the server will GET>", "publicKey": { "id": "<same URL>", "publicKeyPem": "<DSA public key in PEM form>" } } -
Send a Create / Update / Delete activity to
POST /api/forms/{enabled-form-id}/actor/inbox:POST /?api/forms/2/actor/inbox HTTP/1.1 Host: target.example Content-Type: application/activity+json Date: <RFC1123 date> Digest: SHA-256=<base64(sha256(body))> Signature: keyId="<actor URL>",algorithm="RSA-SHA256",headers="(request-target) host date digest content-type",signature="anVuaw==" …
- Record
GHSA-mv28-wj57-f57g- CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L - Weakness
- CWE-347
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
yeswiki/yeswikiPackagist |
4.6.2 up to 4.6.6 | 4.6.6 |
yeswiki/yeswikiPackagist |
4.6.2 up to 4.6.6, plus listed versions v4.6.2v4.6.3v4.6.4v4.6.5 |
4.6.6 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-52767 is a high vulnerability (CVSS 8.2) in Yeswiki, published July 9, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.35%, the 29th percentile. Fixed in yeswiki/yeswiki 4.6.6.
References
github.com/YesWiki/yeswiki/security/advisories/GHSA-mv28-wj57-f57g
github.com/YesWiki/yeswiki/commit/d1795e0301e1a1078f17b4b98f56fff70de2029e
github.com/YesWiki/yeswiki/releases/tag/v4.6.6
github.com/advisories/GHSA-mv28-wj57-f57g
github.com/CVEProject/cvelistV5/tree/main/cves/2026/52xxx/CVE-2026-52767.json
nvd.nist.gov/vuln/detail/CVE-2026-52767
github.com/YesWiki/yeswiki
Left out here: the rest of 2 source descriptions. The JSON record has them.